diff --git a/doomsday/build/build.pro b/doomsday/build/build.pro index 8468b1b070..7519caa11d 100644 --- a/doomsday/build/build.pro +++ b/doomsday/build/build.pro @@ -20,12 +20,12 @@ include(../config.pri) QMAKE_STRIP = true # Update the PK3 files. -!deng_nopackres { +!deng_sdk:!deng_nopackres { runPython2InDir($$PWD/scripts/, packres.py --quiet \"$$OUT_PWD/..\") } # Install the launcher. -deng_snowberry { +!deng_sdk:deng_snowberry { SB_ROOT = ../../snowberry SB_DIR = $$DENG_BASE_DIR/snowberry diff --git a/doomsday/client/client.pro b/doomsday/client/client.pro index 1df0e68461..4cab403c53 100644 --- a/doomsday/client/client.pro +++ b/doomsday/client/client.pro @@ -815,9 +815,9 @@ data.files = $$OUT_PWD/../doomsday.pk3 mod.files = \ $$DOOMSDAY_SCRIPTS \ $$DENG_MODULES_DIR/Config.de \ - $$DENG_MODULES_DIR/gui.de \ $$DENG_MODULES_DIR/Log.de \ - $$DENG_MODULES_DIR/recutil.de + $$DENG_MODULES_DIR/recutil.de \ + $$DENG_MODULES_DIR/../../libgui/modules/gui.de # These fonts may be needed during the initial startup busy mode. startupfonts.files = \ diff --git a/doomsday/client/data/defaultstyle.pack/graphics/progress-mini.png b/doomsday/client/data/defaultstyle.pack/graphics/progress-mini.png index 002d762928..f9f98d0ea7 100644 Binary files a/doomsday/client/data/defaultstyle.pack/graphics/progress-mini.png and b/doomsday/client/data/defaultstyle.pack/graphics/progress-mini.png differ diff --git a/doomsday/client/include/network/serverlink.h b/doomsday/client/include/network/serverlink.h index 0c99046a40..7e051f1b01 100644 --- a/doomsday/client/include/network/serverlink.h +++ b/doomsday/client/include/network/serverlink.h @@ -68,15 +68,39 @@ class ServerLink : public de::shell::AbstractLink bool isDiscovering() const; - int foundServerCount() const; + enum FoundMaskFlag + { + Direct = 0x1, + LocalNetwork = 0x2, + MasterServer = 0x4, - QList foundServers() const; + Any = Direct | LocalNetwork | MasterServer + }; + Q_DECLARE_FLAGS(FoundMask, FoundMaskFlag) - bool isFound(de::Address const &host) const; + /** + * @param mask Defines the sources that are enabled when querying for found servers. + */ + int foundServerCount(FoundMask mask = Any) const; - bool foundServerInfo(de::Address const &host, serverinfo_t *info) const; + /** + * @param mask Defines the sources that are enabled when querying for found servers. + */ + QList foundServers(FoundMask mask = Any) const; - bool foundServerInfo(int index, serverinfo_t *info) const; + bool isFound(de::Address const &host, FoundMask mask = Any) const; + + /** + * @param mask Defines the sources that are enabled when querying for found servers. + */ + bool foundServerInfo(de::Address const &host, serverinfo_t *info, + FoundMask mask = Any) const; + + /** + * @param mask Defines the sources that are enabled when querying for found servers. + */ + bool foundServerInfo(int index, serverinfo_t *info, + FoundMask mask = Any) const; signals: void serversDiscovered(); @@ -96,4 +120,6 @@ protected slots: DENG2_PRIVATE(d) }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ServerLink::FoundMask) + #endif // CLIENT_LINK_H diff --git a/doomsday/client/include/ui/dialogs/manualconnectiondialog.h b/doomsday/client/include/ui/dialogs/manualconnectiondialog.h index f89fa63f2e..8d809c68a0 100644 --- a/doomsday/client/include/ui/dialogs/manualconnectiondialog.h +++ b/doomsday/client/include/ui/dialogs/manualconnectiondialog.h @@ -40,7 +40,10 @@ class ManualConnectionDialog : public de::InputDialog, public de::IPersistent void operator << (de::PersistentState const &fromState); public slots: + void queryOrConnect(); + void contentChanged(); void validate(); + void disconnected(); protected: void finish(int result); diff --git a/doomsday/client/include/ui/widgets/mpselectionwidget.h b/doomsday/client/include/ui/widgets/mpselectionwidget.h index 0c0cf12c06..1fae02721f 100644 --- a/doomsday/client/include/ui/widgets/mpselectionwidget.h +++ b/doomsday/client/include/ui/widgets/mpselectionwidget.h @@ -20,6 +20,7 @@ #define DENG_CLIENT_MPSELECTIONWIDGET_H #include +#include "network/net_main.h" /** * Menu that populates itself with available multiplayer games. @@ -31,10 +32,43 @@ class MPSelectionWidget : public de::MenuWidget Q_OBJECT public: - MPSelectionWidget(); + DENG2_DEFINE_AUDIENCE(Selection, void gameSelected(serverinfo_t const &info)) + + enum DiscoveryMode { + NoDiscovery, + DiscoverUsingMaster, + DirectDiscoveryOnly + }; + + /** + * Action for joining a game on a multiplayer server. + */ + class JoinAction : public de::Action + { + public: + JoinAction(serverinfo_t const &sv); + void trigger(); + + private: + DENG2_PRIVATE(d) + }; + +public: + MPSelectionWidget(DiscoveryMode discovery = NoDiscovery); + + /** + * Enables or disables joining games by pressing the menu items in the widget. + * By default, this is enabled. If disabled, one will only get a notification + * about the selection. + * + * @param enableJoin @c true to allow automatic joining, @c false to disallow. + */ + void setJoinGameWhenSelected(bool enableJoin); void setColumns(int numberOfColumns); + serverinfo_t const &serverInfo(de::ui::DataPos pos) const; + signals: void availabilityChanged(); void gameSelected(); diff --git a/doomsday/client/modules/appconfig.de b/doomsday/client/modules/appconfig.de index bba0ec0c6d..d9ed41c62e 100644 --- a/doomsday/client/modules/appconfig.de +++ b/doomsday/client/modules/appconfig.de @@ -23,6 +23,7 @@ # # TODO: make sure the server doesn't run this +import gui import Version import Updater import Log @@ -30,6 +31,8 @@ import Log def setDefaults(d) # Applies the client's defaults. # - d: Record where to set the values. + + gui.setDefaults(d) # Additional Log defaults. d.log.filterBySubsystem = False @@ -47,52 +50,6 @@ def setDefaults(d) record d.input record d.input.mouse d.input.mouse.syncSensitivity = True - - try - import DisplayMode - - # The default audio and video subsystems. - d.video = 'opengl' - d.audio = 'fmod' - - # Window manager defaults. - record d.window - d.window.fsaa = True # Remove this (should be window-specific). - - # Configure the main window. - record d.window.main - d.window.main.showFps = False - d.window.main.center = True - d.window.main.fsaa = True - d.window.main.vsync = True - - # The default window parameters depend on the original display mode. - mode = DisplayMode.originalMode() - - # By default the fullscreen resolution is the desktop resolution. - d.window.main.fullSize = [mode['width'], mode['height']] - - # In windowed mode mode, leave some space on the sides so that - # the first switch to windowed mode does not place the window in an - # inconvenient location. The reduction is done proportionally. - offx = mode['width'] * 0.15 - offy = mode['height'] * 0.15 - d.window.main.rect = [offx, offy, - mode['width'] - 2*offx, - mode['height'] - 2*offy] - d.window.main.colorDepth = mode['depth'] - - if Version.OS == 'windows' or Version.OS == 'macx' - d.window.main.fullscreen = True - d.window.main.maximize = False - else - d.window.main.fullscreen = False - d.window.main.maximize = True - end - - catch NotFoundError - # DisplayMode isn't available on the server. - end # Defaults for the automatic updater. record d.updater @@ -114,5 +71,5 @@ def setDefaults(d) record d.console d.console.snap = True d.console.script = False +end - diff --git a/doomsday/client/src/alertmask.cpp b/doomsday/client/src/alertmask.cpp index 3d1d75c7fb..e1af368366 100644 --- a/doomsday/client/src/alertmask.cpp +++ b/doomsday/client/src/alertmask.cpp @@ -67,7 +67,7 @@ void AlertMask::init() { foreach(Variable const *var, App::config().names().subrecord("alert").members()) { - var->audienceForChange += d; + var->audienceForChange() += d; } d->updateMask(); } diff --git a/doomsday/client/src/clientapp.cpp b/doomsday/client/src/clientapp.cpp index 5a75049b8c..b11e114cb0 100644 --- a/doomsday/client/src/clientapp.cpp +++ b/doomsday/client/src/clientapp.cpp @@ -91,61 +91,6 @@ static Value *Function_App_GamePlugin(Context &, Function::ArgumentValues const return new TextValue(name); } -static Value *Function_App_LoadFont(Context &, Function::ArgumentValues const &args) -{ - LOG_AS("ClientApp"); - try - { - // Try to load the specific font. - Block data(App::fileSystem().root().locate(args.at(0)->asText())); - int id; - id = QFontDatabase::addApplicationFontFromData(data); - if(id < 0) - { - LOG_RES_WARNING("Failed to load font:"); - } - else - { - LOG_RES_VERBOSE("Loaded font: %s") << args.at(0)->asText(); - //qDebug() << args.at(0)->asText(); - //qDebug() << "Families:" << QFontDatabase::applicationFontFamilies(id); - } - } - catch(Error const &er) - { - LOG_RES_WARNING("Failed to load font:\n") << er.asText(); - } - return 0; -} - -static Value *Function_App_AddFontMapping(Context &, Function::ArgumentValues const &args) -{ - // arg 0: family name - // arg 1: dictionary with [Text style, Number weight] => Text fontname - - // styles: regular, italic - // weight: 0-99 (25=light, 50=normal, 75=bold) - - NativeFont::StyleMapping mapping; - - DictionaryValue const &dict = args.at(1)->as(); - DENG2_FOR_EACH_CONST(DictionaryValue::Elements, i, dict.elements()) - { - NativeFont::Spec spec; - ArrayValue const &key = i->first.value->as(); - if(key.at(0).asText() == "italic") - { - spec.style = NativeFont::Italic; - } - spec.weight = roundi(key.at(1).asNumber()); - mapping.insert(spec, i->second->asText()); - } - - NativeFont::defineMapping(args.at(0)->asText(), mapping); - - return 0; -} - static Value *Function_App_Quit(Context &, Function::ArgumentValues const &) { Sys_Quit(); @@ -319,17 +264,12 @@ ClientApp::ClientApp(int &argc, char **argv) { novideo = false; - // Override the system locale (affects number/time formatting). - QLocale::setDefault(QLocale("en_US.UTF-8")); - // Use the host system's proxy configuration. QNetworkProxyFactory::setUseSystemConfiguration(true); // Metadata. - setOrganizationDomain ("dengine.net"); - setOrganizationName ("Deng Team"); - setApplicationName ("Doomsday Engine"); - setApplicationVersion (DOOMSDAY_VERSION_BASE); + setMetadata("Deng Team", "dengine.net", "Doomsday Engine", DOOMSDAY_VERSION_BASE); + setUnixHomeFolderName(".doomsday"); setTerminateFunc(handleLegacyCoreTerminate); @@ -337,10 +277,8 @@ ClientApp::ClientApp(int &argc, char **argv) setGame(d->games.nullGame()); d->binder.init(scriptSystem().nativeModule("App")) - << DENG2_FUNC_NOARG (App_GamePlugin, "gamePlugin") - << DENG2_FUNC (App_AddFontMapping, "addFontMapping", "family" << "mappings") - << DENG2_FUNC (App_LoadFont, "loadFont", "fileName") - << DENG2_FUNC_NOARG (App_Quit, "quit"); + << DENG2_FUNC_NOARG (App_GamePlugin, "gamePlugin") + << DENG2_FUNC_NOARG (App_Quit, "quit"); } void ClientApp::initialize() diff --git a/doomsday/client/src/dd_main.cpp b/doomsday/client/src/dd_main.cpp index 8ddb856a5d..3b2e73bc45 100644 --- a/doomsday/client/src/dd_main.cpp +++ b/doomsday/client/src/dd_main.cpp @@ -1372,7 +1372,7 @@ bool App_ChangeGame(Game &game, bool allowReload) } // The current game will be gone very soon. - DENG2_FOR_EACH_OBSERVER(App::GameUnloadAudience, i, App::app().audienceForGameUnload) + DENG2_FOR_EACH_OBSERVER(App::GameUnloadAudience, i, App::app().audienceForGameUnload()) { i->aboutToUnloadGame(App::game()); } @@ -1628,7 +1628,7 @@ bool App_ChangeGame(Game &game, bool allowReload) #endif // Game change is complete. - DENG2_FOR_EACH_OBSERVER(App::GameChangeAudience, i, App::app().audienceForGameChange) + DENG2_FOR_EACH_OBSERVER(App::GameChangeAudience, i, App::app().audienceForGameChange()) { i->currentGameChanged(App::game()); } @@ -1742,7 +1742,7 @@ void DD_FinishInitializationAfterWindowReady() } /// @todo This notification should be done from the app. - DENG2_FOR_EACH_OBSERVER(App::StartupCompleteAudience, i, App::app().audienceForStartupComplete) + DENG2_FOR_EACH_OBSERVER(App::StartupCompleteAudience, i, App::app().audienceForStartupComplete()) { i->appStartupCompleted(); } diff --git a/doomsday/client/src/gl/gl_main.cpp b/doomsday/client/src/gl/gl_main.cpp index e7b5b795d1..73d07503df 100644 --- a/doomsday/client/src/gl/gl_main.cpp +++ b/doomsday/client/src/gl/gl_main.cpp @@ -556,43 +556,10 @@ void GL_Restore2DState(int step, viewport_t const *port, viewdata_t const *viewD Matrix4f GL_GetProjectionMatrix() { - // We're assuming pixels are squares. - float aspect = viewpw / (float) viewph; - - if (vrCfg().mode() == VRConfig::OculusRift) - { - aspect = vrCfg().oculusRift().aspect(); - // A little trigonometry to apply aspect ratio to angles - float x = tan(0.5 * de::degreeToRadian(Rend_FieldOfView())); - yfov = de::radianToDegree(2.0 * atan2(x/aspect, 1.0f)); - } - else - { - yfov = Rend_FieldOfView() / aspect; - } - - float fH = tan(0.5 * de::degreeToRadian(yfov)) * glNearClip; - float fW = fH*aspect; - /* - * Asymmetric frustum shift is computed to realign screen-depth items after view point has shifted. - * Asymmetric frustum shift method is probably superior to competing toe-in stereo 3D method: - * - AFS preserves identical near and far clipping planes in both views - * - AFS shows items at/near infinity better - * - AFS conforms to what stereo 3D photographers call "ortho stereo" - * Asymmetric frustum shift is used for all stereo 3D modes except Oculus Rift mode, which only - * applies the viewpoint shift. - */ - float frustumShift = 0; - if (vrCfg().frustumShift()) - { - frustumShift = vrCfg().eyeShift() * glNearClip / vrCfg().screenDistance(); - } - - return Matrix4f::frustum(-fW - frustumShift, fW - frustumShift, - -fH, fH, - glNearClip, glFarClip) * - Matrix4f::translate(Vector3f(-vrCfg().eyeShift(), 0, 0)) * - Matrix4f::scale(Vector3f(1, 1, -1)); + float const fov = Rend_FieldOfView(); + Vector2f const size(viewpw, viewph); + yfov = vrCfg().verticalFieldOfView(fov, size); + return vrCfg().projectionMatrix(Rend_FieldOfView(), size, glNearClip, glFarClip); } void GL_ProjectionMatrix() diff --git a/doomsday/client/src/network/serverlink.cpp b/doomsday/client/src/network/serverlink.cpp index 0b1bcb7aae..da8d85c274 100644 --- a/doomsday/client/src/network/serverlink.cpp +++ b/doomsday/client/src/network/serverlink.cpp @@ -66,7 +66,7 @@ DENG2_PIMPL(ServerLink) ~Instance() { - Loop::appLoop().audienceForIteration -= this; + Loop::appLoop().audienceForIteration() -= this; } void notifyDiscoveryUpdate() @@ -183,7 +183,7 @@ DENG2_PIMPL(ServerLink) fetching = true; N_MAPost(MAC_REQUEST); N_MAPost(MAC_WAIT); - Loop::appLoop().audienceForIteration += this; + Loop::appLoop().audienceForIteration() += this; } void loopIteration() @@ -193,7 +193,7 @@ DENG2_PIMPL(ServerLink) if(N_MADone()) { fetching = false; - Loop::appLoop().audienceForIteration -= this; + Loop::appLoop().audienceForIteration() -= this; fromMaster.clear(); int const count = N_MasterGet(0, 0); @@ -208,27 +208,38 @@ DENG2_PIMPL(ServerLink) } } - Servers allFound() const + Servers allFound(FoundMask const &mask) const { - Servers all = discovered; + Servers all; - // Append from master (if available). - DENG2_FOR_EACH_CONST(Servers, i, fromMaster) + if(mask.testFlag(Direct)) { - all.insert(i.key(), i.value()); + all = discovered; } - // Append the ones from the server finder. - foreach(Address const &sv, finder.foundServers()) + if(mask.testFlag(MasterServer)) { - serverinfo_t info; - ServerInfo_FromRecord(&info, finder.messageFromServer(sv)); + // Append from master (if available). + DENG2_FOR_EACH_CONST(Servers, i, fromMaster) + { + all.insert(i.key(), i.value()); + } + } + + if(mask.testFlag(LocalNetwork)) + { + // Append the ones from the server finder. + foreach(Address const &sv, finder.foundServers()) + { + serverinfo_t info; + ServerInfo_FromRecord(&info, finder.messageFromServer(sv)); - // Update the address in the info, which is missing because this - // information didn't come from the master. - strncpy(info.address, sv.host().toString().toLatin1(), sizeof(info.address) - 1); + // Update the address in the info, which is missing because this + // information didn't come from the master. + strncpy(info.address, sv.host().toString().toLatin1(), sizeof(info.address) - 1); - all.insert(sv, info); + all.insert(sv, info); + } } return all; @@ -330,33 +341,33 @@ bool ServerLink::isDiscovering() const d->fetching); } -int ServerLink::foundServerCount() const +int ServerLink::foundServerCount(FoundMask mask) const { - return d->allFound().size(); + return d->allFound(mask).size(); } -QList
ServerLink::foundServers() const +QList
ServerLink::foundServers(FoundMask mask) const { - return d->allFound().keys(); + return d->allFound(mask).keys(); } -bool ServerLink::isFound(Address const &host) const +bool ServerLink::isFound(Address const &host, FoundMask mask) const { - return d->allFound().contains(host); + return d->allFound(mask).contains(host); } -bool ServerLink::foundServerInfo(int index, serverinfo_t *info) const +bool ServerLink::foundServerInfo(int index, serverinfo_t *info, FoundMask mask) const { - Instance::Servers all = d->allFound(); + Instance::Servers all = d->allFound(mask); QList
listed = all.keys(); if(index < 0 || index >= listed.size()) return false; memcpy(info, &all[listed[index]], sizeof(*info)); return true; } -bool ServerLink::foundServerInfo(de::Address const &host, serverinfo_t *info) const +bool ServerLink::foundServerInfo(de::Address const &host, serverinfo_t *info, FoundMask mask) const { - Instance::Servers all = d->allFound(); + Instance::Servers all = d->allFound(mask); if(!all.contains(host)) return false; memcpy(info, &all[host], sizeof(*info)); return true; diff --git a/doomsday/client/src/render/rend_main.cpp b/doomsday/client/src/render/rend_main.cpp index cd7e2fd7e9..ef0a701557 100644 --- a/doomsday/client/src/render/rend_main.cpp +++ b/doomsday/client/src/render/rend_main.cpp @@ -512,103 +512,31 @@ Matrix4f Rend_GetModelViewMatrix(int consoleNum, bool useAngles) if(useAngles) { - //bool scheduledLate = false; - float yaw = vang; float pitch = vpitch; float roll = 0; - /* - static float storedPitch, storedRoll, storedYaw; - */ + /// @todo Elevate roll angle use into viewer_t, and maybe all the way up into player + /// model. /* - if(VR::viewPositionHeld()) + * Pitch and yaw can be taken directly from the head tracker, as the game is aware of + * these values and is syncing with them independently (however, game has more + * latency). + */ + if((vrCfg().mode() == VRConfig::OculusRift) && vrCfg().oculusRift().isReady()) { - roll = storedRoll; - pitch = storedPitch; - yaw = storedYaw; - } - else*/ + Vector3f const pry = vrCfg().oculusRift().headOrientation(); - { - /// @todo Elevate roll angle use into viewer_t, and maybe all the way up into player - /// model. + // Use angles directly from the Rift for best response. + roll = -radianToDegree(pry[1]); + pitch = radianToDegree(pry[0]); - /* - * Pitch and yaw can be taken directly from the head tracker, as the game is aware of - * these values and is syncing with them independently (however, game has more - * latency). - */ - if((vrCfg().mode() == VRConfig::OculusRift) && vrCfg().oculusRift().isReady()) - { - Vector3f const pry = vrCfg().oculusRift().headOrientation(); - - // Use angles directly from the Rift for best response. - roll = -radianToDegree(pry[1]); - pitch = radianToDegree(pry[0]); - -#if 0 - - static float previousRiftYaw = 0; - static float previousVang2 = 0; - - // Desired view angle without interpolation? - float vang2 = viewData->latest.angle / (float) ANGLE_MAX *360 - 90; - - // Late-scheduled update - scheduledLate = true; - roll = -radianToDegree(pry[1]); - pitch = radianToDegree(pry[0]); - - // Yaw might have a contribution from mouse/keyboard. - // So only late schedule if it seems to be head tracking only. - yaw = vang2; // default no late schedule - float currentRiftYaw = radianToDegree(pry[2]); - float dRiftYaw = currentRiftYaw - previousRiftYaw; - while (dRiftYaw > 180) dRiftYaw -= 360; - while (dRiftYaw < -180) dRiftYaw += 360; - float dVang = vang2 - previousVang2; - while (dVang > 180) dVang -= 360; - while (dVang < -180) dVang += 360; - if (abs(dVang) < 2.0 * abs(dRiftYaw)) // Mostly head motion - { - yaw = storedYaw + dRiftYaw; - float dy = vang2 - yaw; - while (dy > 180) dy -= 360; - while (dy < -180) dy += 360; - yaw += 0.05 * dy; // ease slowly toward target angle - } - else - { - yaw = vang2; // No interpolation if not from head tracker - } - - previousRiftYaw = currentRiftYaw; - previousVang2 = vang2; -#endif - } - - /* - if(!scheduledLate) - { - // Vanilla angle update - roll = 0; - pitch = vpitch; - yaw = vang; - }*/ } modelView = Matrix4f::rotate(roll, Vector3f(0, 0, 1)) * Matrix4f::rotate(pitch, Vector3f(1, 0, 0)) * Matrix4f::rotate(yaw, Vector3f(0, 1, 0)); - - /* - // Keep these for possible use in later frames. - storedRoll = roll; - storedPitch = pitch; - storedYaw = yaw; - */ } return (modelView * diff --git a/doomsday/client/src/settingsregister.cpp b/doomsday/client/src/settingsregister.cpp index 3d1751479f..92deb8ffe9 100644 --- a/doomsday/client/src/settingsregister.cpp +++ b/doomsday/client/src/settingsregister.cpp @@ -99,16 +99,16 @@ DENG2_OBSERVES(App, GameChange) Instance(Public *i) : Base(i), current(CUSTOM_PROFILE) { - App::app().audienceForGameUnload += this; - App::app().audienceForGameChange += this; + App::app().audienceForGameUnload() += this; + App::app().audienceForGameChange() += this; addProfile(current); } ~Instance() { - App::app().audienceForGameUnload -= this; - App::app().audienceForGameChange -= this; + App::app().audienceForGameUnload() -= this; + App::app().audienceForGameChange() -= this; clearProfiles(); } diff --git a/doomsday/client/src/ui/clientwindow.cpp b/doomsday/client/src/ui/clientwindow.cpp index 4bbe40e87c..3ce760b67e 100644 --- a/doomsday/client/src/ui/clientwindow.cpp +++ b/doomsday/client/src/ui/clientwindow.cpp @@ -126,20 +126,20 @@ DENG2_PIMPL(ClientWindow) /// @todo The decision whether to receive input notifications from the /// canvas is really a concern for the input drivers. - App::app().audienceForGameChange += this; - App::app().audienceForStartupComplete += this; + App::app().audienceForGameChange() += this; + App::app().audienceForStartupComplete() += this; // Listen to input. - self.canvas().audienceForMouseStateChange += this; + self.canvas().audienceForMouseStateChange() += this; } ~Instance() { - App::app().audienceForGameChange -= this; - App::app().audienceForStartupComplete -= this; + App::app().audienceForGameChange() -= this; + App::app().audienceForStartupComplete() -= this; - self.canvas().audienceForFocusChange -= this; - self.canvas().audienceForMouseStateChange -= this; + self.canvas().audienceForFocusChange() -= this; + self.canvas().audienceForMouseStateChange() -= this; releaseRef(cursorX); releaseRef(cursorY); @@ -341,7 +341,7 @@ DENG2_PIMPL(ClientWindow) } */ - self.canvas().audienceForFocusChange += this; + self.canvas().audienceForFocusChange() += this; #ifdef WIN32 if(self.isFullScreen()) @@ -651,8 +651,8 @@ ClientWindow::ClientWindow(String const &id) : BaseWindow(id) , d(new Instance(this)) { - canvas().audienceForGLResize += this; - canvas().audienceForGLInit += this; + canvas().audienceForGLResize() += this; + canvas().audienceForGLInit() += this; #ifdef WIN32 // Set an icon for the window. diff --git a/doomsday/client/src/ui/dialogs/alertdialog.cpp b/doomsday/client/src/ui/dialogs/alertdialog.cpp index 17914fda44..cb0ed9fa93 100644 --- a/doomsday/client/src/ui/dialogs/alertdialog.cpp +++ b/doomsday/client/src/ui/dialogs/alertdialog.cpp @@ -106,8 +106,8 @@ DENG_GUI_PIMPL(AlertDialog) area.enableIndicatorDraw(true); - alerts->organizer().audienceForWidgetCreation += this; - alerts->organizer().audienceForWidgetUpdate += this; + alerts->organizer().audienceForWidgetCreation() += this; + alerts->organizer().audienceForWidgetUpdate() += this; } NotificationWidget ¬ifs() diff --git a/doomsday/client/src/ui/dialogs/logsettingsdialog.cpp b/doomsday/client/src/ui/dialogs/logsettingsdialog.cpp index 1ff2dbf3d5..7454c9a2d1 100644 --- a/doomsday/client/src/ui/dialogs/logsettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/logsettingsdialog.cpp @@ -100,7 +100,7 @@ DENG2_PIMPL(LogSettingsDialog) // This'll keep the dialog's size fixed even though the choices change size. columnWidth->setSource(domWidgets[0].level->maximumWidth()); - separately->audienceForToggle += this; + separately->audienceForToggle() += this; } catch(Error const &er) { diff --git a/doomsday/client/src/ui/dialogs/manualconnectiondialog.cpp b/doomsday/client/src/ui/dialogs/manualconnectiondialog.cpp index 155b05e21b..3873c85aea 100644 --- a/doomsday/client/src/ui/dialogs/manualconnectiondialog.cpp +++ b/doomsday/client/src/ui/dialogs/manualconnectiondialog.cpp @@ -17,28 +17,124 @@ */ #include "ui/dialogs/manualconnectiondialog.h" +#include "ui/widgets/mpselectionwidget.h" +#include "clientapp.h" + +#include +#include #include using namespace de; -DENG2_PIMPL_NOREF(ManualConnectionDialog) +DENG2_PIMPL(ManualConnectionDialog) +, DENG2_OBSERVES(ServerLink, DiscoveryUpdate) +, DENG2_OBSERVES(MPSelectionWidget, Selection) { String usedAddress; + FoldPanelWidget *fold; + MPSelectionWidget *games; + ProgressWidget *progress; + bool querying; + bool joinWhenEnterPressed; + + Instance(Public *i) + : Base(i) + , querying(false) + , joinWhenEnterPressed(false) + { + ClientApp::serverLink().audienceForDiscoveryUpdate += this; + } + + ~Instance() + { + ClientApp::serverLink().audienceForDiscoveryUpdate -= this; + } + + void linkDiscoveryUpdate(ServerLink const &link) + { + if(querying) + { + // Time to show what we found. + querying = false; + progress->setRotationSpeed(0); + self.editor().enable(); + self.validate(); + + if(link.foundServerCount(ServerLink::Direct) > 0) + { + progress->hide(); + fold->open(); + + if(link.foundServerCount(ServerLink::Direct) == 1) + { + joinWhenEnterPressed = true; + } + } + else + { + fold->close(0); + progress->setRotationSpeed(0); + progress->setText(_E(l) + tr("No response")); + progress->setOpacity(0, 4, 2); + } + } + } + + void gameSelected(serverinfo_t const &info) + { + self.setAcceptanceAction(new MPSelectionWidget::JoinAction(info)); + self.accept(); + } + + ButtonWidget &connectButton() + { + return self.buttonWidget(tr("Connect")); + } }; ManualConnectionDialog::ManualConnectionDialog(String const &name) - : InputDialog(name), d(new Instance) + : InputDialog(name), d(new Instance(this)) { + add(d->progress = new ProgressWidget); + d->progress->useMiniStyle("altaccent"); + d->progress->setWidthPolicy(ui::Expand); + d->progress->setTextAlignment(ui::AlignLeft); + d->progress->hide(); + + // The found games are shown inside a fold panel. + d->fold = new FoldPanelWidget; + d->games = new MPSelectionWidget(MPSelectionWidget::DirectDiscoveryOnly); + d->games->setJoinGameWhenSelected(false); + d->games->audienceForSelection += d; + d->games->setColumns(1); + d->games->rule().setInput(Rule::Width, rule().width() - margins().width()); + d->fold->setContent(d->games); + area().add(d->fold); + title().setText(tr("Connect to Server")); - message().setText(tr("Enter the address of the multiplayer server you want to connect to. " - "The address can be a domain name or an IP address. " + message().setText(tr("Enter the IP address or domain name of the multiplayer server you want to connect to. " "Optionally, you may include a TCP port number, for example " _E(b) "10.0.1.1:13209" _E(.) ".")); - defaultActionItem()->setLabel(tr("Connect")); - buttonWidget(tr("Connect")).disable(); + buttons().clear() + << new DialogButtonItem(Default, tr("Connect"), + new SignalAction(this, SLOT(queryOrConnect()))) + << new DialogButtonItem(Reject); + + d->connectButton().disable(); + + d->progress->rule() + .setInput(Rule::Top, buttonsMenu().rule().top()) + .setInput(Rule::Right, buttonsMenu().rule().left()) + .setInput(Rule::Height, buttonsMenu().rule().height() - margins().bottom()); + disconnect(&editor(), SIGNAL(enterPressed(QString)), this, SLOT(accept())); + connect(&editor(), SIGNAL(enterPressed(QString)), this, SLOT(queryOrConnect())); connect(&editor(), SIGNAL(editorContentChanged()), this, SLOT(validate())); + connect(&editor(), SIGNAL(editorContentChanged()), this, SLOT(contentChanged())); + connect(&ClientApp::serverLink(), SIGNAL(disconnected()), this, SLOT(disconnected())); + + updateLayout(); } void ManualConnectionDialog::operator >> (PersistentState &toState) const @@ -53,17 +149,62 @@ void ManualConnectionDialog::operator << (PersistentState const &fromState) validate(); } +void ManualConnectionDialog::queryOrConnect() +{ + if(d->connectButton().isDisabled()) + { + // Should not try now. + return; + } + + if(!d->querying) + { + // Automatically connect if there is a single choice. + if(d->joinWhenEnterPressed) + { + d->gameSelected(d->games->serverInfo(0)); + return; + } + + d->joinWhenEnterPressed = false; + d->querying = true; + d->progress->setText(_E(l) + tr("Looking for host...")); + d->progress->setRotationSpeed(40); + d->progress->show(); + d->progress->setOpacity(1); + editor().disable(); + validate(); + + ClientApp::serverLink().discover(editor().text()); + } +} + +void ManualConnectionDialog::contentChanged() +{ + d->joinWhenEnterPressed = false; +} + void ManualConnectionDialog::validate() { bool valid = true; + if(d->querying) + { + valid = false; + } + if(editor().text().isEmpty() || editor().text().contains(';') || editor().text().endsWith(":") || editor().text().startsWith(":")) { valid = false; } - buttonWidget(tr("Connect")).enable(valid); + d->connectButton().enable(valid); +} + +void ManualConnectionDialog::disconnected() +{ + d->linkDiscoveryUpdate(ClientApp::serverLink()); } void ManualConnectionDialog::finish(int result) @@ -76,4 +217,3 @@ void ManualConnectionDialog::finish(int result) InputDialog::finish(result); } - diff --git a/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp b/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp index da370bef0c..d3ece3ddfe 100644 --- a/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/videosettingsdialog.cpp @@ -70,12 +70,12 @@ DENG2_OBSERVES(PersistentCanvasWindow, AttributeChange) #ifdef USE_COLOR_DEPTH_CHOICE area.add(depths = new ChoiceWidget); #endif - win.audienceForAttributeChange += this; + win.audienceForAttributeChange() += this; } ~Instance() { - win.audienceForAttributeChange -= this; + win.audienceForAttributeChange() -= this; } /** diff --git a/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp b/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp index cc8c8ff398..39ce5ebd27 100644 --- a/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp +++ b/doomsday/client/src/ui/dialogs/vrsettingsdialog.cpp @@ -106,8 +106,6 @@ VRSettingsDialog::VRSettingsDialog(String const &name) LabelWidget *heightLabel = LabelWidget::newWithText(tr("Height (m):"), &area()); LabelWidget *ipdLabel = LabelWidget::newWithText(tr("IPD (mm):"), &area()); LabelWidget *dominantLabel = LabelWidget::newWithText(tr("Dominant Eye:"), &area()); - LabelWidget *sampleLabel = LabelWidget::newWithText(tr("Oculus Rift\nMultisampling:"), &area()); - sampleLabel->setTextLineAlignment(ui::AlignRight); // Layout. GridLayout layout(area().contentRule().left(), area().contentRule().top()); @@ -118,20 +116,21 @@ VRSettingsDialog::VRSettingsDialog(String const &name) << *heightLabel << *d->humanHeight << *ipdLabel << *d->ipd << *dominantLabel << *d->dominantEye - << Const(0) << *d->swapEyes - << *sampleLabel << *d->riftSamples; + << Const(0) << *d->swapEyes; + + LabelWidget *ovrLabel = LabelWidget::newWithText(_E(1)_E(D) + tr("Oculus Rift"), &area()); + LabelWidget *sampleLabel = LabelWidget::newWithText(tr("Multisampling:"), &area()); + ovrLabel->margins().setTop("gap"); + sampleLabel->setTextLineAlignment(ui::AlignRight); + + layout.setCellAlignment(Vector2i(0, 5), ui::AlignLeft); + layout.append(*ovrLabel, 2) << *sampleLabel << *d->riftSamples; if(vrCfg().oculusRift().isReady()) { - LabelWidget *ovrLabel = LabelWidget::newWithText(_E(1)_E(D) + tr("Oculus Rift"), &area()); LabelWidget *latencyLabel = LabelWidget::newWithText(tr("Prediction Latency:"), &area()); LabelWidget *utilLabel = LabelWidget::newWithText(tr("Utilities:"), &area()); - ovrLabel->margins().setTop("gap"); - - layout.setCellAlignment(Vector2i(0, 6), ui::AlignLeft); - - layout.append(*ovrLabel, 2); layout << *latencyLabel << *d->riftPredictionLatency << *utilLabel << *d->riftSetup << Const(0) << *d->desktopSetup; diff --git a/doomsday/client/src/ui/editors/rendererappearanceeditor.cpp b/doomsday/client/src/ui/editors/rendererappearanceeditor.cpp index 7109c95d4b..3a569db6e8 100644 --- a/doomsday/client/src/ui/editors/rendererappearanceeditor.cpp +++ b/doomsday/client/src/ui/editors/rendererappearanceeditor.cpp @@ -265,7 +265,7 @@ DENG2_OBSERVES(App, GameChange) firstColumnWidth(new IndirectRule) { // The editor will close automatically when going to Ring Zero. - App::app().audienceForGameChange += this; + App::app().audienceForGameChange() += this; settings.audienceForProfileChange += this; @@ -570,7 +570,7 @@ DENG2_OBSERVES(App, GameChange) ~Instance() { - App::app().audienceForGameChange -= this; + App::app().audienceForGameChange() -= this; settings.audienceForProfileChange -= this; releaseRef(firstColumnWidth); } diff --git a/doomsday/client/src/ui/widgets/consolecommandwidget.cpp b/doomsday/client/src/ui/widgets/consolecommandwidget.cpp index 69ce394ee5..2d30bda1f2 100644 --- a/doomsday/client/src/ui/widgets/consolecommandwidget.cpp +++ b/doomsday/client/src/ui/widgets/consolecommandwidget.cpp @@ -32,14 +32,14 @@ DENG2_OBSERVES(App, GameChange) { Instance(Public *i) : Base(i) { - App::app().audienceForStartupComplete += this; - App::app().audienceForGameChange += this; + App::app().audienceForStartupComplete() += this; + App::app().audienceForGameChange() += this; } ~Instance() { - App::app().audienceForStartupComplete -= this; - App::app().audienceForGameChange -= this; + App::app().audienceForStartupComplete() -= this; + App::app().audienceForGameChange() -= this; } void appStartupCompleted() diff --git a/doomsday/client/src/ui/widgets/consolewidget.cpp b/doomsday/client/src/ui/widgets/consolewidget.cpp index b3227acb57..12673fa55e 100644 --- a/doomsday/client/src/ui/widgets/consolewidget.cpp +++ b/doomsday/client/src/ui/widgets/consolewidget.cpp @@ -88,12 +88,12 @@ DENG2_OBSERVES(Variable, Change) grabWidth = style().rules().rule("gap").valuei(); - App::config()["console.script"].audienceForChange += this; + App::config()["console.script"].audienceForChange() += this; } ~Instance() { - App::config()["console.script"].audienceForChange -= this; + App::config()["console.script"].audienceForChange() -= this; releaseRef(horizShift); releaseRef(width); diff --git a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp index b88df2bbbb..7f91583ab8 100644 --- a/doomsday/client/src/ui/widgets/gameselectionwidget.cpp +++ b/doomsday/client/src/ui/widgets/gameselectionwidget.cpp @@ -109,13 +109,13 @@ DENG_GUI_PIMPL(GameSelectionWidget) break; case MultiplayerGames: - menu = new MPSelectionWidget; + menu = new MPSelectionWidget(MPSelectionWidget::DiscoverUsingMaster); QObject::connect(menu, SIGNAL(gameSelected()), owner->thisPublic, SIGNAL(gameSessionSelected())); QObject::connect(menu, SIGNAL(availabilityChanged()), owner->thisPublic, SLOT(updateSubsetLayout())); break; } - menu->items().audienceForAddition += this; + menu->items().audienceForAddition() += this; setContent(menu); menu->enableScrolling(false); @@ -239,15 +239,15 @@ DENG_GUI_PIMPL(GameSelectionWidget) updateSubsetLayout(); App_Games().audienceForAddition += this; - App::app().audienceForStartupComplete += this; - App::app().audienceForGameChange += this; + App::app().audienceForStartupComplete() += this; + App::app().audienceForGameChange() += this; } ~Instance() { App_Games().audienceForAddition -= this; - App::app().audienceForStartupComplete -= this; - App::app().audienceForGameChange -= this; + App::app().audienceForStartupComplete() -= this; + App::app().audienceForGameChange() -= this; } void updateSubsetVisibility() diff --git a/doomsday/client/src/ui/widgets/gamesessionwidget.cpp b/doomsday/client/src/ui/widgets/gamesessionwidget.cpp index 5e74ed84f7..c15ccff701 100644 --- a/doomsday/client/src/ui/widgets/gamesessionwidget.cpp +++ b/doomsday/client/src/ui/widgets/gamesessionwidget.cpp @@ -49,14 +49,14 @@ DENG2_PIMPL(GameSessionWidget) self.add(popup = new DocumentPopupWidget); popup->setAnchorAndOpeningDirection(info->rule(), ui::Up); popup->document().setMaximumLineWidth(popup->style().rules().rule("document.popup.width").valuei()); - info->audienceForPress += this; + info->audienceForPress() += this; - App::app().audienceForGameUnload += this; + App::app().audienceForGameUnload() += this; } ~Instance() { - App::app().audienceForGameUnload -= this; + App::app().audienceForGameUnload() -= this; } void aboutToUnloadGame(game::Game const &) diff --git a/doomsday/client/src/ui/widgets/mpselectionwidget.cpp b/doomsday/client/src/ui/widgets/mpselectionwidget.cpp index 8bc0c93010..8c1f6063e6 100644 --- a/doomsday/client/src/ui/widgets/mpselectionwidget.cpp +++ b/doomsday/client/src/ui/widgets/mpselectionwidget.cpp @@ -75,33 +75,6 @@ DENG_GUI_PIMPL(MPSelectionWidget) */ struct ServerWidget : public GameSessionWidget { - struct JoinAction : public Action - { - public: - JoinAction(serverinfo_t const &sv, ButtonWidget &owner) - : _owner(&owner) - { - _gameId = sv.gameIdentityKey; - _cmd = String("connect %1 %2").arg(sv.address).arg(sv.port); - } - - void trigger() - { - Action::trigger(); - - BusyMode_FreezeGameForBusyMode(); - ClientWindow::main().taskBar().close(); - - App_ChangeGame(App_Games().byIdentityKey(_gameId), false /*no reload*/); - Con_Execute(CMDS_DDAY, _cmd.toLatin1(), false, false); - } - - private: - ButtonWidget *_owner; - String _gameId; - String _cmd; - }; - ServerWidget() { loadButton().disable(); @@ -124,10 +97,10 @@ DENG_GUI_PIMPL(MPSelectionWidget) loadButton().enable(sv.canJoin); if(sv.canJoin) { - loadButton().setAction(new JoinAction(sv, loadButton())); + loadButton().setAction(new JoinAction(sv)); } - loadButton().setText(String(_E(1) "%1 " _E(.)_E(2) "(%5/%6)" _E(.) "\n%2" + loadButton().setText(String(_E(1) "%1 " _E(.)_E(2) "(%5/%6)" _E(.) " "DENG2_CHAR_MDASH" %2" _E(D)_E(l) "\n%7 %4") .arg(sv.name) .arg(svGame.title()) @@ -146,7 +119,13 @@ DENG_GUI_PIMPL(MPSelectionWidget) } }; - Instance(Public *i) : Base(i) + ServerLink::FoundMask mask; + bool joinWhenSelected; + + Instance(Public *i) + : Base(i) + , mask(ServerLink::Any) + , joinWhenSelected(true) { self.organizer().setWidgetFactory(*this); link().audienceForDiscoveryUpdate += this; @@ -160,7 +139,7 @@ DENG_GUI_PIMPL(MPSelectionWidget) GuiWidget *makeItemWidget(ui::Item const &item, GuiWidget const *) { ServerWidget *w = new ServerWidget; - w->loadButton().audienceForPress += this; + w->loadButton().audienceForPress() += this; w->rule().setInput(Rule::Height, w->loadButton().rule().height()); // Automatically close the info popup if the dialog is closed. @@ -171,11 +150,27 @@ DENG_GUI_PIMPL(MPSelectionWidget) void updateItemWidget(GuiWidget &widget, ui::Item const &item) { - widget.as().updateFromItem(item.as()); + ServerWidget &sv = widget.as(); + sv.updateFromItem(item.as()); + + if(!joinWhenSelected) + { + // Only send notification. + sv.loadButton().setAction(0); + } } - void buttonPressed(ButtonWidget &) + void buttonPressed(ButtonWidget &loadButton) { + if(ServerListItem const *it = self.organizer().findItemForWidget( + loadButton.parentWidget()->as())->maybeAs()) + { + DENG2_FOR_PUBLIC_AUDIENCE(Selection, i) + { + i->gameSelected(it->info()); + } + } + // A load button has been pressed. emit self.gameSelected(); } @@ -188,7 +183,7 @@ DENG_GUI_PIMPL(MPSelectionWidget) for(ui::Data::Pos idx = 0; idx < self.items().size(); ++idx) { String const id = self.items().at(idx).data().toString(); - if(!link.isFound(Address::parse(id))) + if(!link.isFound(Address::parse(id), mask)) { self.items().remove(idx--); changed = true; @@ -196,10 +191,10 @@ DENG_GUI_PIMPL(MPSelectionWidget) } // Add new entries and update existing ones. - foreach(de::Address const &host, link.foundServers()) + foreach(de::Address const &host, link.foundServers(mask)) { serverinfo_t info; - if(!link.foundServerInfo(host, &info)) continue; + if(!link.foundServerInfo(host, &info, mask)) continue; ui::Data::Pos found = self.items().findData(hostId(info)); if(found == ui::Data::InvalidPos) @@ -224,12 +219,30 @@ DENG_GUI_PIMPL(MPSelectionWidget) } }; -MPSelectionWidget::MPSelectionWidget() +MPSelectionWidget::MPSelectionWidget(DiscoveryMode discovery) : MenuWidget("mp-selection"), d(new Instance(this)) { setGridSize(3, ui::Filled, 0, ui::Expand); - d->link().discoverUsingMaster(); + switch(discovery) + { + case DiscoverUsingMaster: + d->link().discoverUsingMaster(); + break; + + case DirectDiscoveryOnly: + // Only show servers found via direct connection. + d->mask = ServerLink::Direct; + break; + + default: + break; + } +} + +void MPSelectionWidget::setJoinGameWhenSelected(bool enableJoin) +{ + d->joinWhenSelected = enableJoin; } void MPSelectionWidget::setColumns(int numberOfColumns) @@ -239,15 +252,33 @@ void MPSelectionWidget::setColumns(int numberOfColumns) setGridSize(numberOfColumns, ui::Filled, 0, ui::Expand); } } -/* -void MPSelectionWidget::update() + +serverinfo_t const &MPSelectionWidget::serverInfo(ui::DataPos pos) const { - MenuWidget::update(); + DENG2_ASSERT(pos < items().size()); + return items().at(pos).as().info(); +} - Rectanglei rect; - if(hasChangedPlace(rect)) - { - d->updateLayoutForWidth(rect.width()); - } +DENG2_PIMPL_NOREF(MPSelectionWidget::JoinAction) +{ + String gameId; + String cmd; +}; + +MPSelectionWidget::JoinAction::JoinAction(serverinfo_t const &sv) + : d(new Instance) +{ + d->gameId = sv.gameIdentityKey; + d->cmd = String("connect %1 %2").arg(sv.address).arg(sv.port); +} + +void MPSelectionWidget::JoinAction::trigger() +{ + Action::trigger(); + + BusyMode_FreezeGameForBusyMode(); + ClientWindow::main().taskBar().close(); + + App_ChangeGame(App_Games().byIdentityKey(d->gameId), false /*no reload*/); + Con_Execute(CMDS_DDAY, d->cmd.toLatin1(), false, false); } -*/ diff --git a/doomsday/client/src/ui/widgets/profilepickerwidget.cpp b/doomsday/client/src/ui/widgets/profilepickerwidget.cpp index d7fb8cf182..4200a4193d 100644 --- a/doomsday/client/src/ui/widgets/profilepickerwidget.cpp +++ b/doomsday/client/src/ui/widgets/profilepickerwidget.cpp @@ -51,7 +51,7 @@ DENG_GUI_PIMPL(ProfilePickerWidget) ~Instance() { - if(menu) menu->audienceForClose -= this; + if(menu) menu->audienceForClose() -= this; } void updateStyle() @@ -161,7 +161,7 @@ void ProfilePickerWidget::openMenu() d->menu->setDeleteAfterDismissed(true); d->menu->setAnchorAndOpeningDirection(d->button->rule(), ui::Down); - d->menu->audienceForClose += d; + d->menu->audienceForClose() += d; d->menu->open(); } diff --git a/doomsday/client/src/ui/widgets/taskbarwidget.cpp b/doomsday/client/src/ui/widgets/taskbarwidget.cpp index ea0307018a..9ad66dbe8f 100644 --- a/doomsday/client/src/ui/widgets/taskbarwidget.cpp +++ b/doomsday/client/src/ui/widgets/taskbarwidget.cpp @@ -130,7 +130,7 @@ DENG_GUI_PIMPL(TaskBarWidget) vertShift = new ScalarRule(0); - App::app().audienceForGameChange += this; + App::app().audienceForGameChange() += this; ClientApp::serverLink().audienceForJoin += this; ClientApp::serverLink().audienceForLeave += this; @@ -139,7 +139,7 @@ DENG_GUI_PIMPL(TaskBarWidget) ~Instance() { - App::app().audienceForGameChange -= this; + App::app().audienceForGameChange() -= this; ClientApp::serverLink().audienceForJoin -= this; ClientApp::serverLink().audienceForLeave -= this; @@ -265,8 +265,8 @@ DENG_GUI_PIMPL(TaskBarWidget) itemWidget(mainMenu, POS_GAMES) .show(!newGame.isNull()); itemWidget(mainMenu, POS_UNLOAD) .show(!newGame.isNull()); itemWidget(mainMenu, POS_GAMES_SEPARATOR) .show(!newGame.isNull()); - itemWidget(mainMenu, POS_CONNECT) .show(!newGame.isNull()); - itemWidget(mainMenu, POS_CONNECT_SEPARATOR).show(!newGame.isNull()); + //itemWidget(mainMenu, POS_CONNECT) .show(!newGame.isNull()); + //itemWidget(mainMenu, POS_CONNECT_SEPARATOR).show(!newGame.isNull()); itemWidget(configMenu, POS_RENDERER_SETTINGS).show(!newGame.isNull()); itemWidget(configMenu, POS_VR_SETTINGS) .show(!newGame.isNull()); @@ -425,8 +425,8 @@ TaskBarWidget::TaskBarWidget() : GuiWidget("taskbar"), d(new Instance(this)) d->itemWidget(d->mainMenu, POS_GAMES).hide(); d->itemWidget(d->mainMenu, POS_UNLOAD).hide(); d->itemWidget(d->mainMenu, POS_GAMES_SEPARATOR).hide(); - d->itemWidget(d->mainMenu, POS_CONNECT).hide(); - d->itemWidget(d->mainMenu, POS_CONNECT_SEPARATOR).hide(); + //d->itemWidget(d->mainMenu, POS_CONNECT).hide(); + //d->itemWidget(d->mainMenu, POS_CONNECT_SEPARATOR).hide(); d->itemWidget(d->configMenu, POS_RENDERER_SETTINGS).hide(); d->itemWidget(d->configMenu, POS_VR_SETTINGS).hide(); @@ -720,11 +720,7 @@ void TaskBarWidget::connectToServerManually() { ManualConnectionDialog *dlg = new ManualConnectionDialog; dlg->setDeleteAfterDismissed(true); - if(dlg->exec(root())) - { - // Connect to the provided address. - Con_Executef(CMDS_DDAY, false, "connect %s", dlg->editor().text().toLatin1().constData()); - } + dlg->exec(root()); } void TaskBarWidget::updateCommandLineLayout() @@ -746,4 +742,7 @@ void TaskBarWidget::updateCommandLineLayout() cmdRule.setInput(Rule::Left, d->console->button().rule().right()) .setInput(Rule::Bottom, rule().bottom()) .setInput(Rule::Right, layout.widgets().last()->as().rule().left()); + + // Just use a plain background for this editor. + d->console->commandLine().set(Background(style().colors().colorf("background"))); } diff --git a/doomsday/client/src/unix/dd_uinit.cpp b/doomsday/client/src/unix/dd_uinit.cpp index 26131cbbc6..5dd1a381db 100644 --- a/doomsday/client/src/unix/dd_uinit.cpp +++ b/doomsday/client/src/unix/dd_uinit.cpp @@ -25,6 +25,7 @@ #include #include +#include #ifdef UNIX # include "library.h" @@ -63,7 +64,8 @@ static void determineGlobalPaths(application_t* app) { filename_t homePath; directory_t* temp; - dd_snprintf(homePath, FILENAME_T_MAXLEN, "%s/.doomsday/runtime/", getenv("HOME")); + dd_snprintf(homePath, FILENAME_T_MAXLEN, "%s/%s/runtime/", getenv("HOME"), + DENG2_APP->unixHomeFolderName().toLatin1().constData()); temp = Dir_New(homePath); Dir_mkpath(Dir_Path(temp)); app->usingHomeDir = Dir_SetCurrent(Dir_Path(temp)); diff --git a/doomsday/client/src/updater/updateavailabledialog.cpp b/doomsday/client/src/updater/updateavailabledialog.cpp index a2b42e3730..4166dffb01 100644 --- a/doomsday/client/src/updater/updateavailabledialog.cpp +++ b/doomsday/client/src/updater/updateavailabledialog.cpp @@ -90,24 +90,11 @@ DENG2_OBSERVES(ToggleWidget, Toggle) checking->rule().setRect(self.rule()); self.add(checking); - //settings = new ButtonWidget; - //self.add(settings); - - /// @todo The dialog buttons should support a opposite-aligned button. - /*settings->setSizePolicy(ui::Filled, ui::Filled); - settings->setImage(self.style().images().image("gear")); - settings->rule() - .setInput(Rule::Left, self.area().contentRule().left()) - .setInput(Rule::Bottom, self.buttons().contentRule().bottom()) - .setInput(Rule::Height, self.buttons().contentRule().height()) - .setInput(Rule::Width, settings->rule().height()); - settings->setAction()));*/ - autoCheck = new ToggleWidget; self.area().add(autoCheck); autoCheck->setAlignment(ui::AlignLeft); autoCheck->setText(tr("Check for updates automatically")); - autoCheck->audienceForToggle += this; + autoCheck->audienceForToggle() += this; // Include the toggle in the layout. self.updateLayout(); @@ -225,7 +212,10 @@ void UpdateAvailableDialog::editSettings() st->setAnchorAndOpeningDirection(buttonWidget(DialogWidget::Id1)->rule(), ui::Up); st->setDeleteAfterDismissed(true); if(st->exec(root())) - { + { + // The Gear button will soon be deleted, so we'll need to detach from it. + st->detachAnchor(); + d->autoCheck->setInactive(UpdaterSettings().onlyCheckManually()); d->showProgress(true, SHOW_ANIM_SPAN); emit checkAgain(); diff --git a/doomsday/client/src/updater/updater.cpp b/doomsday/client/src/updater/updater.cpp index 0e34942190..e57f8ad440 100644 --- a/doomsday/client/src/updater/updater.cpp +++ b/doomsday/client/src/updater/updater.cpp @@ -120,7 +120,6 @@ class UpdaterStatusWidget : public ProgressWidget useMiniStyle(); setColor("text"); setShadowColor(""); // no shadow, please - setRotationSpeed(0); setSizePolicy(ui::Expand, ui::Expand); // The notification has a hidden button that can be clicked. @@ -532,7 +531,7 @@ Updater::Updater() : d(new Instance(this)) connect(d->network, SIGNAL(finished(QNetworkReply *)), this, SLOT(gotReply(QNetworkReply *))); // Do a silent auto-update check when starting. - App::app().audienceForStartupComplete += d; + App::app().audienceForStartupComplete() += d; } void Updater::setupUI() diff --git a/doomsday/client/src/updater/updatersettingsdialog.cpp b/doomsday/client/src/updater/updatersettingsdialog.cpp index 2a0b84430d..376477c043 100644 --- a/doomsday/client/src/updater/updatersettingsdialog.cpp +++ b/doomsday/client/src/updater/updatersettingsdialog.cpp @@ -112,7 +112,7 @@ DENG2_OBSERVES(ToggleWidget, Toggle) fetch(); - autoCheck->audienceForToggle += this; + autoCheck->audienceForToggle() += this; // Place the widgets into a grid. GridLayout layout(area.contentRule().left(), area.contentRule().top()); diff --git a/doomsday/config.pri b/doomsday/config.pri index 245ec54798..3dfb07510a 100644 --- a/doomsday/config.pri +++ b/doomsday/config.pri @@ -74,6 +74,12 @@ CONFIG(debug, debug|release) { DEFINES += NDEBUG } +# SDK build. +deng_sdk { + DEFINES += DENG_SDK_BUILD + echo("SDK build.") +} + # Debugging options. deng_fakememoryzone: DEFINES += LIBDENG_FAKE_MEMORY_ZONE @@ -94,25 +100,33 @@ isStableRelease(): DEFINES += DENG_STABLE # Options defined by the user (may not exist). exists(config_user.pri): include(config_user.pri) +deng_sdk { + # SDK install location. + !isEmpty(SDK_PREFIX) { + DENG_SDK_DIR = $$SDK_PREFIX + DENG_SDK_HEADER_DIR = $$DENG_SDK_DIR/include/doomsday/de + DENG_SDK_LIB_DIR = $$DENG_SDK_DIR/lib + } + else:!isEmpty(PREFIX) { + DENG_SDK_DIR = $$PREFIX + DENG_SDK_HEADER_DIR = $$DENG_SDK_DIR/include/doomsday/de + DENG_SDK_LIB_DIR = $$DENG_SDK_DIR/lib + } + else { + DENG_SDK_DIR = $$OUT_PWD/.. + DENG_SDK_HEADER_DIR = $$DENG_SDK_DIR/include/de + DENG_SDK_LIB_DIR = $$DENG_SDK_DIR/lib + } + echo(SDK header directory: $$DENG_SDK_HEADER_DIR) + echo(SDK library directory: $$DENG_SDK_LIB_DIR) +} + win32: include(config_win32.pri) else:macx: include(config_macx.pri) else: include(config_unix.pri) # Apply deng_* Configuration ------------------------------------------------- -unix:deng_ccache { - # ccache can be used to speed up recompilation. - *-clang* { - QMAKE_CC = ccache $$QMAKE_CC -Qunused-arguments - QMAKE_CXX = ccache $$QMAKE_CXX -Qunused-arguments - QMAKE_CXXFLAGS_WARN_ON += -Wno-self-assign - } - *-gcc*|*-g++* { - QMAKE_CC = ccache $$QMAKE_CC - QMAKE_CXX = ccache $$QMAKE_CXX - } -} - deng_nofixedasm { DEFINES += DENG_NO_FIXED_ASM } @@ -125,3 +139,16 @@ deng_nosdlmixer|deng_nosdl { deng_nosdl { DEFINES += DENG_NO_SDL } + +unix:deng_ccache { + # ccache can be used to speed up recompilation. + *-clang* { + QMAKE_CC = ccache $$QMAKE_CC -Qunused-arguments + QMAKE_CXX = ccache $$QMAKE_CXX -Qunused-arguments + QMAKE_CXXFLAGS_WARN_ON += -Wno-self-assign + } + *-gcc*|*-g++* { + QMAKE_CC = ccache $$QMAKE_CC + QMAKE_CXX = ccache $$QMAKE_CXX + } +} diff --git a/doomsday/config_unix.pri b/doomsday/config_unix.pri index 3ba5eb2c1c..485c01a52d 100644 --- a/doomsday/config_unix.pri +++ b/doomsday/config_unix.pri @@ -30,16 +30,8 @@ DENG_BIN_DIR = $$PREFIX/bin # Library location. isEmpty(DENG_LIB_DIR) { - DENG_LIB_DIR = $$PREFIX/lib - - contains(QMAKE_HOST.arch, x86_64) { - exists($$PREFIX/lib64) { - DENG_LIB_DIR = $$PREFIX/lib64 - } - exists($$PREFIX/lib/x86_64-linux-gnu) { - DENG_LIB_DIR = $$PREFIX/lib/x86_64-linux-gnu - } - } + deng_sdk: DENG_LIB_DIR = $$findLibDir($$DENG_SDK_DIR) + else: DENG_LIB_DIR = $$findLibDir($$PREFIX) } # Target location for plugin libraries. @@ -47,9 +39,7 @@ DENG_PLUGIN_LIB_DIR = $$DENG_LIB_DIR/doomsday # When installing libraries to a non-standard location, instruct # the linker where to find them. -!contains(DENG_LIB_DIR, ^/usr/.*) { - QMAKE_LFLAGS += -Wl,-rpath,$$DENG_LIB_DIR -} +!contains(DENG_LIB_DIR, ^/usr/.*): QMAKE_LFLAGS += -Wl,-rpath,$$DENG_LIB_DIR DENG_BASE_DIR = $$PREFIX/share/doomsday DENG_DATA_DIR = $$DENG_BASE_DIR/data diff --git a/doomsday/doomsday_sdk.pri b/doomsday/doomsday_sdk.pri new file mode 100644 index 0000000000..a65575ca78 --- /dev/null +++ b/doomsday/doomsday_sdk.pri @@ -0,0 +1,163 @@ +# The Doomsday Engine Project +# Copyright (c) 2014 Jaakko Keränen +# +# qmake .pri file for projects using the Doomsday SDK. +# +# The Doomsday SDK is distributed under the GNU Lesser General Public +# License version 3 (or, at your option, any later version). Please +# visit http://www.gnu.org/licenses/lgpl.html for details. +# +# Variables: +# - DENG_CONFIG Names of supporting libraries to use (gui, appfw, shell) + +DENG_SDK_DIR = $$PWD + +isEmpty(PREFIX): PREFIX = $$OUT_PWD + +exists($$DENG_SDK_DIR/include/doomsday) { + INCLUDEPATH += $$DENG_SDK_DIR/include/doomsday +} +else { + INCLUDEPATH += $$DENG_SDK_DIR/include +} + +win32: LIBS += -L$$DENG_SDK_DIR/lib + else: QMAKE_LFLAGS = -L$$DENG_SDK_DIR/lib $$QMAKE_LFLAGS + +# The core library is always required. +LIBS += -ldeng2 + +contains(DENG_CONFIG, appfw): DENG_CONFIG *= gui shell + +# Supporting libraries are optional. +contains(DENG_CONFIG, gui): LIBS += -ldeng_gui +contains(DENG_CONFIG, appfw): LIBS += -ldeng_appfw +contains(DENG_CONFIG, shell): LIBS += -ldeng_shell + +# Instruct the dynamic linker to load the libs from the SDK lib dir. +*-g++*|*-gcc*|*-clang* { + QMAKE_LFLAGS += -Wl,-rpath,$$DENG_SDK_DIR/lib +} + +macx { + QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.7 +} + +DENG_MODULES = $$DENG_SDK_DIR/modules/*.de + +# Macros --------------------------------------------------------------------- + +defineTest(dengPostLink) { + isEmpty(QMAKE_POST_LINK) { + QMAKE_POST_LINK = $$1 + } else { + QMAKE_POST_LINK = $$QMAKE_POST_LINK && $$1 + } + export(QMAKE_POST_LINK) +} + +defineTest(dengClear) { + # 1: file to remove + win32: system(del /q \"$$1\") + else: system(rm -f \"$$1\") +} + +defineTest(dengPack) { + # 1: path of a .pack file + # 2: actual root directory + # 3: files to include, relative to the root + system(cd \"$$2\" && zip -r \"$$1\" $$3) +} + +defineTest(dengPackModules) { + # 1: path of a .pack file + dengPack($$1, $$DENG_SDK_DIR, modules/*.de) +} + +defineReplace(dengFindLibDir) { + # Determines the appropriate library directory given prefix $$1 + prefix = $$1 + dir = $$prefix/lib + contains(QMAKE_HOST.arch, x86_64) { + exists($$prefix/lib64) { + dir = $$prefix/lib64 + } + exists($$prefix/lib/x86_64-linux-gnu) { + dir = $$prefix/lib/x86_64-linux-gnu + } + } + return($$dir) +} + +defineReplace(dengSdkLib) { + # 1: name of library + win32: fn = $$DENG_SDK_DIR/lib/$${1}.dll + else:macx { + versions = 2 1 0 + for(vers, versions) { + fn = $$DENG_SDK_DIR/lib/lib$${1}.$${vers}.dylib + exists($$fn): return($$fn) + } + } + else { + fn = $$DENG_SDK_DIR/lib/lib$${1}.so + # Apply the latest major version. + exists($${fn}.2): fn = $${fn}.2 + else:exists($${fn}.1): fn = $${fn}.1 + else:exists($${fn}.0): fn = $${fn}.0 + } + return($$fn) +} + +defineTest(dengDeploy) { + # 1: app name + # 2: install prefix + # 3: base pack file + prefix = $$2 + INSTALLS += target basepack denglibs + basepack.files = $$3 + + denglibs.files = $$dengSdkLib(deng2) + contains(DENG_CONFIG, gui): denglibs.files += $$dengSdkLib(deng_gui) + contains(DENG_CONFIG, appfw): denglibs.files += $$dengSdkLib(deng_appfw) + contains(DENG_CONFIG, shell): denglibs.files += $$dengSdkLib(deng_shell) + + win32 { + } + else:macx { + QMAKE_BUNDLE_DATA += $$INSTALLS + QMAKE_BUNDLE_DATA -= target + basepack.path = Contents/Resources + denglibs.path = Contents/Frameworks + } + else { + target.path = $$prefix/bin + basepack.path = $$prefix/share/$${1} + denglibs.path = $$dengFindLibDir($$prefix) + } + + unix { + # Symlink the libraries rather than copy. + macx { + QMAKE_BUNDLE_DATA -= denglibs + fwDir = $${TARGET}.app/$$denglibs.path + } + else { + INSTALLS -= denglibs + fwDir = $$denglibs.path + } + for(fn, denglibs.files) { + dengPostLink(mkdir -p \"$$fwDir\" && ln -sf \"$$fn\" \"$$fwDir\") + } + } + + macx: export(QMAKE_BUNDLE_DATA) + else { + export(INSTALLS) + export(target.path) + } + export(basepack.files) + export(basepack.path) + export(denglibs.files) + export(denglibs.path) +} diff --git a/doomsday/libappfw/include/de/framework/childwidgetorganizer.h b/doomsday/libappfw/include/de/framework/childwidgetorganizer.h index 7c4dfe1b90..7b2dce319e 100644 --- a/doomsday/libappfw/include/de/framework/childwidgetorganizer.h +++ b/doomsday/libappfw/include/de/framework/childwidgetorganizer.h @@ -100,7 +100,7 @@ class LIBAPPFW_PUBLIC ChildWidgetOrganizer * Notified when the organizer creates a widget for a context item. Allows * third parties to customize the widget as needed. */ - DENG2_DEFINE_AUDIENCE(WidgetCreation, + DENG2_DEFINE_AUDIENCE2(WidgetCreation, void widgetCreatedForItem(GuiWidget &widget, ui::Item const &item)) @@ -108,7 +108,7 @@ class LIBAPPFW_PUBLIC ChildWidgetOrganizer * Notified when the organizer updates a widget for a changed context item. * Allows third parties to customize the widget as needed. */ - DENG2_DEFINE_AUDIENCE(WidgetUpdate, + DENG2_DEFINE_AUDIENCE2(WidgetUpdate, void widgetUpdatedForItem(GuiWidget &widget, ui::Item const &item)) diff --git a/doomsday/libappfw/include/de/framework/data.h b/doomsday/libappfw/include/de/framework/data.h index 3a766e700e..bae21bc774 100644 --- a/doomsday/libappfw/include/de/framework/data.h +++ b/doomsday/libappfw/include/de/framework/data.h @@ -50,18 +50,20 @@ class LIBAPPFW_PUBLIC Data /** * Notified when a new item is added to the data context. */ - DENG2_DEFINE_AUDIENCE(Addition, void dataItemAdded(Pos id, Item const &item)) + DENG2_DEFINE_AUDIENCE2(Addition, void dataItemAdded(Pos id, Item const &item)) /** * Notified when an item has been removed from the data context. When this * is called @a item is no longer in the context and can be modified at * will. */ - DENG2_DEFINE_AUDIENCE(Removal, void dataItemRemoved(Pos oldId, Item &item)) + DENG2_DEFINE_AUDIENCE2(Removal, void dataItemRemoved(Pos oldId, Item &item)) - DENG2_DEFINE_AUDIENCE(OrderChange, void dataItemOrderChanged()) + DENG2_DEFINE_AUDIENCE2(OrderChange, void dataItemOrderChanged()) public: + Data(); + virtual ~Data() {} virtual Data &clear() = 0; @@ -125,8 +127,13 @@ class LIBAPPFW_PUBLIC Data * Returns the total number of items in the data context. */ virtual dsize size() const = 0; + +private: + DENG2_PRIVATE(d) }; +typedef Data::Pos DataPos; + } // namespace ui } // namespace de diff --git a/doomsday/libappfw/include/de/framework/guiwidgetprivate.h b/doomsday/libappfw/include/de/framework/guiwidgetprivate.h index 8ed31c717a..00ca9de28a 100644 --- a/doomsday/libappfw/include/de/framework/guiwidgetprivate.h +++ b/doomsday/libappfw/include/de/framework/guiwidgetprivate.h @@ -60,7 +60,7 @@ class GuiWidgetPrivate : public Private, { if(_observingAtlas) { - _observingAtlas->audienceForReposition -= this; + _observingAtlas->audienceForReposition() -= this; } /** @@ -80,7 +80,7 @@ class GuiWidgetPrivate : public Private, { // Automatically start observing the root atlas. _observingAtlas = &root().atlas(); - _observingAtlas->audienceForReposition += this; + _observingAtlas->audienceForReposition() += this; } } diff --git a/doomsday/libappfw/include/de/framework/item.h b/doomsday/libappfw/include/de/framework/item.h index dc5ed13df3..612830cab3 100644 --- a/doomsday/libappfw/include/de/framework/item.h +++ b/doomsday/libappfw/include/de/framework/item.h @@ -60,7 +60,7 @@ class LIBAPPFW_PUBLIC Item }; Q_DECLARE_FLAGS(Semantics, SemanticFlag) - DENG2_DEFINE_AUDIENCE(Change, void itemChanged(Item const &item)) + DENG2_DEFINE_AUDIENCE2(Change, void itemChanged(Item const &item)) public: Item(Semantics semantics = DefaultSemantics); @@ -75,14 +75,11 @@ class LIBAPPFW_PUBLIC Item String label() const; - void setDataContext(Data &context) { _context = &context; } + void setDataContext(Data &context); - bool hasDataContext() const { return _context != 0; } + bool hasDataContext() const; - Data &dataContext() const { - DENG2_ASSERT(hasDataContext()); - return *_context; - } + Data &dataContext() const; /** * Returns a text string that should be used for sorting the item inside a @@ -95,9 +92,9 @@ class LIBAPPFW_PUBLIC Item * * @param d Variant data to be associated with the item. */ - void setData(QVariant const &d) { _data = d; } + void setData(QVariant const &d); - QVariant const &data() const { return _data; } + QVariant const &data() const; DENG2_AS_IS_METHODS() @@ -108,12 +105,7 @@ class LIBAPPFW_PUBLIC Item void notifyChange(); private: - Semantics _semantics; - Data *_context; - String _label; - QVariant _data; - - friend class Data; + DENG2_PRIVATE(d) }; Q_DECLARE_OPERATORS_FOR_FLAGS(Item::Semantics) diff --git a/doomsday/libappfw/include/de/framework/margins.h b/doomsday/libappfw/include/de/framework/margins.h index 9a584beaaf..5e38bae768 100644 --- a/doomsday/libappfw/include/de/framework/margins.h +++ b/doomsday/libappfw/include/de/framework/margins.h @@ -34,7 +34,7 @@ namespace ui { class LIBAPPFW_PUBLIC Margins { public: - DENG2_DEFINE_AUDIENCE(Change, void marginsChanged()) + DENG2_DEFINE_AUDIENCE2(Change, void marginsChanged()) public: Margins(String const &defaultMargin = "gap"); diff --git a/doomsday/libappfw/include/de/vr/oculusrift.h b/doomsday/libappfw/include/de/vr/oculusrift.h index 278cc86e64..c472fe542a 100644 --- a/doomsday/libappfw/include/de/vr/oculusrift.h +++ b/doomsday/libappfw/include/de/vr/oculusrift.h @@ -22,6 +22,7 @@ #include "../libappfw.h" #include +#include namespace de { @@ -49,10 +50,18 @@ class LIBAPPFW_PUBLIC OculusRift void update(); - // Returns current pitch, roll, yaw angles, in radians. If no head tracking is available, - // the returned values are not valid. + /** + * Returns the current head orientation as a vector containing the pitch, roll and + * yaw angles, in radians. If no head tracking is available, the returned values are + * not valid. + */ Vector3f headOrientation() const; + /** + * Returns a model-view matrix that applies the head's orientation. + */ + Matrix4f headModelViewMatrix() const; + float predictionLatency() const; /** diff --git a/doomsday/libappfw/include/de/vr/vrconfig.h b/doomsday/libappfw/include/de/vr/vrconfig.h index 2b5a513a37..2585c324a9 100644 --- a/doomsday/libappfw/include/de/vr/vrconfig.h +++ b/doomsday/libappfw/include/de/vr/vrconfig.h @@ -21,6 +21,7 @@ #define LIBAPPFW_VRCONFIG_H #include +#include namespace de { @@ -172,6 +173,33 @@ class LIBAPPFW_PUBLIC VRConfig /// Multisampling used in unwarped Rift framebuffer. int riftFramebufferSampleCount() const; + float viewAspect(Vector2f const &viewPortSize) const; + + /** + * Calculates a vertical field of view angle based on a horizontal FOV angle, + * view port size, and the current VR configuration. + * + * @param horizFovDegrees Field of view horizontally, in degrees. + * @param viewPortSize Size of the viewport in pixels. + * + * @return Vertical field of view, in degrees. + */ + float verticalFieldOfView(float horizFovDegrees, Vector2f const &viewPortSize) const; + + /** + * Produces a projection matrix suitable for the current VR configuration. + * + * @param fovDegrees Horizontal field of view angle. + * @param viewPortSize Size of the viewport in pixels. + * @param nearClip Distance of the near clipping plane. + * @param farClip Distance of the far clipping plane. + * + * @return Projection matrix. + */ + Matrix4f projectionMatrix(float fovDegrees, + Vector2f const &viewPortSize, + float nearClip, float farClip) const; + de::OculusRift &oculusRift(); de::OculusRift const &oculusRift() const; diff --git a/doomsday/libappfw/include/de/widgets/buttonwidget.h b/doomsday/libappfw/include/de/widgets/buttonwidget.h index 65fa2d9d9a..ce7322fc57 100644 --- a/doomsday/libappfw/include/de/widgets/buttonwidget.h +++ b/doomsday/libappfw/include/de/widgets/buttonwidget.h @@ -43,20 +43,20 @@ class LIBAPPFW_PUBLIC ButtonWidget : public LabelWidget /** * Notified when the state of the button changes. */ - DENG2_DEFINE_AUDIENCE(StateChange, void buttonStateChanged(ButtonWidget &button, State state)) + DENG2_DEFINE_AUDIENCE2(StateChange, void buttonStateChanged(ButtonWidget &button, State state)) /** * Notified immediately before the button's action is to be triggered. Will * occur regardless of whether an action has been set. */ - DENG2_DEFINE_AUDIENCE(Press, void buttonPressed(ButtonWidget &button)) + DENG2_DEFINE_AUDIENCE2(Press, void buttonPressed(ButtonWidget &button)) /** * Notified when the button's action is triggered (could be before or after * the action). Will not occur if no action has been defined for the * button. */ - DENG2_DEFINE_AUDIENCE(Triggered, void buttonActionTriggered(ButtonWidget &button)) + DENG2_DEFINE_AUDIENCE2(Triggered, void buttonActionTriggered(ButtonWidget &button)) public: ButtonWidget(String const &name = ""); @@ -86,12 +86,18 @@ class LIBAPPFW_PUBLIC ButtonWidget : public LabelWidget Action const *action() const; + /** + * Triggers the action of the button. + */ + void trigger(); + State state() const; // Events. void update(); bool handleEvent(Event const &event); + protected: void updateModelViewProjection(GLUniform &uMvp); diff --git a/doomsday/libappfw/include/de/widgets/dialogwidget.h b/doomsday/libappfw/include/de/widgets/dialogwidget.h index 1320ee725e..da68c00700 100644 --- a/doomsday/libappfw/include/de/widgets/dialogwidget.h +++ b/doomsday/libappfw/include/de/widgets/dialogwidget.h @@ -141,7 +141,7 @@ class LIBAPPFW_PUBLIC DialogWidget : public PopupWidget ScrollAreaWidget &area(); - //MenuWidget &buttons(); + MenuWidget &buttonsMenu(); /** * Additional buttons of the dialog, laid out opposite to the normal dialog @@ -159,6 +159,15 @@ class LIBAPPFW_PUBLIC DialogWidget : public PopupWidget ButtonWidget *buttonWidget(int roleId) const; + /** + * Sets the action that will be triggered if the dialog is accepted. The action + * will be triggered after the dialog has started closing (called from + * DialogWidget::finish()). + * + * @param action Action to trigger after the dialog has been accepted. + */ + void setAcceptanceAction(RefArg action); + /** * Shows the dialog and blocks execution until the dialog is closed -- * another event loop is started for event processing. Call either accept() diff --git a/doomsday/libappfw/include/de/widgets/foldpanelwidget.h b/doomsday/libappfw/include/de/widgets/foldpanelwidget.h index 553709b2e6..af6d401ea3 100644 --- a/doomsday/libappfw/include/de/widgets/foldpanelwidget.h +++ b/doomsday/libappfw/include/de/widgets/foldpanelwidget.h @@ -27,15 +27,14 @@ namespace de { /** * Folding panel. * - * You should first set the container of the folding panel with setContent(). - * This ensures that widgets added to the panel use the appropriate stylist. + * You should first set the container of the folding panel with setContent(). This + * ensures that widgets added to the panel use the appropriate stylist. * - * When dismissed, the panel contents are GL-deinitialized and removed from - * the widget tree entirely. + * When dismissed, the panel contents are GL-deinitialized and removed from the widget + * tree entirely. * - * FoldPanelWidget creates a title button for toggling the panel open and - * closed. It is the user's responsibility to lay out this button - * appropriately. + * If needed, FoldPanelWidget can create a title button for toggling the panel open and + * closed. It is the user's responsibility to lay out this button appropriately. */ class LIBAPPFW_PUBLIC FoldPanelWidget : public PanelWidget { diff --git a/doomsday/libappfw/include/de/widgets/panelwidget.h b/doomsday/libappfw/include/de/widgets/panelwidget.h index 547031ba1d..288dfee8e1 100644 --- a/doomsday/libappfw/include/de/widgets/panelwidget.h +++ b/doomsday/libappfw/include/de/widgets/panelwidget.h @@ -43,7 +43,7 @@ class LIBAPPFW_PUBLIC PanelWidget : public GuiWidget /** * Audience to be notified when the panel is closing. */ - DENG2_DEFINE_AUDIENCE(Close, void panelBeingClosed(PanelWidget &)) + DENG2_DEFINE_AUDIENCE2(Close, void panelBeingClosed(PanelWidget &)) public: PanelWidget(String const &name = ""); diff --git a/doomsday/libappfw/include/de/widgets/popupwidget.h b/doomsday/libappfw/include/de/widgets/popupwidget.h index f47c8bed70..48b9227ca7 100644 --- a/doomsday/libappfw/include/de/widgets/popupwidget.h +++ b/doomsday/libappfw/include/de/widgets/popupwidget.h @@ -57,6 +57,11 @@ class LIBAPPFW_PUBLIC PopupWidget : public PanelWidget Rule const &anchorX() const; Rule const &anchorY() const; + /** + * Replace the anchor with rules of matching constant value. + */ + void detachAnchor(); + /** * Tells the popup to delete itself after being dismissed. The default is that * the popup does not get deleted. diff --git a/doomsday/libappfw/include/de/widgets/progresswidget.h b/doomsday/libappfw/include/de/widgets/progresswidget.h index d42b68d97c..7ec5249581 100644 --- a/doomsday/libappfw/include/de/widgets/progresswidget.h +++ b/doomsday/libappfw/include/de/widgets/progresswidget.h @@ -48,7 +48,7 @@ class LIBAPPFW_PUBLIC ProgressWidget : public LabelWidget public: ProgressWidget(String const &name = ""); - void useMiniStyle(); + void useMiniStyle(DotPath const &colorId = "text"); void setRotationSpeed(float anglesPerSecond); Mode mode() const; diff --git a/doomsday/libappfw/include/de/widgets/togglewidget.h b/doomsday/libappfw/include/de/widgets/togglewidget.h index 644077dd1d..9df8ff39ab 100644 --- a/doomsday/libappfw/include/de/widgets/togglewidget.h +++ b/doomsday/libappfw/include/de/widgets/togglewidget.h @@ -40,7 +40,7 @@ class LIBAPPFW_PUBLIC ToggleWidget : public ButtonWidget /** * Audience to be notified whenever the toggle is toggled. */ - DENG2_DEFINE_AUDIENCE(Toggle, void toggleStateChanged(ToggleWidget &toggle)) + DENG2_DEFINE_AUDIENCE2(Toggle, void toggleStateChanged(ToggleWidget &toggle)) public: ToggleWidget(String const &name = ""); diff --git a/doomsday/libappfw/libappfw.pro b/doomsday/libappfw/libappfw.pro index 28ea1c51ce..cc528540c6 100644 --- a/doomsday/libappfw/libappfw.pro +++ b/doomsday/libappfw/libappfw.pro @@ -28,7 +28,7 @@ win32 { } # Public headers. -HEADERS += \ +publicHeaders(root, \ include/de/AtlasProceduralImage \ include/de/BaseGuiApp \ include/de/BaseWindow \ @@ -70,12 +70,21 @@ HEADERS += \ include/de/SequentialLayout \ include/de/SignalAction \ include/de/SliderWidget \ + include/de/Style \ include/de/TabWidget \ include/de/TextDrawable \ include/de/ToggleWidget \ include/de/VRWindowTransform \ include/de/WindowSystem \ include/de/WindowTransform \ + include/de/VariableChoiceWidget \ + include/de/VariableToggleWidget \ + include/de/VRConfig \ + \ + include/de/libappfw.h \ +) + +publicHeaders(ui, \ include/de/ui/ActionItem \ include/de/ui/Data \ include/de/ui/ImageItem \ @@ -86,12 +95,11 @@ HEADERS += \ include/de/ui/SubmenuItem \ include/de/ui/SubwidgetItem \ include/de/ui/VariableToggleItem \ - include/de/VariableChoiceWidget \ - include/de/VariableToggleWidget \ - include/de/VRConfig \ \ - include/de/dialogs/inputdialog.h \ - include/de/dialogs/messagedialog.h \ + include/de/ui/defs.h \ +) + +publicHeaders(framework, \ include/de/framework/actionitem.h \ include/de/framework/atlasproceduralimage.h \ include/de/framework/baseguiapp.h \ @@ -123,10 +131,14 @@ HEADERS += \ include/de/framework/vrwindowtransform.h \ include/de/framework/windowsystem.h \ include/de/framework/windowtransform.h \ - include/de/libappfw.h \ - include/de/ui/defs.h \ +) + +publicHeaders(vr, \ include/de/vr/oculusrift.h \ include/de/vr/vrconfig.h \ +) + +publicHeaders(widgets, \ include/de/widgets/blurwidget.h \ include/de/widgets/buttonwidget.h \ include/de/widgets/choicewidget.h \ @@ -152,7 +164,13 @@ HEADERS += \ include/de/widgets/tabwidget.h \ include/de/widgets/togglewidget.h \ include/de/widgets/variablechoicewidget.h \ - include/de/widgets/variabletogglewidget.h + include/de/widgets/variabletogglewidget.h \ +) + +publicHeaders(dialogs, \ + include/de/dialogs/inputdialog.h \ + include/de/dialogs/messagedialog.h \ +) # Sources and private headers. SOURCES += \ @@ -224,3 +242,8 @@ else { INSTALLS += target target.path = $$DENG_LIB_DIR } + +deng_sdk { + INSTALLS *= target + target.path = $$DENG_SDK_LIB_DIR +} diff --git a/doomsday/libappfw/src/baseguiapp.cpp b/doomsday/libappfw/src/baseguiapp.cpp index 74438a48be..a7353f5ac7 100644 --- a/doomsday/libappfw/src/baseguiapp.cpp +++ b/doomsday/libappfw/src/baseguiapp.cpp @@ -19,10 +19,71 @@ #include "de/BaseGuiApp" #include "de/VRConfig" +#include +#include +#include +#include +#include + namespace de { +static Value *Function_App_LoadFont(Context &, Function::ArgumentValues const &args) +{ + try + { + // Try to load the specific font. + Block data(App::fileSystem().root().locate(args.at(0)->asText())); + int id; + id = QFontDatabase::addApplicationFontFromData(data); + if(id < 0) + { + LOG_RES_WARNING("Failed to load font:"); + } + else + { + LOG_RES_VERBOSE("Loaded font: %s") << args.at(0)->asText(); + //qDebug() << args.at(0)->asText(); + //qDebug() << "Families:" << QFontDatabase::applicationFontFamilies(id); + } + } + catch(Error const &er) + { + LOG_RES_WARNING("Failed to load font:\n") << er.asText(); + } + return 0; +} + +static Value *Function_App_AddFontMapping(Context &, Function::ArgumentValues const &args) +{ + // arg 0: family name + // arg 1: dictionary with [Text style, Number weight] => Text fontname + + // styles: regular, italic + // weight: 0-99 (25=light, 50=normal, 75=bold) + + NativeFont::StyleMapping mapping; + + DictionaryValue const &dict = args.at(1)->as(); + DENG2_FOR_EACH_CONST(DictionaryValue::Elements, i, dict.elements()) + { + NativeFont::Spec spec; + ArrayValue const &key = i->first.value->as(); + if(key.at(0).asText() == "italic") + { + spec.style = NativeFont::Italic; + } + spec.weight = roundi(key.at(1).asNumber()); + mapping.insert(spec, i->second->asText()); + } + + NativeFont::defineMapping(args.at(0)->asText(), mapping); + + return 0; +} + DENG2_PIMPL_NOREF(BaseGuiApp) { + Binder binder; QScopedPointer uiState; GLShaderBank shaders; VRConfig vr; @@ -30,7 +91,14 @@ DENG2_PIMPL_NOREF(BaseGuiApp) BaseGuiApp::BaseGuiApp(int &argc, char **argv) : GuiApp(argc, argv), d(new Instance) -{} +{ + // Override the system locale (affects number/time formatting). + QLocale::setDefault(QLocale("en_US.UTF-8")); + + d->binder.init(scriptSystem().nativeModule("App")) + << DENG2_FUNC (App_AddFontMapping, "addFontMapping", "family" << "mappings") + << DENG2_FUNC (App_LoadFont, "loadFont", "fileName"); +} void BaseGuiApp::initSubsystems(SubsystemInitFlags flags) { diff --git a/doomsday/libappfw/src/basewindow.cpp b/doomsday/libappfw/src/basewindow.cpp index 8831eae76f..d85f7554bb 100644 --- a/doomsday/libappfw/src/basewindow.cpp +++ b/doomsday/libappfw/src/basewindow.cpp @@ -37,14 +37,14 @@ DENG2_PIMPL(BaseWindow) , xf(&defaultXf) { // Listen to input. - self.canvas().audienceForKeyEvent += this; - self.canvas().audienceForMouseEvent += this; + self.canvas().audienceForKeyEvent() += this; + self.canvas().audienceForMouseEvent() += this; } ~Instance() { - self.canvas().audienceForKeyEvent -= this; - self.canvas().audienceForMouseEvent -= this; + self.canvas().audienceForKeyEvent() -= this; + self.canvas().audienceForMouseEvent() -= this; } void keyEvent(KeyEvent const &ev) diff --git a/doomsday/libappfw/src/childwidgetorganizer.cpp b/doomsday/libappfw/src/childwidgetorganizer.cpp index 9b21d3f4ed..fb937dee60 100644 --- a/doomsday/libappfw/src/childwidgetorganizer.cpp +++ b/doomsday/libappfw/src/childwidgetorganizer.cpp @@ -55,7 +55,7 @@ DENG2_OBSERVES(ui::Item, Change ) { DENG2_FOR_EACH_CONST(Mapping, i, mapping) { - i.value()->audienceForDeletion -= this; + i.value()->audienceForDeletion() -= this; } } @@ -63,9 +63,9 @@ DENG2_OBSERVES(ui::Item, Change ) { if(context) { - context->audienceForAddition -= this; - context->audienceForRemoval -= this; - context->audienceForOrderChange -= this; + context->audienceForAddition() -= this; + context->audienceForRemoval() -= this; + context->audienceForOrderChange() -= this; clearWidgets(); context = 0; @@ -77,9 +77,9 @@ DENG2_OBSERVES(ui::Item, Change ) { makeWidgets(); - context->audienceForAddition += this; - context->audienceForRemoval += this; - context->audienceForOrderChange += this; + context->audienceForAddition() += this; + context->audienceForRemoval() += this; + context->audienceForOrderChange() += this; } } @@ -116,14 +116,14 @@ DENG2_OBSERVES(ui::Item, Change ) } // Others may alter the widget in some way. - DENG2_FOR_PUBLIC_AUDIENCE(WidgetCreation, i) + DENG2_FOR_PUBLIC_AUDIENCE2(WidgetCreation, i) { i->widgetCreatedForItem(*w, item); } // Observe. - w->audienceForDeletion += this; // in case it's manually deleted - item.audienceForChange += this; + w->audienceForDeletion() += this; // in case it's manually deleted + item.audienceForChange() += this; } void makeWidgets() @@ -139,7 +139,7 @@ DENG2_OBSERVES(ui::Item, Change ) void deleteWidget(GuiWidget *w) { - w->audienceForDeletion -= this; + w->audienceForDeletion() -= this; GuiWidget::destroy(w); } @@ -147,7 +147,7 @@ DENG2_OBSERVES(ui::Item, Change ) { DENG2_FOR_EACH_CONST(Mapping, i, mapping) { - i.key()->audienceForChange -= this; + i.key()->audienceForChange() -= this; deleteWidget(i.value()); } @@ -182,7 +182,7 @@ DENG2_OBSERVES(ui::Item, Change ) Mapping::const_iterator found = mapping.constFind(&item); if(found != mapping.constEnd()) { - found.key()->audienceForChange -= this; + found.key()->audienceForChange() -= this; deleteWidget(found.value()); mapping.remove(found.key()); } @@ -216,7 +216,7 @@ DENG2_OBSERVES(ui::Item, Change ) factory->updateItemWidget(w, item); // Notify. - DENG2_FOR_PUBLIC_AUDIENCE(WidgetUpdate, i) + DENG2_FOR_PUBLIC_AUDIENCE2(WidgetUpdate, i) { i->widgetUpdatedForItem(w, item); } @@ -252,8 +252,14 @@ DENG2_OBSERVES(ui::Item, Change ) } return 0; } + + DENG2_PIMPL_AUDIENCE(WidgetCreation) + DENG2_PIMPL_AUDIENCE(WidgetUpdate) }; +DENG2_AUDIENCE_METHOD(ChildWidgetOrganizer, WidgetCreation) +DENG2_AUDIENCE_METHOD(ChildWidgetOrganizer, WidgetUpdate) + ChildWidgetOrganizer::ChildWidgetOrganizer(GuiWidget &container) : d(new Instance(this, &container)) {} diff --git a/doomsday/libappfw/src/data.cpp b/doomsday/libappfw/src/data.cpp index 174125bfae..1984939347 100644 --- a/doomsday/libappfw/src/data.cpp +++ b/doomsday/libappfw/src/data.cpp @@ -38,6 +38,20 @@ static bool itemGreaterThan(Item const &a, Item const &b) return a.sortKey().compareWithoutCase(b.sortKey()) > 0; } +DENG2_PIMPL_NOREF(Data) +{ + DENG2_PIMPL_AUDIENCE(Addition) + DENG2_PIMPL_AUDIENCE(Removal) + DENG2_PIMPL_AUDIENCE(OrderChange) +}; + +DENG2_AUDIENCE_METHOD(Data, Addition) +DENG2_AUDIENCE_METHOD(Data, Removal) +DENG2_AUDIENCE_METHOD(Data, OrderChange) + +Data::Data() : d(new Instance) +{} + void Data::sort(SortMethod method) { switch(method) diff --git a/doomsday/libappfw/src/dialogcontentstylist.cpp b/doomsday/libappfw/src/dialogcontentstylist.cpp index 65ebaff526..3628731a14 100644 --- a/doomsday/libappfw/src/dialogcontentstylist.cpp +++ b/doomsday/libappfw/src/dialogcontentstylist.cpp @@ -41,7 +41,7 @@ DialogContentStylist::~DialogContentStylist() { if(_container) { - _container->audienceForChildAddition -= this; + _container->audienceForChildAddition() -= this; } } @@ -49,11 +49,11 @@ void DialogContentStylist::setContainer(GuiWidget &container) { if(_container) { - _container->audienceForChildAddition -= this; + _container->audienceForChildAddition() -= this; } _container = &container; - _container->audienceForChildAddition += this; + _container->audienceForChildAddition() += this; } void DialogContentStylist::widgetChildAdded(Widget &child) diff --git a/doomsday/libappfw/src/guirootwidget.cpp b/doomsday/libappfw/src/guirootwidget.cpp index 4a95eab8b2..78ce7c980d 100644 --- a/doomsday/libappfw/src/guirootwidget.cpp +++ b/doomsday/libappfw/src/guirootwidget.cpp @@ -55,7 +55,7 @@ DENG2_PIMPL(GuiRootWidget) , uTexAtlas("uTex", GLUniform::Sampler2D) , noFramesDrawnYet(true) { - self.audienceForChildAddition += this; + self.audienceForChildAddition() += this; } ~Instance() diff --git a/doomsday/libappfw/src/guiwidget.cpp b/doomsday/libappfw/src/guiwidget.cpp index 3b65c4832d..3bac617fd8 100644 --- a/doomsday/libappfw/src/guiwidget.cpp +++ b/doomsday/libappfw/src/guiwidget.cpp @@ -86,11 +86,11 @@ DENG2_PIMPL(GuiWidget) , uBlurStep ("uBlurStep", GLUniform::Vec2) , uBlurWindow ("uWindow", GLUniform::Vec4) { - self.audienceForChildAddition += this; - margins.audienceForChange += this; + self.audienceForChildAddition() += this; + margins.audienceForChange() += this; #ifdef DENG2_DEBUG - self.audienceForParentChange += this; + self.audienceForParentChange() += this; rule.setDebugName(self.path()); #endif diff --git a/doomsday/libappfw/src/item.cpp b/doomsday/libappfw/src/item.cpp index 350bd50f40..ead2563408 100644 --- a/doomsday/libappfw/src/item.cpp +++ b/doomsday/libappfw/src/item.cpp @@ -21,12 +21,30 @@ namespace de { namespace ui { +DENG2_PIMPL_NOREF(Item) +{ + Data *context; + Semantics semantics; + String label; + QVariant data; + + Instance(Semantics sem, String const &text = "", QVariant var = QVariant()) + : context(0) + , semantics(sem) + , label(text) + , data(var) {} + + DENG2_PIMPL_AUDIENCE(Change) +}; + +DENG2_AUDIENCE_METHOD(Item, Change) + Item::Item(Semantics semantics) - : _semantics(semantics), _context(0) + : d(new Instance(semantics)) {} Item::Item(Semantics semantics, String const &label) - : _semantics(semantics), _context(0), _label(label) + : d(new Instance(semantics, label)) {} Item::~Item() @@ -34,28 +52,54 @@ Item::~Item() Item::Semantics Item::semantics() const { - return _semantics; + return d->semantics; } void Item::setLabel(String const &label) { - _label = label; + d->label = label; notifyChange(); } String Item::label() const { - return _label; + return d->label; +} + +void Item::setDataContext(Data &context) +{ + d->context = &context; +} + +bool Item::hasDataContext() const +{ + return d->context != 0; +} + +Data &Item::dataContext() const +{ + DENG2_ASSERT(hasDataContext()); + return *d->context; } String Item::sortKey() const { - return _label; + return d->label; +} + +void Item::setData(QVariant const &v) +{ + d->data = v; +} + +QVariant const &Item::data() const +{ + return d->data; } void Item::notifyChange() { - DENG2_FOR_AUDIENCE(Change, i) + DENG2_FOR_AUDIENCE2(Change, i) { i->itemChanged(*this); } diff --git a/doomsday/libappfw/src/listdata.cpp b/doomsday/libappfw/src/listdata.cpp index 9abd60a795..576fd9c1f2 100644 --- a/doomsday/libappfw/src/listdata.cpp +++ b/doomsday/libappfw/src/listdata.cpp @@ -81,7 +81,7 @@ Data &ListData::insert(Pos pos, Item *item) item->setDataContext(*this); // Notify. - DENG2_FOR_AUDIENCE(Addition, i) + DENG2_FOR_AUDIENCE2(Addition, i) { i->dataItemAdded(pos, *item); } @@ -101,7 +101,7 @@ Item *ListData::take(Data::Pos pos) Item *taken = _items.takeAt(pos); // Notify. - DENG2_FOR_AUDIENCE(Removal, i) + DENG2_FOR_AUDIENCE2(Removal, i) { i->dataItemRemoved(pos, *taken); } @@ -123,7 +123,7 @@ void ListData::sort(LessThanFunc lessThan) qSort(_items.begin(), _items.end(), ListItemSorter(lessThan)); // Notify. - DENG2_FOR_AUDIENCE(OrderChange, i) + DENG2_FOR_AUDIENCE2(OrderChange, i) { i->dataItemOrderChanged(); } @@ -134,7 +134,7 @@ void ListData::stableSort(LessThanFunc lessThan) qStableSort(_items.begin(), _items.end(), ListItemSorter(lessThan)); // Notify. - DENG2_FOR_AUDIENCE(OrderChange, i) + DENG2_FOR_AUDIENCE2(OrderChange, i) { i->dataItemOrderChanged(); } diff --git a/doomsday/libappfw/src/margins.cpp b/doomsday/libappfw/src/margins.cpp index fdbea37d05..6e482f2829 100644 --- a/doomsday/libappfw/src/margins.cpp +++ b/doomsday/libappfw/src/margins.cpp @@ -81,7 +81,7 @@ DENG2_PIMPL(Margins) changeRef(inputs[side], rule); updateOutput(side); - DENG2_FOR_PUBLIC_AUDIENCE(Change, i) + DENG2_FOR_AUDIENCE(Change, i) { i->marginsChanged(); } @@ -120,8 +120,12 @@ DENG2_PIMPL(Margins) } return *outputs[side]; } + + DENG2_PIMPL_AUDIENCE(Change) }; +DENG2_AUDIENCE_METHOD(Margins, Change) + Margins::Margins(String const &defaultMargin) : d(new Instance(this, defaultMargin)) {} diff --git a/doomsday/libappfw/src/vr/oculusrift.cpp b/doomsday/libappfw/src/vr/oculusrift.cpp index 8b8bf7f784..2ad871dfed 100644 --- a/doomsday/libappfw/src/vr/oculusrift.cpp +++ b/doomsday/libappfw/src/vr/oculusrift.cpp @@ -319,7 +319,7 @@ void OculusRift::update() Vector3f OculusRift::headOrientation() const { - de::Vector3f result; + Vector3f result; #ifdef DENG_HAVE_OCULUS_API DENG2_GUARD(d); if(isReady()) @@ -332,6 +332,14 @@ Vector3f OculusRift::headOrientation() const return result; } +Matrix4f OculusRift::headModelViewMatrix() const +{ + Vector3f const pry = headOrientation(); + return Matrix4f::rotate(pry[1], Vector3f(0, 0, 1)) * + Matrix4f::rotate(pry[0], Vector3f(1, 0, 0)) * + Matrix4f::rotate(pry[2], Vector3f(0, 1, 0)); +} + float OculusRift::distortionScale() const { float lensShift = d->screenSize.x * 0.25f - lensSeparationDistance() * 0.5f; diff --git a/doomsday/libappfw/src/vr/vrconfig.cpp b/doomsday/libappfw/src/vr/vrconfig.cpp index 3388d6f69e..4f53b9124f 100644 --- a/doomsday/libappfw/src/vr/vrconfig.cpp +++ b/doomsday/libappfw/src/vr/vrconfig.cpp @@ -18,6 +18,7 @@ */ #include "de/VRConfig" +#include "de/math.h" namespace de { @@ -174,6 +175,63 @@ int VRConfig::riftFramebufferSampleCount() const return d->riftFramebufferSamples; } +float VRConfig::viewAspect(Vector2f const &viewPortSize) const +{ + if(mode() == OculusRift) + { + // Override with the Oculus Rift's aspect ratio. + return oculusRift().aspect(); + } + + // We're assuming pixels are squares. + return viewPortSize.x / viewPortSize.y; +} + +float VRConfig::verticalFieldOfView(float horizFovDegrees, Vector2f const &viewPortSize) const +{ + // We're assuming pixels are squares. + float const aspect = viewAspect(viewPortSize); + + if(mode() == OculusRift) + { + // A little trigonometry to apply aspect ratio to angles + float x = std::tan(.5f * degreeToRadian(horizFovDegrees)); + return radianToDegree(2.f * std::atan2(x / aspect, 1.f)); + } + + return horizFovDegrees / aspect; +} + +Matrix4f VRConfig::projectionMatrix(float fovDegrees, + Vector2f const &viewPortSize, + float nearClip, float farClip) const +{ + float const yfov = verticalFieldOfView(fovDegrees, viewPortSize); + float const fH = std::tan(.5f * degreeToRadian(yfov)) * nearClip; + float const fW = fH * viewAspect(viewPortSize); + + /* + * Asymmetric frustum shift is computed to realign screen-depth items after view point has shifted. + * Asymmetric frustum shift method is probably superior to competing toe-in stereo 3D method: + * - AFS preserves identical near and far clipping planes in both views + * - AFS shows items at/near infinity better + * - AFS conforms to what stereo 3D photographers call "ortho stereo" + * Asymmetric frustum shift is used for all stereo 3D modes except Oculus Rift mode, which only + * applies the viewpoint shift. + */ + float shift = 0; + if(frustumShift()) + { + shift = eyeShift() * nearClip / screenDistance(); + } + + return Matrix4f::frustum(-fW - shift, fW - shift, + -fH, fH, + nearClip, farClip) * + Matrix4f::translate(Vector3f(-eyeShift(), 0, 0)) * + Matrix4f::scale(Vector3f(1, 1, -1)); +} + OculusRift &VRConfig::oculusRift() { return d->ovr; diff --git a/doomsday/libappfw/src/widgets/buttonwidget.cpp b/doomsday/libappfw/src/widgets/buttonwidget.cpp index f47bb0aad9..13c319ca67 100644 --- a/doomsday/libappfw/src/widgets/buttonwidget.cpp +++ b/doomsday/libappfw/src/widgets/buttonwidget.cpp @@ -52,7 +52,7 @@ DENG2_OBSERVES(Action, Triggered) ~Instance() { - if(action) action->audienceForTriggered -= this; + if(action) action->audienceForTriggered() -= this; releaseRef(action); } @@ -113,7 +113,7 @@ DENG2_OBSERVES(Action, Triggered) break; } - DENG2_FOR_PUBLIC_AUDIENCE(StateChange, i) + DENG2_FOR_PUBLIC_AUDIENCE2(StateChange, i) { i->buttonStateChanged(self, state); } @@ -170,13 +170,21 @@ DENG2_OBSERVES(Action, Triggered) void actionTriggered(Action &) { - DENG2_FOR_PUBLIC_AUDIENCE(Triggered, i) + DENG2_FOR_PUBLIC_AUDIENCE2(Triggered, i) { i->buttonActionTriggered(self); } } + + DENG2_PIMPL_AUDIENCE(StateChange) + DENG2_PIMPL_AUDIENCE(Press) + DENG2_PIMPL_AUDIENCE(Triggered) }; +DENG2_AUDIENCE_METHOD(ButtonWidget, StateChange) +DENG2_AUDIENCE_METHOD(ButtonWidget, Press) +DENG2_AUDIENCE_METHOD(ButtonWidget, Triggered) + ButtonWidget::ButtonWidget(String const &name) : LabelWidget(name), d(new Instance(this)) {} @@ -196,14 +204,14 @@ void ButtonWidget::setAction(RefArg action) { if(d->action) { - d->action->audienceForTriggered -= d; + d->action->audienceForTriggered() -= d; } changeRef(d->action, action); if(action) { - action->audienceForTriggered += d; + action->audienceForTriggered() += d; } } @@ -212,6 +220,20 @@ Action const *ButtonWidget::action() const return d->action; } +void ButtonWidget::trigger() +{ + // Hold an extra ref so the action isn't deleted by triggering. + AutoRef held = holdRef(d->action); + + // Notify. + DENG2_FOR_AUDIENCE2(Press, i) i->buttonPressed(*this); + + if(held) + { + held->trigger(); + } +} + ButtonWidget::State ButtonWidget::state() const { return d->state; @@ -242,16 +264,7 @@ bool ButtonWidget::handleEvent(Event const &event) d->updateHover(mouse.pos()); if(hitTest(mouse.pos())) { - // Hold an extra ref so the action isn't deleted by triggering. - AutoRef held = holdRef(d->action); - - // Notify. - DENG2_FOR_AUDIENCE(Press, i) i->buttonPressed(*this); - - if(held) - { - held->trigger(); - } + trigger(); } return true; diff --git a/doomsday/libappfw/src/widgets/choicewidget.cpp b/doomsday/libappfw/src/widgets/choicewidget.cpp index 9989bb157e..124a458604 100644 --- a/doomsday/libappfw/src/widgets/choicewidget.cpp +++ b/doomsday/libappfw/src/widgets/choicewidget.cpp @@ -37,20 +37,21 @@ DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate) */ struct SelectAction : public de::Action { - Instance *d; + ChoiceWidget::Instance *wd; ui::Item const &selItem; - SelectAction(Instance *inst, ui::Item const &item) : d(inst), selItem(item) {} + SelectAction(ChoiceWidget::Instance *inst, ui::Item const &item) + : wd(inst), selItem(item) {} void trigger() { Action::trigger(); - d->selected = d->items().find(selItem); - d->updateButtonWithSelection(); - d->updateItemHighlight(); - d->choices->dismiss(); + wd->selected = wd->items().find(selItem); + wd->updateButtonWithSelection(); + wd->updateItemHighlight(); + wd->choices->dismiss(); - emit d->self.selectionChangedByUser(d->selected); + emit wd->self.selectionChangedByUser(wd->selected); } }; @@ -65,11 +66,11 @@ DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate) self.setFont("choice.selected"); choices = new PopupMenuWidget; - choices->items().audienceForAddition += this; - choices->items().audienceForRemoval += this; - choices->items().audienceForOrderChange += this; - choices->menu().organizer().audienceForWidgetCreation += this; - choices->menu().organizer().audienceForWidgetUpdate += this; + choices->items().audienceForAddition() += this; + choices->items().audienceForRemoval() += this; + choices->items().audienceForOrderChange() += this; + choices->menu().organizer().audienceForWidgetCreation() += this; + choices->menu().organizer().audienceForWidgetUpdate() += this; self.add(choices); self.setAction(new SignalAction(thisPublic, SLOT(openPopup()))); @@ -80,7 +81,7 @@ DENG2_OBSERVES(ChildWidgetOrganizer, WidgetUpdate) ~Instance() { - choices->items().audienceForRemoval -= this; + choices->items().audienceForRemoval() -= this; releaseRef(maxWidth); } diff --git a/doomsday/libappfw/src/widgets/dialogwidget.cpp b/doomsday/libappfw/src/widgets/dialogwidget.cpp index 35598a47d8..e0e534021e 100644 --- a/doomsday/libappfw/src/widgets/dialogwidget.cpp +++ b/doomsday/libappfw/src/widgets/dialogwidget.cpp @@ -85,6 +85,7 @@ public ChildWidgetOrganizer::IFilter MenuWidget *extraButtons; ui::ListData buttonItems; QEventLoop subloop; + de::Action *acceptAction; Animation glow; bool needButtonUpdate; float normalGlow; @@ -92,12 +93,13 @@ public ChildWidgetOrganizer::IFilter DialogContentStylist stylist; Instance(Public *i, Flags const &dialogFlags) - : Base(i), - modality(Modal), - flags(dialogFlags), - heading(0), - needButtonUpdate(false), - animatingGlow(false) + : Base(i) + , modality(Modal) + , flags(dialogFlags) + , heading(0) + , acceptAction(0) + , needButtonUpdate(false) + , animatingGlow(false) { // Initialize the border glow. normalGlow = style().colors().colorf("glow").w; @@ -112,19 +114,19 @@ public ChildWidgetOrganizer::IFilter buttons = new MenuWidget("buttons"); buttons->margins().setTop(""); buttons->setItems(buttonItems); - buttons->items().audienceForAddition += this; - buttons->items().audienceForRemoval += this; - buttons->organizer().audienceForWidgetCreation += this; - buttons->organizer().audienceForWidgetUpdate += this; + buttons->items().audienceForAddition() += this; + buttons->items().audienceForRemoval() += this; + buttons->organizer().audienceForWidgetCreation() += this; + buttons->organizer().audienceForWidgetUpdate() += this; buttons->organizer().setFilter(*this); extraButtons = new MenuWidget("extra"); extraButtons->margins().setTop(""); extraButtons->setItems(buttonItems); - extraButtons->items().audienceForAddition += this; - extraButtons->items().audienceForRemoval += this; - extraButtons->organizer().audienceForWidgetCreation += this; - extraButtons->organizer().audienceForWidgetUpdate += this; + extraButtons->items().audienceForAddition() += this; + extraButtons->items().audienceForRemoval() += this; + extraButtons->organizer().audienceForWidgetCreation() += this; + extraButtons->organizer().audienceForWidgetUpdate() += this; extraButtons->organizer().setFilter(*this); // The menu maintains its own width and height based on children. @@ -190,6 +192,11 @@ public ChildWidgetOrganizer::IFilter self.setContent(container); } + ~Instance() + { + releaseRef(acceptAction); + } + void updateContentHeight() { // Determine suitable maximum height. @@ -414,6 +421,11 @@ ScrollAreaWidget &DialogWidget::area() return *d->area; } +MenuWidget &DialogWidget::buttonsMenu() +{ + return *d->buttons; +} + /* MenuWidget &DialogWidget::buttons() { @@ -459,6 +471,11 @@ ButtonWidget *DialogWidget::buttonWidget(int roleId) const return 0; } +void DialogWidget::setAcceptanceAction(RefArg action) +{ + changeRef(d->acceptAction, action); +} + int DialogWidget::exec(GuiRootWidget &root) { d->modality = Modal; @@ -615,10 +632,20 @@ void DialogWidget::preparePanelForOpening() d->updateBackground(); } -void DialogWidget::finish(int) +void DialogWidget::finish(int result) { root().setFocus(0); close(); + + if(result > 0) + { + // Success! + if(d->acceptAction) + { + AutoRef held = *d->acceptAction; + held->trigger(); + } + } } DialogWidget::ButtonItem::ButtonItem(RoleFlags flags, String const &label) diff --git a/doomsday/libappfw/src/widgets/documentwidget.cpp b/doomsday/libappfw/src/widgets/documentwidget.cpp index 1fb98813b3..df33af3204 100644 --- a/doomsday/libappfw/src/widgets/documentwidget.cpp +++ b/doomsday/libappfw/src/widgets/documentwidget.cpp @@ -125,7 +125,7 @@ public Font::RichFormat::IStyle void glInit() { - atlas().audienceForReposition += this; + atlas().audienceForReposition() += this; glText.init(atlas(), self.font(), this); @@ -145,7 +145,7 @@ public Font::RichFormat::IStyle void glDeinit() { - atlas().audienceForReposition -= this; + atlas().audienceForReposition() -= this; glText.deinit(); drawable.clear(); } diff --git a/doomsday/libappfw/src/widgets/labelwidget.cpp b/doomsday/libappfw/src/widgets/labelwidget.cpp index 7d8663a52f..86f98825b5 100644 --- a/doomsday/libappfw/src/widgets/labelwidget.cpp +++ b/doomsday/libappfw/src/widgets/labelwidget.cpp @@ -249,7 +249,7 @@ public Font::RichFormat::IStyle */ void contentPlacement(ContentLayout &layout) const { - Rectanglei const contentRect = contentArea(); //self.rule().recti().adjusted(margin.xy(), -margin.zw()); + Rectanglei const contentRect = contentArea(); Vector2f const imgSize = imageSize() * imageScale; @@ -513,6 +513,38 @@ public Font::RichFormat::IStyle self.updateModelViewProjection(uMvpMatrix); drawable.draw(); } + + Rule const *widthRule() const + { + switch(appearType) + { + case AppearInstantly: + case AppearGrowVertically: + if(horizPolicy == Expand) return width; + break; + + case AppearGrowHorizontally: + if(horizPolicy == Expand) return appearSize; + break; + } + return 0; + } + + Rule const *heightRule() const + { + switch(appearType) + { + case AppearInstantly: + case AppearGrowHorizontally: + if(vertPolicy == Expand) return height; + break; + + case AppearGrowVertically: + if(vertPolicy == Expand) return appearSize; + break; + } + return 0; + } }; LabelWidget::LabelWidget(String const &name) : GuiWidget(name), d(new Instance(this)) @@ -715,7 +747,7 @@ void LabelWidget::setWidthPolicy(SizePolicy policy) d->horizPolicy = policy; if(policy == Expand) { - rule().setInput(Rule::Width, *d->width); + rule().setInput(Rule::Width, *d->widthRule()); } else { @@ -728,7 +760,7 @@ void LabelWidget::setHeightPolicy(SizePolicy policy) d->vertPolicy = policy; if(policy == Expand) { - rule().setInput(Rule::Height, *d->height); + rule().setInput(Rule::Height, *d->heightRule()); } else { @@ -741,22 +773,13 @@ void LabelWidget::setAppearanceAnimation(AppearanceAnimation method, TimeDelta c d->appearType = method; d->appearSpan = span; - switch(d->appearType) + if(Rule const *w = d->widthRule()) { - case AppearInstantly: - if(d->horizPolicy == Expand) rule().setInput(Rule::Width, *d->width); - if(d->vertPolicy == Expand) rule().setInput(Rule::Height, *d->height); - break; - - case AppearGrowHorizontally: - if(d->horizPolicy == Expand) rule().setInput(Rule::Width, *d->appearSize); - if(d->vertPolicy == Expand) rule().setInput(Rule::Height, *d->height); - break; - - case AppearGrowVertically: - if(d->horizPolicy == Expand) rule().setInput(Rule::Width, *d->width); - if(d->vertPolicy == Expand) rule().setInput(Rule::Height, *d->appearSize); - break; + rule().setInput(Rule::Width, *w); + } + if(Rule const *h = d->heightRule()) + { + rule().setInput(Rule::Height, *h); } } diff --git a/doomsday/libappfw/src/widgets/lineeditwidget.cpp b/doomsday/libappfw/src/widgets/lineeditwidget.cpp index 1edf43b2d9..c944bfb535 100644 --- a/doomsday/libappfw/src/widgets/lineeditwidget.cpp +++ b/doomsday/libappfw/src/widgets/lineeditwidget.cpp @@ -76,6 +76,8 @@ DENG_GUI_PIMPL(LineEditWidget) updateStyle(); uCursorColor = Vector4f(1, 1, 1, 1); + + self.set(Background(Vector4f(1, 1, 1, 1), Background::GradientFrame)); } ~Instance() @@ -113,15 +115,29 @@ DENG_GUI_PIMPL(LineEditWidget) void updateBackground() { - Background bg(style().colors().colorf("background")); - if(hovering > 0) + // If using a gradient frame, update parameters automatically. + if(self.background().type == Background::GradientFrame) { - bg.type = Background::GradientFrame; - bg.thickness = 6; - bg.color = Vector4f(1, 1, 1, .15f * hovering); - self.requestGeometry(); + Background bg; + if(!self.hasFocus()) + { + bg = Background(Background::GradientFrame, Vector4f(1, 1, 1, .15f + hovering * .2f), 6); + //style().colors().colorf("background")); + } + else + { + bg = Background(style().colors().colorf("background"), Background::GradientFrame, + Vector4f(1, 1, 1, .25f + hovering * .3f), 6); + /*if(hovering > 0) + { + bg.type = Background::GradientFrame; + bg.thickness = 6; + bg.color = Vector4f(1, 1, 1, .15f * hovering); + self.requestGeometry(); + }*/ + } + self.set(bg); } - self.set(bg); } void glInit() @@ -188,7 +204,7 @@ DENG_GUI_PIMPL(LineEditWidget) void updateHover(Vector2i const &pos) { - if(!self.hasFocus() && self.hitTest(pos)) + if(/*!self.hasFocus() && */ self.hitTest(pos)) { if(hovering.target() < 1) { diff --git a/doomsday/libappfw/src/widgets/logwidget.cpp b/doomsday/libappfw/src/widgets/logwidget.cpp index 11425e6998..49ea394bdc 100644 --- a/doomsday/libappfw/src/widgets/logwidget.cpp +++ b/doomsday/libappfw/src/widgets/logwidget.cpp @@ -463,8 +463,8 @@ public Font::RichFormat::IStyle Atlas::BackingStore | Atlas::AllowDefragment, GLTexture::maximumSize().min(Atlas::Size(4096, 2048))); - entryAtlas->audienceForReposition += this; - entryAtlas->audienceForOutOfSpace += this; + entryAtlas->audienceForReposition() += this; + entryAtlas->audienceForOutOfSpace() += this; // Simple texture for the scroll indicator. Image solidWhitePixel = Image::solidColor(Image::Color(255, 255, 255, 255), diff --git a/doomsday/libappfw/src/widgets/menuwidget.cpp b/doomsday/libappfw/src/widgets/menuwidget.cpp index 21f5d03806..8721e16fa6 100644 --- a/doomsday/libappfw/src/widgets/menuwidget.cpp +++ b/doomsday/libappfw/src/widgets/menuwidget.cpp @@ -43,7 +43,7 @@ DENG2_PIMPL(MenuWidget) class SubAction : public de::Action, DENG2_OBSERVES(Widget, Deletion) { public: - SubAction(Instance *inst, ui::Item const &parentItem) + SubAction(MenuWidget::Instance *inst, ui::Item const &parentItem) : d(inst) , _parentItem(parentItem) , _dir(ui::Right) @@ -65,7 +65,7 @@ DENG2_PIMPL(MenuWidget) // Popups need a parent. d->self.add(_widget); - _widget->audienceForDeletion += this; + _widget->audienceForDeletion() += this; _dir = openingDirection; } @@ -95,7 +95,7 @@ DENG2_PIMPL(MenuWidget) } protected: - Instance *d; + MenuWidget::Instance *d; ui::Item const &_parentItem; ui::Direction _dir; PopupWidget *_widget; @@ -107,7 +107,7 @@ DENG2_PIMPL(MenuWidget) class SubmenuAction : public SubAction { public: - SubmenuAction(Instance *inst, ui::SubmenuItem const &parentItem) + SubmenuAction(MenuWidget::Instance *inst, ui::SubmenuItem const &parentItem) : SubAction(inst, parentItem) { PopupMenuWidget *sub = new PopupMenuWidget; @@ -124,7 +124,7 @@ DENG2_PIMPL(MenuWidget) class SubwidgetAction : public SubAction { public: - SubwidgetAction(Instance *inst, ui::SubwidgetItem const &parentItem) + SubwidgetAction(MenuWidget::Instance *inst, ui::SubwidgetItem const &parentItem) : SubAction(inst, parentItem) , _item(parentItem) {} @@ -181,18 +181,18 @@ DENG2_PIMPL(MenuWidget) if(items) { // Get rid of the old context. - items->audienceForAddition -= this; - items->audienceForRemoval -= this; - items->audienceForOrderChange -= this; + items->audienceForAddition() -= this; + items->audienceForRemoval() -= this; + items->audienceForOrderChange() -= this; organizer.unsetContext(); } items = ctx; // Take new context into use. - items->audienceForAddition += this; - items->audienceForRemoval += this; - items->audienceForOrderChange += this; + items->audienceForAddition() += this; + items->audienceForRemoval() += this; + items->audienceForOrderChange() += this; organizer.setContext(*items); // recreates widgets } @@ -324,8 +324,8 @@ DENG2_PIMPL(MenuWidget) openSubs.insert(w); - w->audienceForClose += this; - w->audienceForDeletion += this; + w->audienceForClose() += this; + w->audienceForDeletion() += this; } bool isVisibleItem(Widget const *child) const diff --git a/doomsday/libappfw/src/widgets/notificationwidget.cpp b/doomsday/libappfw/src/widgets/notificationwidget.cpp index 8bd124772d..959fa859ea 100644 --- a/doomsday/libappfw/src/widgets/notificationwidget.cpp +++ b/doomsday/libappfw/src/widgets/notificationwidget.cpp @@ -52,8 +52,8 @@ DENG2_OBSERVES(Widget, ChildRemoval) uMvpMatrix("uMvpMatrix", GLUniform::Mat4), uColor ("uColor", GLUniform::Vec4) { - self.audienceForChildAddition += this; - self.audienceForChildRemoval += this; + self.audienceForChildAddition() += this; + self.audienceForChildRemoval() += this; dismissTimer.setSingleShot(true); dismissTimer.setInterval(ANIM_SPAN.asMilliSeconds()); diff --git a/doomsday/libappfw/src/widgets/panelwidget.cpp b/doomsday/libappfw/src/widgets/panelwidget.cpp index ada96f8c15..2a7317806d 100644 --- a/doomsday/libappfw/src/widgets/panelwidget.cpp +++ b/doomsday/libappfw/src/widgets/panelwidget.cpp @@ -146,7 +146,7 @@ DENG_GUI_PIMPL(PanelWidget) self.panelClosing(); - DENG2_FOR_PUBLIC_AUDIENCE(Close, i) + DENG2_FOR_PUBLIC_AUDIENCE2(Close, i) { i->panelBeingClosed(self); } @@ -156,8 +156,12 @@ DENG_GUI_PIMPL(PanelWidget) dismissTimer.start(); dismissTimer.setInterval((CLOSING_ANIM_SPAN + delay).asMilliSeconds()); } + + DENG2_PIMPL_AUDIENCE(Close) }; +DENG2_AUDIENCE_METHOD(PanelWidget, Close) + PanelWidget::PanelWidget(String const &name) : GuiWidget(name), d(new Instance(this)) { setBehavior(ChildHitClipping); diff --git a/doomsday/libappfw/src/widgets/popupmenuwidget.cpp b/doomsday/libappfw/src/widgets/popupmenuwidget.cpp index 7e69133df5..3f0d99cf84 100644 --- a/doomsday/libappfw/src/widgets/popupmenuwidget.cpp +++ b/doomsday/libappfw/src/widgets/popupmenuwidget.cpp @@ -67,12 +67,12 @@ DENG_GUI_PIMPL(PopupMenuWidget) b->setOverrideImageSize(style().fonts().font("default").height().valuei()); } - b->audienceForStateChange += this; + b->audienceForStateChange() += this; // Triggered actions close the menu. if(item.semantics().testFlag(ui::Item::ActivationClosesPopup)) { - b->audienceForTriggered += this; + b->audienceForTriggered() += this; } } } @@ -191,8 +191,8 @@ PopupMenuWidget::PopupMenuWidget(String const &name) menu().setGridSize(1, ui::Expand, 0, ui::Expand); - menu().organizer().audienceForWidgetCreation += d; - menu().organizer().audienceForWidgetUpdate += d; + menu().organizer().audienceForWidgetCreation() += d; + menu().organizer().audienceForWidgetUpdate() += d; } MenuWidget &PopupMenuWidget::menu() const diff --git a/doomsday/libappfw/src/widgets/popupwidget.cpp b/doomsday/libappfw/src/widgets/popupwidget.cpp index aec2c71794..7b820ceb5d 100644 --- a/doomsday/libappfw/src/widgets/popupwidget.cpp +++ b/doomsday/libappfw/src/widgets/popupwidget.cpp @@ -61,7 +61,7 @@ DENG_GUI_PIMPL(PopupWidget) ~Instance() { - if(realParent) realParent->audienceForDeletion -= this; + if(realParent) realParent->audienceForDeletion() -= this; releaseRef(anchorX); releaseRef(anchorY); @@ -244,6 +244,13 @@ Rule const &PopupWidget::anchorY() const return *d->anchorY; } +void PopupWidget::detachAnchor() +{ + setAnchorX(Constf(anchorX().value())); + setAnchorY(Constf(anchorY().value())); + d->updateLayout(); +} + void PopupWidget::setDeleteAfterDismissed(bool deleteAfterDismiss) { d->deleteAfterDismiss = deleteAfterDismiss; @@ -407,7 +414,7 @@ void PopupWidget::preparePanelForOpening() // Reparent the popup into the root widget, on top of everything else. d->realParent = Widget::parent(); DENG2_ASSERT(d->realParent != 0); - d->realParent->audienceForDeletion += d; + d->realParent->audienceForDeletion() += d; d->realParent->remove(*this); d->realParent->root().as().addOnTop(this); @@ -421,7 +428,7 @@ void PopupWidget::panelDismissed() // Move back to the original parent widget. if(d->realParent) { - d->realParent->audienceForDeletion -= d; + d->realParent->audienceForDeletion() -= d; } else { diff --git a/doomsday/libappfw/src/widgets/progresswidget.cpp b/doomsday/libappfw/src/widgets/progresswidget.cpp index ee39188096..84a2807cee 100644 --- a/doomsday/libappfw/src/widgets/progresswidget.cpp +++ b/doomsday/libappfw/src/widgets/progresswidget.cpp @@ -32,6 +32,7 @@ DENG_GUI_PIMPL(ProgressWidget), public Lockable Animation pos; float angle; float rotationSpeed; + bool mini; Id gearTex; DotPath colorId; DotPath shadowColorId; @@ -40,24 +41,32 @@ DENG_GUI_PIMPL(ProgressWidget), public Lockable int framesWhileAnimDone; ///< # of frames drawn while animation was already done. Instance(Public *i) - : Base(i), - mode(Indefinite), - visualRange(0, 1), - pos(0, Animation::Linear), - angle(0), - rotationSpeed(20), - colorId("progress.light.wheel"), - shadowColorId("progress.light.shadow"), - gearId("progress.gear"), - updateAt(Time::invalidTime()), - framesWhileAnimDone(0) + : Base(i) + , mode(Indefinite) + , visualRange(0, 1) + , pos(0, Animation::Linear) + , angle(0) + , rotationSpeed(20) + , mini(false) + , colorId("progress.light.wheel") + , shadowColorId("progress.light.shadow") + , gearId("progress.gear") + , updateAt(Time::invalidTime()) + , framesWhileAnimDone(0) { updateStyle(); } void updateStyle() { - self.setImageColor(style().colors().colorf(colorId) /* * Vector4f(1, 1, 1, .5f)*/); + if(mini) + { + self.setImageColor(Vector4f()); + } + else + { + self.setImageColor(style().colors().colorf(colorId)); + } } void glInit() @@ -88,18 +97,19 @@ ProgressWidget::ProgressWidget(String const &name) setTextLineAlignment(ui::AlignLeft); } -void ProgressWidget::useMiniStyle() +void ProgressWidget::useMiniStyle(DotPath const &colorId) { - // Don't use the normal wheel. - setImage(de::Image()); - + d->mini = true; + d->colorId = colorId; d->gearId = "progress.mini"; - d->colorId = "text"; + setTextColor(colorId); + setRotationSpeed(40); setImageScale(1); - setAlignment(ui::AlignCenter, LabelWidget::AlignByCombination); // Resize to the height of the default font. setOverrideImageSize(style().fonts().font("default").height().value()); + + d->updateStyle(); } void ProgressWidget::setRotationSpeed(float anglesPerSecond) @@ -131,7 +141,7 @@ void ProgressWidget::setColor(DotPath const &styleId) d->updateStyle(); } -void ProgressWidget::setShadowColor(const DotPath &styleId) +void ProgressWidget::setShadowColor(DotPath const &styleId) { d->shadowColorId = styleId; d->updateStyle(); @@ -206,7 +216,7 @@ void ProgressWidget::glMakeGeometry(DefaultVertexBuf::Builder &verts) // There is a shadow behind the wheel. float gradientThick = layout.image.width() * 2.f; - float solidThick = layout.image.width() * .53f; + float solidThick = layout.image.width() * .53f; Vector4f const shadowColor = style().colors().colorf(d->shadowColorId); verts.makeRing(layout.image.middle(), @@ -256,7 +266,7 @@ void ProgressWidget::glMakeGeometry(DefaultVertexBuf::Builder &verts) DefaultVertexBuf::Builder gear; DefaultVertexBuf::Type v; - v.rgba = style().colors().colorf(d->colorId) * Vector4f(1, 1, 1, 2); + v.rgba = style().colors().colorf(d->colorId) * Vector4f(1, 1, 1, d->mini? 1.f : 1.9f); for(int i = 0; i <= edgeCount; ++i) { diff --git a/doomsday/libappfw/src/widgets/tabwidget.cpp b/doomsday/libappfw/src/widgets/tabwidget.cpp index 809d6c77ad..a793e920f2 100644 --- a/doomsday/libappfw/src/widgets/tabwidget.cpp +++ b/doomsday/libappfw/src/widgets/tabwidget.cpp @@ -44,9 +44,9 @@ DENG2_PIMPL(TabWidget) buttons->margins().set(""); buttons->setGridSize(0, ui::Expand, 1, ui::Expand, GridLayout::ColumnFirst); - buttons->organizer().audienceForWidgetCreation += this; - buttons->items().audienceForAddition += this; - buttons->items().audienceForOrderChange += this; + buttons->organizer().audienceForWidgetCreation() += this; + buttons->items().audienceForAddition() += this; + buttons->items().audienceForOrderChange() += this; // Center the buttons inside the widget. buttons->rule() @@ -63,7 +63,7 @@ DENG2_PIMPL(TabWidget) btn.setFont("tab.label"); btn.margins().set("dialog.gap"); - btn.audienceForPress += this; + btn.audienceForPress() += this; } void buttonPressed(ButtonWidget &button) diff --git a/doomsday/libappfw/src/widgets/togglewidget.cpp b/doomsday/libappfw/src/widgets/togglewidget.cpp index 287e7e1fb9..1b494ccdde 100644 --- a/doomsday/libappfw/src/widgets/togglewidget.cpp +++ b/doomsday/libappfw/src/widgets/togglewidget.cpp @@ -112,12 +112,12 @@ DENG2_OBSERVES(ButtonWidget, Press) { self.setImage(procImage); // base class owns it - self.audienceForPress += this; + self.audienceForPress() += this; } ~Instance() { - self.audienceForPress -= this; + self.audienceForPress() -= this; } void buttonPressed(ButtonWidget &) @@ -127,8 +127,12 @@ DENG2_OBSERVES(ButtonWidget, Press) emit self.stateChangedByUser(self.toggleState()); } + + DENG2_PIMPL_AUDIENCE(Toggle) }; +DENG2_AUDIENCE_METHOD(ToggleWidget, Toggle) + ToggleWidget::ToggleWidget(String const &name) : ButtonWidget(name), d(new Instance(this)) { setTextAlignment(ui::AlignRight); @@ -144,7 +148,7 @@ void ToggleWidget::setToggleState(ToggleState state, bool notify) if(notify) { - DENG2_FOR_AUDIENCE(Toggle, i) i->toggleStateChanged(*this); + DENG2_FOR_AUDIENCE2(Toggle, i) i->toggleStateChanged(*this); } emit stateChanged(state); } diff --git a/doomsday/libappfw/src/widgets/variablechoicewidget.cpp b/doomsday/libappfw/src/widgets/variablechoicewidget.cpp index 486b3cd20a..b3c4f6b6e4 100644 --- a/doomsday/libappfw/src/widgets/variablechoicewidget.cpp +++ b/doomsday/libappfw/src/widgets/variablechoicewidget.cpp @@ -33,16 +33,16 @@ DENG2_OBSERVES(Variable, Change ) { updateFromVariable(); - var->audienceForDeletion += this; - var->audienceForChange += this; + var->audienceForDeletion() += this; + var->audienceForChange() += this; } ~Instance() { if(var) { - var->audienceForDeletion -= this; - var->audienceForChange -= this; + var->audienceForDeletion() -= this; + var->audienceForChange() -= this; } } @@ -57,9 +57,9 @@ DENG2_OBSERVES(Variable, Change ) { if(!var) return; - var->audienceForChange -= this; + var->audienceForChange() -= this; var->set(NumberValue(self.selectedItem().data().toInt())); - var->audienceForChange += this; + var->audienceForChange() += this; } void variableValueChanged(Variable &, Value const &) diff --git a/doomsday/libappfw/src/widgets/variabletogglewidget.cpp b/doomsday/libappfw/src/widgets/variabletogglewidget.cpp index 15f7f9f193..3a387598c8 100644 --- a/doomsday/libappfw/src/widgets/variabletogglewidget.cpp +++ b/doomsday/libappfw/src/widgets/variabletogglewidget.cpp @@ -39,18 +39,18 @@ DENG2_OBSERVES(ToggleWidget, Toggle ) { updateFromVariable(); - self.audienceForToggle += this; - var->audienceForDeletion += this; - var->audienceForChange += this; + self.audienceForToggle() += this; + var->audienceForDeletion() += this; + var->audienceForChange() += this; } ~Instance() { if(var) { - var->audienceForDeletion -= this; - var->audienceForChange -= this; - self.audienceForToggle -= this; + var->audienceForDeletion() -= this; + var->audienceForChange() -= this; + self.audienceForToggle() -= this; } } @@ -66,9 +66,9 @@ DENG2_OBSERVES(ToggleWidget, Toggle ) { if(!var) return; - var->audienceForChange -= this; + var->audienceForChange() -= this; var->set(self.isActive()? activeValue : inactiveValue); - var->audienceForChange += this; + var->audienceForChange() += this; } void toggleStateChanged(ToggleWidget &) diff --git a/doomsday/libdeng2/concurrency.pri b/doomsday/libdeng2/concurrency.pri index 217f2875bc..6f242088b3 100644 --- a/doomsday/libdeng2/concurrency.pri +++ b/doomsday/libdeng2/concurrency.pri @@ -1,18 +1,20 @@ -HEADERS += \ +publicHeaders(root, \ include/de/Guard \ include/de/Lockable \ include/de/ReadWriteLockable \ include/de/Task \ include/de/TaskPool \ - include/de/Waitable + include/de/Waitable \ +) -HEADERS += \ +publicHeaders(concurrency, \ include/de/concurrency/guard.h \ include/de/concurrency/lockable.h \ include/de/concurrency/readwritelockable.h \ include/de/concurrency/task.h \ include/de/concurrency/taskpool.h \ - include/de/concurrency/waitable.h + include/de/concurrency/waitable.h \ +) SOURCES += \ src/concurrency/guard.cpp \ diff --git a/doomsday/libdeng2/data.pri b/doomsday/libdeng2/data.pri index 7253bfb4b0..993d225502 100644 --- a/doomsday/libdeng2/data.pri +++ b/doomsday/libdeng2/data.pri @@ -1,4 +1,4 @@ -HEADERS += \ +publicHeaders(root, \ include/de/AccessorValue \ include/de/Archive \ include/de/ArrayValue \ @@ -32,6 +32,7 @@ HEADERS += \ include/de/LittleEndianByteOrder \ include/de/NoneValue \ include/de/NumberValue \ + include/de/Observers \ include/de/Path \ include/de/PathTree \ include/de/Property \ @@ -51,9 +52,10 @@ HEADERS += \ include/de/WaitableFIFO \ include/de/Writer \ include/de/Zeroed \ - include/de/ZipArchive + include/de/ZipArchive \ +) -HEADERS += \ +publicHeaders(data, \ include/de/data/accessorvalue.h \ include/de/data/archive.h \ include/de/data/arrayvalue.h \ @@ -105,7 +107,8 @@ HEADERS += \ include/de/data/waitablefifo.h \ include/de/data/writer.h \ include/de/data/zeroed.h \ - include/de/data/ziparchive.h + include/de/data/ziparchive.h \ +) SOURCES += \ src/data/accessorvalue.cpp \ diff --git a/doomsday/libdeng2/filesys.pri b/doomsday/libdeng2/filesys.pri index 04d779853b..e8b1fe7d03 100644 --- a/doomsday/libdeng2/filesys.pri +++ b/doomsday/libdeng2/filesys.pri @@ -1,4 +1,4 @@ -HEADERS += \ +publicHeaders(root, \ include/de/ArchiveFeed \ include/de/ArchiveEntryFile \ include/de/ByteArrayFile \ @@ -11,9 +11,10 @@ HEADERS += \ include/de/LibraryFile \ include/de/NativeFile \ include/de/NativePath \ - include/de/PackageFolder + include/de/PackageFolder \ +) -HEADERS += \ +publicHeaders(filesys, \ include/de/filesys/archivefeed.h \ include/de/filesys/archiveentryfile.h \ include/de/filesys/bytearrayfile.h \ @@ -25,7 +26,8 @@ HEADERS += \ include/de/filesys/libraryfile.h \ include/de/filesys/nativefile.h \ include/de/filesys/nativepath.h \ - include/de/filesys/packagefolder.h + include/de/filesys/packagefolder.h \ +) SOURCES += \ src/filesys/archivefeed.cpp \ diff --git a/doomsday/libdeng2/game.pri b/doomsday/libdeng2/game.pri index 16afde034c..f60cf169ac 100644 --- a/doomsday/libdeng2/game.pri +++ b/doomsday/libdeng2/game.pri @@ -1,15 +1,15 @@ -HEADERS += \ +publicHeaders(game, \ include/de/game/Game \ include/de/game/IGameStateReader \ include/de/game/SavedSession \ - include/de/game/SavedSessionRepository - -HEADERS += \ + include/de/game/SavedSessionRepository \ + \ include/de/game/game.h \ include/de/game/igamestatereader.h \ include/de/game/savedsession.h \ - include/de/game/savedsessionrepository.h - + include/de/game/savedsessionrepository.h \ +) + SOURCES += \ src/game/game.cpp \ src/game/savedsession.cpp \ diff --git a/doomsday/libdeng2/include/de/core/app.h b/doomsday/libdeng2/include/de/core/app.h index 58d346fe70..db3c5db033 100644 --- a/doomsday/libdeng2/include/de/core/app.h +++ b/doomsday/libdeng2/include/de/core/app.h @@ -69,17 +69,17 @@ class DENG2_PUBLIC App : DENG2_OBSERVES(Clock, TimeChange) /** * Notified when application startup has been fully completed. */ - DENG2_DEFINE_AUDIENCE(StartupComplete, void appStartupCompleted()) + DENG2_DEFINE_AUDIENCE2(StartupComplete, void appStartupCompleted()) /** * Notified before the current game is unloaded. */ - DENG2_DEFINE_AUDIENCE(GameUnload, void aboutToUnloadGame(game::Game const &gameBeingUnloaded)) + DENG2_DEFINE_AUDIENCE2(GameUnload, void aboutToUnloadGame(game::Game const &gameBeingUnloaded)) /** * Notified after the current game has been changed. */ - DENG2_DEFINE_AUDIENCE(GameChange, void currentGameChanged(game::Game const &newGame)) + DENG2_DEFINE_AUDIENCE2(GameChange, void currentGameChanged(game::Game const &newGame)) public: /** @@ -95,6 +95,51 @@ class DENG2_PUBLIC App : DENG2_OBSERVES(Clock, TimeChange) virtual ~App(); + /** + * Defines metadata about the application. + * + * @param appName Name of the application, as presented to humans. + * @param appVersion Version of the application. + * @param orgName Name of the author/organization. + * @param orgDomain Network domain of the author/organization. + */ + virtual void setMetadata(String const &orgName, String const &orgDomain, + String const &appName, String const &appVersion) = 0; + + /** + * Sets the path of the configuration script that will be automatically run if needed + * during application launch. The script governs the contents of the special + * persistent Config module. @see Config + * + * This method must be called before initSubsystems(). + * + * @param path Location of the @em Config.de script file. The default path of the + * script is "/modules/Config.de". + */ + void setConfigScript(Path const &path); + + /** + * Sets the name of the application. Derived classes should call this from their + * implementation of setMetadata(). + * + * @param appName Application name. Defaults to "Doomsday Engine". + */ + void setName(String const &appName); + + /** + * Sets the Unix-style home folder name. For instance, ".doomsday" could be used. + * + * @param name Name of the (usually hidden) user-specific home data folder. + */ + void setUnixHomeFolderName(String const &name); + + String unixHomeFolderName() const; + + /** + * Returns the home folder name without the possible dot in the beginning. + */ + String unixEtcFolderName() const; + /** * Sets a callback to be called when an uncaught exception occurs. */ diff --git a/doomsday/libdeng2/include/de/core/asset.h b/doomsday/libdeng2/include/de/core/asset.h index eec86d9913..ec782f5dc8 100644 --- a/doomsday/libdeng2/include/de/core/asset.h +++ b/doomsday/libdeng2/include/de/core/asset.h @@ -51,15 +51,16 @@ class DENG2_PUBLIC Asset /** * Notified whenever the state of the asset changes. */ - DENG2_DEFINE_AUDIENCE(StateChange, void assetStateChanged(Asset &)) + DENG2_DEFINE_AUDIENCE2(StateChange, void assetStateChanged(Asset &)) /** * Notified when the asset is destroyed. */ - DENG2_DEFINE_AUDIENCE(Deletion, void assetDeleted(Asset &)) + DENG2_DEFINE_AUDIENCE2(Deletion, void assetDeleted(Asset &)) public: Asset(State initialState = NotReady); + Asset(Asset const &other); virtual ~Asset(); void setState(State s); @@ -72,7 +73,7 @@ class DENG2_PUBLIC Asset virtual bool isReady() const; private: - State _state; + DENG2_PRIVATE(d) }; /** diff --git a/doomsday/libdeng2/include/de/core/clock.h b/doomsday/libdeng2/include/de/core/clock.h index 57bf0bd3a1..87e06b24a6 100644 --- a/doomsday/libdeng2/include/de/core/clock.h +++ b/doomsday/libdeng2/include/de/core/clock.h @@ -36,7 +36,7 @@ class DENG2_PUBLIC Clock * Notified whenever the time of the clock changes. The audience members * will be notified in unspecified order. */ - DENG2_DEFINE_AUDIENCE(TimeChange, void timeChanged(Clock const &)) + DENG2_DEFINE_AUDIENCE2(TimeChange, void timeChanged(Clock const &)) /** * Notified whenever the time of the clock changes. The entire priority @@ -73,8 +73,7 @@ class DENG2_PUBLIC Clock static Time const &appTime(); private: - Time _startedAt; - Time _time; + DENG2_PRIVATE(d) static Clock *_appClock; }; diff --git a/doomsday/libdeng2/include/de/core/loop.h b/doomsday/libdeng2/include/de/core/loop.h index a4932244a5..27d9df7d0f 100644 --- a/doomsday/libdeng2/include/de/core/loop.h +++ b/doomsday/libdeng2/include/de/core/loop.h @@ -39,7 +39,7 @@ class DENG2_PUBLIC Loop : public QObject /** * Audience to be notified each time the loop iterates. */ - DENG2_DEFINE_AUDIENCE(Iteration, void loopIteration()) + DENG2_DEFINE_AUDIENCE2(Iteration, void loopIteration()) public: /** diff --git a/doomsday/libdeng2/include/de/core/textapp.h b/doomsday/libdeng2/include/de/core/textapp.h index c2e908badf..920971158f 100644 --- a/doomsday/libdeng2/include/de/core/textapp.h +++ b/doomsday/libdeng2/include/de/core/textapp.h @@ -46,6 +46,9 @@ class DENG2_PUBLIC TextApp : public QCoreApplication, public App, public: TextApp(int &argc, char **argv); + void setMetadata(String const &orgName, String const &orgDomain, + String const &appName, String const &appVersion); + bool notify(QObject *receiver, QEvent *event); int execLoop(); diff --git a/doomsday/libdeng2/include/de/core/unixinfo.h b/doomsday/libdeng2/include/de/core/unixinfo.h index d4b21b0407..183e074760 100644 --- a/doomsday/libdeng2/include/de/core/unixinfo.h +++ b/doomsday/libdeng2/include/de/core/unixinfo.h @@ -53,7 +53,8 @@ class UnixInfo UnixInfo(); /** - * Returns a path preference. + * Returns a path preference. Any symbols in the path (e.g., ~) are expanded + * before the value is returned. * * @param key Path identifier. * @param value The value is returned here. diff --git a/doomsday/libdeng2/include/de/data/bank.h b/doomsday/libdeng2/include/de/data/bank.h index 84529de04e..4b1d1df0a7 100644 --- a/doomsday/libdeng2/include/de/data/bank.h +++ b/doomsday/libdeng2/include/de/data/bank.h @@ -176,13 +176,13 @@ class DENG2_PUBLIC Bank * Notified when a data item has been loaded to memory (cache level * InMemory). May be called from the background thread, if one is running. */ - DENG2_DEFINE_AUDIENCE(Load, void bankLoaded(DotPath const &path)) + DENG2_DEFINE_AUDIENCE2(Load, void bankLoaded(DotPath const &path)) /** * Notified when a data item's cache level changes (in addition to the Load * notification). */ - DENG2_DEFINE_AUDIENCE(CacheLevel, void bankCacheLevelChanged(DotPath const &path, CacheLevel level)) + DENG2_DEFINE_AUDIENCE2(CacheLevel, void bankCacheLevelChanged(DotPath const &path, CacheLevel level)) public: /** diff --git a/doomsday/libdeng2/include/de/data/escapeparser.h b/doomsday/libdeng2/include/de/data/escapeparser.h index 4dcb0cd0c9..f606bba1c6 100644 --- a/doomsday/libdeng2/include/de/data/escapeparser.h +++ b/doomsday/libdeng2/include/de/data/escapeparser.h @@ -38,7 +38,7 @@ class DENG2_PUBLIC EscapeParser * * @param range Range in the original text. */ - DENG2_DEFINE_AUDIENCE(PlainText, void handlePlainText(Rangei const &range)) + DENG2_DEFINE_AUDIENCE2(PlainText, void handlePlainText(Rangei const &range)) /** * Called during parsing when an escape sequence has been parsed. @@ -46,7 +46,7 @@ class DENG2_PUBLIC EscapeParser * * @param range Range in the original text. */ - DENG2_DEFINE_AUDIENCE(EscapeSequence, void handleEscapeSequence(Rangei const &range)) + DENG2_DEFINE_AUDIENCE2(EscapeSequence, void handleEscapeSequence(Rangei const &range)) public: EscapeParser(); diff --git a/doomsday/libdeng2/include/de/data/observers.h b/doomsday/libdeng2/include/de/data/observers.h index dee0ea4ade..a0a6bd294c 100644 --- a/doomsday/libdeng2/include/de/data/observers.h +++ b/doomsday/libdeng2/include/de/data/observers.h @@ -61,6 +61,24 @@ typedef de::Observers Name##Audience; \ extern Name##Audience audienceFor##Name; +#define DENG2_DECLARE_AUDIENCE_METHOD(Name) \ + typedef de::Observers Name##Audience; \ + Name##Audience &audienceFor##Name(); \ + Name##Audience const &audienceFor##Name() const; + +#define DENG2_AUDIENCE_METHOD(ClassName, Name) \ + ClassName::Name##Audience &ClassName::audienceFor##Name() { return d->audienceFor##Name; } \ + ClassName::Name##Audience const &ClassName::audienceFor##Name() const { return d->audienceFor##Name; } + +#define DENG2_AUDIENCE_METHOD_INLINE(Name) \ + typedef de::Observers Name##Audience; \ + Name##Audience _audienceFor##Name; \ + Name##Audience &audienceFor##Name() { return _audienceFor##Name; } \ + Name##Audience const &audienceFor##Name() const { return _audienceFor##Name; } + +#define DENG2_PIMPL_AUDIENCE(Name) \ + Name##Audience audienceFor##Name; + /** * Macro for defining an observer interface containing a single method. * @@ -73,6 +91,14 @@ DENG2_DECLARE_AUDIENCE(Name, Method) \ DENG2_AUDIENCE(Name) +#define DENG2_DEFINE_AUDIENCE2(Name, Method) \ + DENG2_DECLARE_AUDIENCE(Name, Method) \ + DENG2_DECLARE_AUDIENCE_METHOD(Name) + +#define DENG2_DEFINE_AUDIENCE_INLINE(Name, Method) \ + DENG2_DECLARE_AUDIENCE(Name, Method) \ + DENG2_AUDIENCE_METHOD_INLINE(Name) + /** * Macro that can be used in class declarations to specify which audiences the class * can belong to. @@ -101,6 +127,9 @@ #define DENG2_FOR_AUDIENCE(Name, Var) \ DENG2_FOR_EACH_OBSERVER(Name##Audience, Var, audienceFor##Name) +#define DENG2_FOR_AUDIENCE2(Name, Var) \ + DENG2_FOR_EACH_OBSERVER(Name##Audience, Var, audienceFor##Name()) + /** * Macro for looping through the public audience members from inside a private * implementation. @@ -111,12 +140,55 @@ #define DENG2_FOR_PUBLIC_AUDIENCE(Name, Var) \ DENG2_FOR_EACH_OBSERVER(Name##Audience, Var, self.audienceFor##Name) +#define DENG2_FOR_PUBLIC_AUDIENCE2(Name, Var) \ + DENG2_FOR_EACH_OBSERVER(Name##Audience, Var, self.audienceFor##Name()) + namespace de { /** * Template for observer sets. The template type should be an interface * implemented by all the observers. @ingroup data * + * @par How to use the non-pimpl audience macros + * + * These examples explain how to create an audience called "Deletion". In general, + * audience names should be nouns like this so they can be used in the form + * "audience for (something)". + * + * In a class declaration, define the audience in the @c public section of the class: + *
DENG2_DEFINE_AUDIENCE(Deletion, ...interface-function...)
+ * + * This will generate a public member variable called @c audienceForDeletion that + * can be directly manipulated by other objects. + * + * Note that because the audience is created as a public member variable, this can + * easily lead to ABI backwards compatibility issues down the road if there is + * need to make changes to the class. + * + * @par How to use the pimpl audience macros + * + * Another set of macros is provided for declaring and defining a pimpl-friendly + * audience. The caveat is that you'll need to separately declare accessor methods + * and define the audience inside the private implementation of the class. + * + * First, define the audience in the @c public section of the class: + *
DENG2_DEFINE_AUDIENCE2(Deletion, ...interface-function...)
+ * + * This works like DENG2_DEFINE_AUDIENCE, but without a public member variable. + * Instead, accessor methods are declared for accessing the audience. + * + * Then, inside the private implementation (@c Instance struct), define the audience: + *
DENG2_PIMPL_AUDIENCE(Deletion)
+ * + * Finally, define the accessor methods (for instance, just before the constructor + * of the class): + *
DENG2_AUDIENCE_METHOD(ClassName, Deletion)
+ * + * It is recommended to keep the DENG2_PIMPL_AUDIENCE and DENG2_AUDIENCE_METHOD + * macros close together in the source file for easier maintenance. The former + * could be at the end of the @c Instance struct while the latter is immediately + * following the struct. + * * @par Thread-safety * * Observers and Observers::Loop lock the observer set separately for reading diff --git a/doomsday/libdeng2/include/de/data/property.h b/doomsday/libdeng2/include/de/data/property.h index fc04d818e4..7204d4ab55 100644 --- a/doomsday/libdeng2/include/de/data/property.h +++ b/doomsday/libdeng2/include/de/data/property.h @@ -69,12 +69,12 @@ class BaseProperty void setValue(ValueType const &v) { \ if(_value == v) return; \ _value = v; \ - DENG2_FOR_AUDIENCE(Change, i) i->valueOf ## PropName ## Changed(); \ + DENG2_FOR_AUDIENCE2(Change, i) i->valueOf ## PropName ## Changed(); \ } \ PropName &operator = (ValueType const &v) { setValue(v); return *this; } \ PropName &operator += (ValueType const &v) { setValue(_value + v); return *this; } \ PropName &operator -= (ValueType const &v) { setValue(_value - v); return *this; } \ - DENG2_DEFINE_AUDIENCE(Change, void valueOf ## PropName ## Changed()) \ + DENG2_DEFINE_AUDIENCE_INLINE(Change, void valueOf ## PropName ## Changed()) \ }; #define DENG2_PROPERTY(PropName, ValueType) \ diff --git a/doomsday/libdeng2/include/de/data/record.h b/doomsday/libdeng2/include/de/data/record.h index 625733fcb4..232eae6ffd 100644 --- a/doomsday/libdeng2/include/de/data/record.h +++ b/doomsday/libdeng2/include/de/data/record.h @@ -63,7 +63,7 @@ class DENG2_PUBLIC Record : public ISerializable, public LogEntry::Arg::Base, typedef std::pair KeyValue; typedef QList List; - DENG2_DEFINE_AUDIENCE(Deletion, void recordBeingDeleted(Record &record)) + DENG2_DEFINE_AUDIENCE2(Deletion, void recordBeingDeleted(Record &record)) public: Record(); diff --git a/doomsday/libdeng2/include/de/data/variable.h b/doomsday/libdeng2/include/de/data/variable.h index 0fc5d6e962..212558cfbd 100644 --- a/doomsday/libdeng2/include/de/data/variable.h +++ b/doomsday/libdeng2/include/de/data/variable.h @@ -126,7 +126,7 @@ class DENG2_PUBLIC Variable : public ISerializable /** * Returns the name of the variable. */ - String const &name() const { return _name; } + String const &name() const; /** * Sets the value of the variable. @@ -159,12 +159,15 @@ class DENG2_PUBLIC Variable : public ISerializable */ Value &value(); + Value *valuePtr(); + Value const *valuePtr() const; + /** * Returns the value of the variable. */ template Type &value() { - Type *v = dynamic_cast(_value); + Type *v = dynamic_cast(valuePtr()); if(!v) { /// @throw TypeError Casting to Type failed. throw TypeError("Variable::value", @@ -193,7 +196,7 @@ class DENG2_PUBLIC Variable : public ISerializable */ template Type const &value() const { - Type const *v = dynamic_cast(_value); + Type const *v = dynamic_cast(valuePtr()); if(!v) { /// @throw TypeError Casting to Type failed. throw TypeError("Variable::value", @@ -265,7 +268,7 @@ class DENG2_PUBLIC Variable : public ISerializable * * @param variable Variable. */ - DENG2_DEFINE_AUDIENCE(Deletion, void variableBeingDeleted(Variable &variable)) + DENG2_DEFINE_AUDIENCE2(Deletion, void variableBeingDeleted(Variable &variable)) /** * The value of the variable has changed. @@ -273,16 +276,10 @@ class DENG2_PUBLIC Variable : public ISerializable * @param variable Variable. * @param newValue New value of the variable. */ - DENG2_DEFINE_AUDIENCE(Change, void variableValueChanged(Variable &variable, Value const &newValue)) + DENG2_DEFINE_AUDIENCE2(Change, void variableValueChanged(Variable &variable, Value const &newValue)) private: - String _name; - - /// Value of the variable. - Value *_value; - - /// Mode flags. - Flags _mode; + DENG2_PRIVATE(d) }; Q_DECLARE_OPERATORS_FOR_FLAGS(Variable::Flags) diff --git a/doomsday/libdeng2/include/de/data/ziparchive.h b/doomsday/libdeng2/include/de/data/ziparchive.h index 784dee14c6..8afc98e132 100644 --- a/doomsday/libdeng2/include/de/data/ziparchive.h +++ b/doomsday/libdeng2/include/de/data/ziparchive.h @@ -21,6 +21,7 @@ #define LIBDENG2_ZIPARCHIVE_H #include "../Archive" +#include "../NativePath" namespace de { @@ -80,8 +81,7 @@ class DENG2_PUBLIC ZipArchive : public Archive public: /** - * Determines whether a File looks like it could be accessed using - * ZipArchive. + * Determines whether a File looks like it could be accessed using ZipArchive. * * @param file File to check. * @@ -89,6 +89,15 @@ class DENG2_PUBLIC ZipArchive : public Archive */ static bool recognize(File const &file); + /** + * Determines whether a native file looks like it could be in ZIP format. + * + * @param path Native path of the file to check. + * + * @return @c true, if the file looks like an archive. + */ + static bool recognize(NativePath const &path); + protected: void readFromSource(Entry const &entry, Path const &path, IBlock &uncompressedData) const; diff --git a/doomsday/libdeng2/include/de/filesys/file.h b/doomsday/libdeng2/include/de/filesys/file.h index 630ca84d55..7982b81d52 100644 --- a/doomsday/libdeng2/include/de/filesys/file.h +++ b/doomsday/libdeng2/include/de/filesys/file.h @@ -82,7 +82,7 @@ class DENG2_PUBLIC File : public Lockable, public IIOStream * * @param file The file object being deleted. */ - DENG2_DEFINE_AUDIENCE(Deletion, void fileBeingDeleted(File const &file)) + DENG2_DEFINE_AUDIENCE2(Deletion, void fileBeingDeleted(File const &file)) /** * Stores the status of a file (size, time of last modification). @@ -187,7 +187,7 @@ class DENG2_PUBLIC File : public Lockable, public IIOStream static FileSystem &fileSystem(); /// Returns the name of the file. - String const &name() const { return _name; } + String const &name() const; /** * Returns a textual description of the file, intended only for humans. @@ -210,12 +210,12 @@ class DENG2_PUBLIC File : public Lockable, public IIOStream /** * Sets the parent folder of this file. */ - void setParent(Folder *parent) { _parent = parent; } + void setParent(Folder *parent); /** * Returns the parent folder. May be NULL. */ - Folder *parent() const { return _parent; } + Folder *parent() const; /** * Sets the origin Feed of the File. The origin feed is the feed that is able @@ -233,7 +233,7 @@ class DENG2_PUBLIC File : public Lockable, public IIOStream * Returns the origin Feed of the File. * @see setOriginFeed() */ - Feed *originFeed() const { return _originFeed; } + Feed *originFeed() const; /** * Sets the source file of this file. The source is where this file is @@ -306,10 +306,10 @@ class DENG2_PUBLIC File : public Lockable, public IIOStream virtual void setMode(Flags const &newMode); /// Returns the file information (const). - Record const &info() const { return _info; } + Record const &info() const; /// Returns the file information. - Record &info() { return _info; } + Record &info(); /** * Makes sure that the file has write access. @@ -333,27 +333,7 @@ class DENG2_PUBLIC File : public Lockable, public IIOStream explicit File(String const &name = ""); private: - /// The parent folder. - Folder *_parent; - - /// The source file (NULL for non-interpreted files). - File *_source; - - /// Feed that generated the file. This feed is called upon when the file needs - /// to be pruned. May also be NULL. - Feed *_originFeed; - - /// Name of the file. - String _name; - - /// Status of the file. - Status _status; - - /// Mode flags. - Flags _mode; - - /// File information. - Record _info; + DENG2_PRIVATE(d) }; Q_DECLARE_OPERATORS_FOR_FLAGS(File::Flags) diff --git a/doomsday/libdeng2/include/de/widgets/action.h b/doomsday/libdeng2/include/de/widgets/action.h index ee4f566c14..287a09d3b3 100644 --- a/doomsday/libdeng2/include/de/widgets/action.h +++ b/doomsday/libdeng2/include/de/widgets/action.h @@ -39,7 +39,9 @@ class DENG2_PUBLIC Action : public Counted /** * Audience to be notified when the action is triggerd. */ - DENG2_DEFINE_AUDIENCE(Triggered, void actionTriggered(Action &)) + DENG2_DEFINE_AUDIENCE2(Triggered, void actionTriggered(Action &)) + + Action(); /** * Perform the action this instance represents. Derived classes must call @@ -48,8 +50,13 @@ class DENG2_PUBLIC Action : public Counted */ virtual void trigger(); + DENG2_AS_IS_METHODS() + protected: virtual ~Action(); // ref counted, hence not publicly deletable + +private: + DENG2_PRIVATE(d) }; } // namespace de diff --git a/doomsday/libdeng2/include/de/widgets/rulerectangle.h b/doomsday/libdeng2/include/de/widgets/rulerectangle.h index 433248798c..fe4b683b79 100644 --- a/doomsday/libdeng2/include/de/widgets/rulerectangle.h +++ b/doomsday/libdeng2/include/de/widgets/rulerectangle.h @@ -54,6 +54,8 @@ class DENG2_PUBLIC RuleRectangle Rule const &bottom() const; Rule const &width() const; Rule const &height() const; + Rule const &midX() const; + Rule const &midY() const; /** * Sets one of the input rules of the rectangle. diff --git a/doomsday/libdeng2/include/de/widgets/widget.h b/doomsday/libdeng2/include/de/widgets/widget.h index eef4b5319b..ac532f5684 100644 --- a/doomsday/libdeng2/include/de/widgets/widget.h +++ b/doomsday/libdeng2/include/de/widgets/widget.h @@ -85,22 +85,22 @@ class DENG2_PUBLIC Widget /** * Notified when the widget is about to be deleted. */ - DENG2_DEFINE_AUDIENCE(Deletion, void widgetBeingDeleted(Widget &widget)) + DENG2_DEFINE_AUDIENCE2(Deletion, void widgetBeingDeleted(Widget &widget)) /** * Notified when the widget's parent changes. */ - DENG2_DEFINE_AUDIENCE(ParentChange, void widgetParentChanged(Widget &child, Widget *oldParent, Widget *newParent)) + DENG2_DEFINE_AUDIENCE2(ParentChange, void widgetParentChanged(Widget &child, Widget *oldParent, Widget *newParent)) /** * Notified when a child is added to the widget. */ - DENG2_DEFINE_AUDIENCE(ChildAddition, void widgetChildAdded(Widget &child)) + DENG2_DEFINE_AUDIENCE2(ChildAddition, void widgetChildAdded(Widget &child)) /** * Notified after a child has been removed from the widget. */ - DENG2_DEFINE_AUDIENCE(ChildRemoval, void widgetChildRemoved(Widget &child)) + DENG2_DEFINE_AUDIENCE2(ChildRemoval, void widgetChildRemoved(Widget &child)) public: Widget(String const &name = ""); diff --git a/doomsday/libdeng2/libdeng2.pro b/doomsday/libdeng2/libdeng2.pro index 5a52309817..25bf2662d8 100644 --- a/doomsday/libdeng2/libdeng2.pro +++ b/doomsday/libdeng2/libdeng2.pro @@ -68,8 +68,7 @@ include(network.pri) include(scriptsys.pri) include(widgets.pri) -# Convenience headers. -HEADERS += \ +publicHeaders(root, \ include/de/App \ include/de/Asset \ include/de/Clock \ @@ -98,14 +97,16 @@ HEADERS += \ include/de/TextStreamLogSink \ include/de/UnixInfo \ include/de/Vector \ - include/de/Version - -HEADERS += \ + include/de/Version \ + \ include/de/c_wrapper.h \ include/de/charsymbols.h \ include/de/error.h \ include/de/libdeng2.h \ include/de/math.h \ +) + +publicHeaders(core, \ include/de/core/app.h \ include/de/core/asset.h \ include/de/core/clock.h \ @@ -133,7 +134,8 @@ HEADERS += \ include/de/core/textstreamlogsink.h \ include/de/core/unixinfo.h \ include/de/core/vector.h \ - include/de/core/version.h + include/de/core/version.h \ +) # Private headers. HEADERS += \ @@ -169,11 +171,14 @@ SOURCES += \ src/core/textstreamlogsink.cpp \ src/core/unixinfo.cpp -OTHER_FILES += \ +scripts.files = \ modules/Config.de \ - modules/gui.de \ + modules/Log.de \ modules/recutil.de +OTHER_FILES += \ + $$scripts.files + # Installation --------------------------------------------------------------- macx { @@ -195,3 +200,9 @@ macx { INSTALLS += target target.path = $$DENG_LIB_DIR } + +deng_sdk { + INSTALLS *= target scripts + target.path = $$DENG_SDK_LIB_DIR + scripts.path = $$DENG_SDK_DIR/modules +} diff --git a/doomsday/libdeng2/network.pri b/doomsday/libdeng2/network.pri index 1d8a875f6c..8e6e8e14f1 100644 --- a/doomsday/libdeng2/network.pri +++ b/doomsday/libdeng2/network.pri @@ -1,4 +1,4 @@ -HEADERS += \ +publicHeaders(root, \ include/de/Address \ include/de/Beacon \ include/de/BlockPacket \ @@ -9,9 +9,10 @@ HEADERS += \ include/de/Protocol \ include/de/RecordPacket \ include/de/Socket \ - include/de/Transmitter + include/de/Transmitter \ +) -HEADERS += \ +publicHeaders(net, \ include/de/net/address.h \ include/de/net/beacon.h \ include/de/net/blockpacket.h \ @@ -22,10 +23,8 @@ HEADERS += \ include/de/net/protocol.h \ include/de/net/recordpacket.h \ include/de/net/socket.h \ - include/de/net/transmitter.h - -# Private headers. -HEADERS += + include/de/net/transmitter.h \ +) SOURCES += \ src/net/address.cpp \ diff --git a/doomsday/libdeng2/scriptsys.pri b/doomsday/libdeng2/scriptsys.pri index 2a617c5bb9..f155d556b2 100644 --- a/doomsday/libdeng2/scriptsys.pri +++ b/doomsday/libdeng2/scriptsys.pri @@ -1,4 +1,4 @@ -HEADERS += \ +publicHeaders(root, \ include/de/ArrayExpression \ include/de/AssignStatement \ include/de/BuiltInExpression \ @@ -34,9 +34,10 @@ HEADERS += \ include/de/TokenBuffer \ include/de/TokenRange \ include/de/TryStatement \ - include/de/WhileStatement + include/de/WhileStatement \ +) -HEADERS += \ +publicHeaders(scriptsys, \ include/de/scriptsys/arrayexpression.h \ include/de/scriptsys/assignstatement.h \ include/de/scriptsys/builtinexpression.h \ @@ -72,7 +73,8 @@ HEADERS += \ include/de/scriptsys/tokenbuffer.h \ include/de/scriptsys/tokenrange.h \ include/de/scriptsys/trystatement.h \ - include/de/scriptsys/whilestatement.h + include/de/scriptsys/whilestatement.h \ +) SOURCES += \ src/scriptsys/arrayexpression.cpp \ diff --git a/doomsday/libdeng2/src/core/app.cpp b/doomsday/libdeng2/src/core/app.cpp index e6fae7f8dc..f323b09762 100644 --- a/doomsday/libdeng2/src/core/app.cpp +++ b/doomsday/libdeng2/src/core/app.cpp @@ -28,6 +28,7 @@ #include "de/LogBuffer" #include "de/LogFilter" #include "de/Module" +#include "de/NativeFile" #include "de/NumberValue" #include "de/PackageFolder" #include "de/Record" @@ -50,6 +51,9 @@ DENG2_PIMPL(App) { QThread *mainThread; + /// Name of the application (metadata for humans). + String appName; + CommandLine cmdLine; LogFilter logFilter; @@ -57,6 +61,7 @@ DENG2_PIMPL(App) /// Path of the application executable. NativePath appPath; + String unixHomeFolder; NativePath cachedBasePath; NativePath cachedPluginBinaryPath; @@ -69,6 +74,7 @@ DENG2_PIMPL(App) QList systems; FileSystem fs; + QScopedPointer basePackFile; ScriptSystem scriptSys; Record appModule; @@ -79,6 +85,7 @@ DENG2_PIMPL(App) QScopedPointer unixInfo; /// The configuration. + Path configPath; Config *config; game::Game *currentGame; @@ -103,8 +110,15 @@ DENG2_PIMPL(App) GameChangeScriptAudience scriptAudienceForGameChange; Instance(Public *a, QStringList args) - : Base(a), cmdLine(args), persistentData(0), config(0), - currentGame(0), terminateFunc(0) + : Base(a) + , appName("Doomsday Engine") + , cmdLine(args) + , unixHomeFolder(".doomsday") + , persistentData(0) + , configPath("/modules/Config.de") + , config(0) + , currentGame(0) + , terminateFunc(0) { singletonApp = a; mainThread = QThread::currentThread(); @@ -122,19 +136,23 @@ DENG2_PIMPL(App) appModule.addArray("audienceForGameChange"); // game change observers scriptSys.addNativeModule("App", appModule); - self.audienceForGameChange += scriptAudienceForGameChange; + audienceForGameChange += scriptAudienceForGameChange; } ~Instance() { - clock.audienceForTimeChange -= self; + clock.audienceForTimeChange() -= self; + + if(config) + { + // Update the log filter in the persistent configuration. + Record *filter = new Record; + logFilter.write(*filter); + config->names().add("log.filter", filter); - // Update the log filter in the persistent configuration. - Record *filter = new Record; - logFilter.write(*filter); - config->names().add("log.filter", filter); + delete config; + } - delete config; Clock::setAppClock(0); } @@ -145,21 +163,34 @@ DENG2_PIMPL(App) // Initialize the built-in folders. This hooks up the default native // directories into the appropriate places in the file system. // All of these are in read-only mode. + + if(ZipArchive::recognize(self.nativeBasePath())) + { + // As a special case, if the base path points to a resource pack, + // use the contents of the pack as the root of the file system. + // The pack itself does not appear in the file system. + basePackFile.reset(new NativeFile(self.nativeBasePath().fileName(), self.nativeBasePath())); + basePackFile->setStatus(DirectoryFeed::fileStatus(self.nativeBasePath())); + fs.root().attach(new ArchiveFeed(*basePackFile)); + } + else + { #ifdef MACOSX - NativePath appDir = appPath.fileNamePath(); - binFolder.attach(new DirectoryFeed(appDir)); - fs.makeFolder("/data").attach(new DirectoryFeed(self.nativeBasePath())); - fs.makeFolder("/modules").attach(new DirectoryFeed(self.nativeBasePath() / "modules")); + NativePath appDir = appPath.fileNamePath(); + binFolder.attach(new DirectoryFeed(appDir)); + fs.makeFolder("/data").attach(new DirectoryFeed(self.nativeBasePath())); + fs.makeFolder("/modules").attach(new DirectoryFeed(self.nativeBasePath() / "modules")); #elif WIN32 - NativePath appDir = appPath.fileNamePath(); - fs.makeFolder("/data").attach(new DirectoryFeed(appDir / "..\\data")); - fs.makeFolder("/modules").attach(new DirectoryFeed(appDir / "..\\modules")); + NativePath appDir = appPath.fileNamePath(); + fs.makeFolder("/data").attach(new DirectoryFeed(appDir / "..\\data")); + fs.makeFolder("/modules").attach(new DirectoryFeed(appDir / "..\\modules")); #else // UNIX - fs.makeFolder("/data").attach(new DirectoryFeed(self.nativeBasePath() / "data")); - fs.makeFolder("/modules").attach(new DirectoryFeed(self.nativeBasePath() / "modules")); + fs.makeFolder("/data").attach(new DirectoryFeed(self.nativeBasePath() / "data")); + fs.makeFolder("/modules").attach(new DirectoryFeed(self.nativeBasePath() / "modules")); #endif + } if(allowPlugins) { @@ -167,8 +198,8 @@ DENG2_PIMPL(App) } // User's home folder. - fs.makeFolder("/home").attach(new DirectoryFeed(self.nativeHomePath(), - DirectoryFeed::AllowWrite | DirectoryFeed::CreateIfMissing)); + fs.makeFolder("/home", FS::DontInheritFeeds).attach(new DirectoryFeed(self.nativeHomePath(), + DirectoryFeed::AllowWrite | DirectoryFeed::CreateIfMissing)); // Populate the file system. fs.refresh(); @@ -221,8 +252,16 @@ DENG2_PIMPL(App) logFilter.setAllowDev(LogEntry::AllDomains, false); } } + + DENG2_PIMPL_AUDIENCE(StartupComplete) + DENG2_PIMPL_AUDIENCE(GameUnload) + DENG2_PIMPL_AUDIENCE(GameChange) }; +DENG2_AUDIENCE_METHOD(App, StartupComplete) +DENG2_AUDIENCE_METHOD(App, GameUnload) +DENG2_AUDIENCE_METHOD(App, GameChange) + App::App(NativePath const &appFilePath, QStringList args) : d(new Instance(this, args)) { @@ -265,6 +304,38 @@ App::~App() singletonApp = 0; } +void App::setConfigScript(Path const &path) +{ + d->configPath = path; +} + +void App::setName(String const &appName) +{ + d->appName = appName; +} + +void App::setUnixHomeFolderName(String const &name) +{ + d->unixHomeFolder = name; + + // Reload Unix config files. + d->unixInfo.reset(new UnixInfo); +} + +String App::unixHomeFolderName() const +{ + return d->unixHomeFolder; +} + +String App::unixEtcFolderName() const +{ + if(d->unixHomeFolder.startsWith(".")) + { + return d->unixHomeFolder.mid(1); + } + return d->unixHomeFolder; +} + void App::setTerminateFunc(void (*func)(char const *)) { d->terminateFunc = func; @@ -354,13 +425,13 @@ NativePath App::nativeHomePath() #ifdef MACOSX NativePath nativeHome = QDir::homePath(); - nativeHome = nativeHome / "Library/Application Support/Doomsday Engine/runtime"; + nativeHome = nativeHome / "Library/Application Support" / d->appName / "runtime"; #elif WIN32 NativePath nativeHome = appDataPath(); nativeHome = nativeHome / "runtime"; #else // UNIX NativePath nativeHome = QDir::homePath(); - nativeHome = nativeHome / ".doomsday/runtime"; + nativeHome = nativeHome / d->unixHomeFolder / "runtime"; #endif return (d->cachedHomePath = nativeHome); } @@ -418,7 +489,7 @@ void App::initSubsystems(SubsystemInitFlags flags) { // Recreate the persistent state data package. ZipArchive arch; - arch.add("Info", String("# Package for Doomsday's persistent state.\n").toUtf8()); + arch.add("Info", String(QString("# Package for %1's persistent state.\n").arg(d->appName)).toUtf8()); Writer(homeFolder().replaceFile("persist.pack")) << arch; homeFolder().populate(Folder::PopulateOnlyThisFolder); @@ -427,7 +498,7 @@ void App::initSubsystems(SubsystemInitFlags flags) d->persistentData = &homeFolder().locate("persist.pack").archive(); // The configuration. - d->config = new Config("/modules/Config.de"); + d->config = new Config(d->configPath); d->scriptSys.addNativeModule("Config", d->config->names()); d->config->read(); @@ -497,7 +568,7 @@ void App::initSubsystems(SubsystemInitFlags flags) d->clock.setTime(Time::currentHighPerformanceTime()); // Now we can start observing progress of time. - d->clock.audienceForTimeChange += this; + d->clock.audienceForTimeChange() += this; LOG_VERBOSE("libdeng2::App %s subsystems initialized.") << Version().asText(); } diff --git a/doomsday/libdeng2/src/core/asset.cpp b/doomsday/libdeng2/src/core/asset.cpp index 49164ed71f..b6312ebb07 100644 --- a/doomsday/libdeng2/src/core/asset.cpp +++ b/doomsday/libdeng2/src/core/asset.cpp @@ -20,21 +20,38 @@ namespace de { -Asset::Asset(State initialState) : _state(initialState) +DENG2_PIMPL_NOREF(Asset) +{ + State state; + + Instance(State s) : state(s) {} + Instance(Instance const &other) : state(other.state) {} + + DENG2_PIMPL_AUDIENCE(StateChange) + DENG2_PIMPL_AUDIENCE(Deletion) +}; + +DENG2_AUDIENCE_METHOD(Asset, StateChange) +DENG2_AUDIENCE_METHOD(Asset, Deletion) + +Asset::Asset(State initialState) : d(new Instance(initialState)) +{} + +Asset::Asset(Asset const &other) : d(new Instance(*other.d)) {} Asset::~Asset() { - DENG2_FOR_AUDIENCE(Deletion, i) i->assetDeleted(*this); + DENG2_FOR_AUDIENCE2(Deletion, i) i->assetDeleted(*this); } void Asset::setState(State s) { - State old = _state; - _state = s; - if(old != _state) + State old = d->state; + d->state = s; + if(old != d->state) { - DENG2_FOR_AUDIENCE(StateChange, i) i->assetStateChanged(*this); + DENG2_FOR_AUDIENCE2(StateChange, i) i->assetStateChanged(*this); } } @@ -45,12 +62,12 @@ void Asset::setState(bool assetReady) Asset::State Asset::state() const { - return _state; + return d->state; } bool Asset::isReady() const { - return _state == Ready; + return d->state == Ready; } //---------------------------------------------------------------------------- @@ -95,7 +112,7 @@ AssetGroup::AssetGroup() : d(new Instance) AssetGroup::~AssetGroup() { // We are about to be deleted. - audienceForStateChange.clear(); + audienceForStateChange().clear(); clear(); } @@ -109,8 +126,8 @@ void AssetGroup::clear() { DENG2_FOR_EACH(Members, i, d->deps) { - i->first->audienceForDeletion -= this; - i->first->audienceForStateChange -= this; + i->first->audienceForDeletion() -= this; + i->first->audienceForStateChange() -= this; } d->deps.clear(); @@ -120,15 +137,15 @@ void AssetGroup::clear() void AssetGroup::insert(Asset const &asset, Policy policy) { d->deps[&asset] = policy; - asset.audienceForDeletion += this; - asset.audienceForStateChange += this; + asset.audienceForDeletion() += this; + asset.audienceForStateChange() += this; d->update(*this); } void AssetGroup::remove(Asset const &asset) { - asset.audienceForDeletion -= this; - asset.audienceForStateChange -= this; + asset.audienceForDeletion() -= this; + asset.audienceForStateChange() -= this; d->deps.erase(&asset); d->update(*this); } diff --git a/doomsday/libdeng2/src/core/clock.cpp b/doomsday/libdeng2/src/core/clock.cpp index 381317c17a..0d9ac59e07 100644 --- a/doomsday/libdeng2/src/core/clock.cpp +++ b/doomsday/libdeng2/src/core/clock.cpp @@ -20,9 +20,19 @@ namespace de { +DENG2_PIMPL_NOREF(Clock) +{ + Time startedAt; + Time time; + + DENG2_PIMPL_AUDIENCE(TimeChange) +}; + +DENG2_AUDIENCE_METHOD(Clock, TimeChange) + Clock *Clock::_appClock = 0; -Clock::Clock() +Clock::Clock() : d(new Instance) {} Clock::~Clock() @@ -30,9 +40,9 @@ Clock::~Clock() void Clock::setTime(Time const ¤tTime) { - bool changed = (_time != currentTime); + bool changed = (d->time != currentTime); - _time = currentTime; + d->time = currentTime; if(changed) { @@ -40,23 +50,23 @@ void Clock::setTime(Time const ¤tTime) { i->timeChanged(*this); } - DENG2_FOR_AUDIENCE(TimeChange, i) i->timeChanged(*this); + DENG2_FOR_AUDIENCE2(TimeChange, i) i->timeChanged(*this); } } void Clock::advanceTime(TimeDelta const &span) { - setTime(_time + span); + setTime(d->time + span); } TimeDelta Clock::elapsed() const { - return _time - _startedAt; + return d->time - d->startedAt; } Time const &Clock::time() const { - return _time; + return d->time; } void Clock::setAppClock(Clock *c) diff --git a/doomsday/libdeng2/src/core/config.cpp b/doomsday/libdeng2/src/core/config.cpp index bc1e4f1422..a6e0b20d4e 100644 --- a/doomsday/libdeng2/src/core/config.cpp +++ b/doomsday/libdeng2/src/core/config.cpp @@ -112,7 +112,7 @@ void Config::read() // If script is newer, it should be rerun. if(scriptFile.status().modifiedAt > d->refuge.lastWrittenAt()) { - LOG_MSG("%s is newer than %s, rerunning the script.") + LOG_MSG("%s is newer than %s, rerunning the script") << d->configPath << d->refuge.path(); shouldRunScript = true; } @@ -122,6 +122,11 @@ void Config::read() // It is missing from persist.pack if the config hasn't been written yet. shouldRunScript = true; } + catch(IByteArray::OffsetError const &) + { + // Empty or missing serialization? + shouldRunScript = true; + } catch(Error const &error) { LOG_WARNING(error.what()); diff --git a/doomsday/libdeng2/src/core/logbuffer.cpp b/doomsday/libdeng2/src/core/logbuffer.cpp index 2bd0e6cbbe..e77aade084 100644 --- a/doomsday/libdeng2/src/core/logbuffer.cpp +++ b/doomsday/libdeng2/src/core/logbuffer.cpp @@ -230,7 +230,7 @@ void LogBuffer::setOutputFile(String const &path) if(d->outputFile) { - d->outputFile->audienceForDeletion -= this; + d->outputFile->audienceForDeletion() -= this; d->outputFile = 0; } @@ -238,7 +238,7 @@ void LogBuffer::setOutputFile(String const &path) { d->outputFile = &App::rootFolder().replaceFile(path); d->outputFile->setMode(File::Write); - d->outputFile->audienceForDeletion += this; + d->outputFile->audienceForDeletion() += this; // Add a sink for the file. d->fileLogSink = new FileLogSink(*d->outputFile); diff --git a/doomsday/libdeng2/src/core/loop.cpp b/doomsday/libdeng2/src/core/loop.cpp index 5bac69d247..456186f34c 100644 --- a/doomsday/libdeng2/src/core/loop.cpp +++ b/doomsday/libdeng2/src/core/loop.cpp @@ -50,8 +50,12 @@ DENG2_PIMPL(Loop) { loopSingleton = 0; } + + DENG2_PIMPL_AUDIENCE(Iteration) }; +DENG2_AUDIENCE_METHOD(Loop, Iteration) + Loop::Loop() : d(new Instance(this)) {} @@ -102,7 +106,7 @@ void Loop::nextLoopIteration() { if(d->running) { - DENG2_FOR_AUDIENCE(Iteration, i) i->loopIteration(); + DENG2_FOR_AUDIENCE2(Iteration, i) i->loopIteration(); } } catch(Error const &er) diff --git a/doomsday/libdeng2/src/core/monospacelogsinkformatter.cpp b/doomsday/libdeng2/src/core/monospacelogsinkformatter.cpp index d11f5b7ac8..4df429fb24 100644 --- a/doomsday/libdeng2/src/core/monospacelogsinkformatter.cpp +++ b/doomsday/libdeng2/src/core/monospacelogsinkformatter.cpp @@ -40,8 +40,8 @@ struct TabFiller TabFiller(String const &text) : hasTabs(false) { - esc.audienceForPlainText += this; - esc.audienceForEscapeSequence += this; + esc.audienceForPlainText() += this; + esc.audienceForEscapeSequence() += this; // Break the entire message into lines, excluding all escape codes // except for tabs. diff --git a/doomsday/libdeng2/src/core/textapp.cpp b/doomsday/libdeng2/src/core/textapp.cpp index 094094f6b9..55b504a335 100644 --- a/doomsday/libdeng2/src/core/textapp.cpp +++ b/doomsday/libdeng2/src/core/textapp.cpp @@ -29,7 +29,7 @@ DENG2_PIMPL(TextApp) Instance(Public *i) : Base(i) { - loop.audienceForIteration += self; + loop.audienceForIteration() += self; // In text-based apps, we can limit the loop frequency. loop.setRate(35); @@ -42,6 +42,18 @@ TextApp::TextApp(int &argc, char **argv) d(new Instance(this)) {} +void TextApp::setMetadata(String const &orgName, String const &orgDomain, + String const &appName, String const &appVersion) +{ + setName(appName); + + // Qt metadata. + setOrganizationName (orgName); + setOrganizationDomain(orgDomain); + setApplicationName (appName); + setApplicationVersion(appVersion); +} + bool TextApp::notify(QObject *receiver, QEvent *event) { try @@ -83,7 +95,7 @@ Loop &TextApp::loop() NativePath TextApp::appDataPath() const { - return NativePath(QDir::homePath()) / ".doomsday"; + return NativePath(QDir::homePath()) / unixHomeFolderName(); } void TextApp::loopIteration() diff --git a/doomsday/libdeng2/src/core/unixinfo.cpp b/doomsday/libdeng2/src/core/unixinfo.cpp index 18b1201959..8866d86202 100644 --- a/doomsday/libdeng2/src/core/unixinfo.cpp +++ b/doomsday/libdeng2/src/core/unixinfo.cpp @@ -21,6 +21,7 @@ #include "de/UnixInfo" #include "de/Info" +#include "de/App" #include namespace de { @@ -34,14 +35,14 @@ class Infos public: Infos(String fileName) : etcInfo(0), userInfo(0) { - String fn = "/etc/doomsday/" + fileName; + String fn = String("/etc") / App::app().unixEtcFolderName() / fileName; if(QFile::exists(fn)) { etcInfo = new Info; etcInfo->parseNativeFile(fn); } - fn = QDir::homePath() + "/.doomsday/" + fileName; + fn = String(QDir::homePath()) / App::app().unixHomeFolderName() / fileName; if(QFile::exists(fn)) { userInfo = new Info; @@ -109,7 +110,7 @@ bool UnixInfo::path(String const &key, NativePath &value) const String s; if(d->paths->find(key, s)) { - value = s; + value = NativePath(s).expand(); return true; } } diff --git a/doomsday/libdeng2/src/data/bank.cpp b/doomsday/libdeng2/src/data/bank.cpp index ceadea9b78..22bf2bfaa3 100644 --- a/doomsday/libdeng2/src/data/bank.cpp +++ b/doomsday/libdeng2/src/data/bank.cpp @@ -111,6 +111,7 @@ class Cache : public Lockable } // namespace internal + DENG2_PIMPL(Bank), DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via main Loop { @@ -545,7 +546,7 @@ DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via mai ~Instance() { - Loop::appLoop().audienceForIteration -= this; + Loop::appLoop().audienceForIteration() -= this; destroySerialCache(); } @@ -669,13 +670,13 @@ DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via mai notifications.put(new Notification(notif)); if(isThreaded()) { - Loop::appLoop().audienceForIteration += this; + Loop::appLoop().audienceForIteration() += this; } } void loopIteration() { - Loop::appLoop().audienceForIteration -= this; + Loop::appLoop().audienceForIteration() -= this; performNotifications(); } @@ -695,14 +696,14 @@ DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via mai switch(nt.kind) { case Notification::Loaded: - DENG2_FOR_PUBLIC_AUDIENCE(Load, i) + DENG2_FOR_PUBLIC_AUDIENCE2(Load, i) { i->bankLoaded(nt.path); } break; case Notification::CacheChanged: - DENG2_FOR_PUBLIC_AUDIENCE(CacheLevel, i) + DENG2_FOR_PUBLIC_AUDIENCE2(CacheLevel, i) { DENG2_ASSERT(nt.cache != 0); @@ -714,8 +715,14 @@ DENG2_OBSERVES(Loop, Iteration) // notifications from other threads sent via mai break; } } + + DENG2_PIMPL_AUDIENCE(Load) + DENG2_PIMPL_AUDIENCE(CacheLevel) }; +DENG2_AUDIENCE_METHOD(Bank, Load) +DENG2_AUDIENCE_METHOD(Bank, CacheLevel) + Bank::Bank(Flags const &flags, String const &hotStorageLocation) : d(new Instance(this, flags)) { diff --git a/doomsday/libdeng2/src/data/block.cpp b/doomsday/libdeng2/src/data/block.cpp index 8bfe8145b5..39c667b423 100644 --- a/doomsday/libdeng2/src/data/block.cpp +++ b/doomsday/libdeng2/src/data/block.cpp @@ -75,7 +75,8 @@ void Block::get(Offset atPos, Byte *values, Size count) const if(atPos + count > size()) { /// @throw OffsetError The accessed region of the block was out of range. - throw OffsetError("Block::get", "Out of range"); + throw OffsetError("Block::get", "Out of range " + + String("(%1[+%2] > %3)").arg(atPos).arg(count).arg(size())); } for(Offset i = atPos; count > 0; ++i, --count) diff --git a/doomsday/libdeng2/src/data/escapeparser.cpp b/doomsday/libdeng2/src/data/escapeparser.cpp index 2305015a96..afd59709b9 100644 --- a/doomsday/libdeng2/src/data/escapeparser.cpp +++ b/doomsday/libdeng2/src/data/escapeparser.cpp @@ -24,8 +24,14 @@ DENG2_PIMPL_NOREF(EscapeParser) { String original; String plain; + + DENG2_PIMPL_AUDIENCE(PlainText) + DENG2_PIMPL_AUDIENCE(EscapeSequence) }; +DENG2_AUDIENCE_METHOD(EscapeParser, PlainText) +DENG2_AUDIENCE_METHOD(EscapeParser, EscapeSequence) + EscapeParser::EscapeParser() : d(new Instance) {} @@ -44,7 +50,7 @@ void EscapeParser::parse(String const &textWithEscapes) // Empty ranges are ignored. if(range.size() > 0) { - DENG2_FOR_AUDIENCE(PlainText, i) + DENG2_FOR_AUDIENCE2(PlainText, i) { i->handlePlainText(range); } @@ -75,7 +81,7 @@ void EscapeParser::parse(String const &textWithEscapes) break; } - DENG2_FOR_AUDIENCE(EscapeSequence, i) + DENG2_FOR_AUDIENCE2(EscapeSequence, i) { i->handleEscapeSequence(Rangei(range.end + 1, range.end + escLen)); } @@ -89,7 +95,7 @@ void EscapeParser::parse(String const &textWithEscapes) range.end = d->original.size(); if(range.size() > 0) { - DENG2_FOR_AUDIENCE(PlainText, i) + DENG2_FOR_AUDIENCE2(PlainText, i) { i->handlePlainText(range); } diff --git a/doomsday/libdeng2/src/data/record.cpp b/doomsday/libdeng2/src/data/record.cpp index 95d9bc4ab7..18211a6288 100644 --- a/doomsday/libdeng2/src/data/record.cpp +++ b/doomsday/libdeng2/src/data/record.cpp @@ -170,8 +170,12 @@ DENG2_PIMPL(Record) } } } + + DENG2_PIMPL_AUDIENCE(Deletion) }; +DENG2_AUDIENCE_METHOD(Record, Deletion) + Record::Record() : d(new Instance(*this)) {} @@ -184,7 +188,7 @@ Record::Record(Record const &other) Record::~Record() { - DENG2_FOR_AUDIENCE(Deletion, i) i->recordBeingDeleted(*this); + DENG2_FOR_AUDIENCE2(Deletion, i) i->recordBeingDeleted(*this); clear(); } @@ -194,7 +198,7 @@ void Record::clear() { DENG2_FOR_EACH(Members, i, d->members) { - i.value()->audienceForDeletion -= this; + i.value()->audienceForDeletion() -= this; delete i.value(); } d->members.clear(); @@ -209,7 +213,7 @@ void Record::copyMembersFrom(Record const &other, CopyBehavior behavior) i.key().startsWith("__")) continue; Variable *var = new Variable(*i.value()); - var->audienceForDeletion += this; + var->audienceForDeletion() += this; d->members[i.key()] = var; } } @@ -254,14 +258,14 @@ Variable &Record::add(Variable *variable) // Delete the previous variable with this name. delete d->members[variable->name()]; } - var->audienceForDeletion += this; + var->audienceForDeletion() += this; d->members[variable->name()] = var.release(); return *variable; } Variable *Record::remove(Variable &variable) { - variable.audienceForDeletion -= this; + variable.audienceForDeletion() -= this; d->members.remove(variable.name()); return &variable; } @@ -565,7 +569,7 @@ void Record::operator << (Reader &from) // Observe all members for deletion. DENG2_FOR_EACH(Members, i, d->members) { - i.value()->audienceForDeletion += this; + i.value()->audienceForDeletion() += this; } } diff --git a/doomsday/libdeng2/src/data/recordvalue.cpp b/doomsday/libdeng2/src/data/recordvalue.cpp index e92bed2f15..eb5856a675 100644 --- a/doomsday/libdeng2/src/data/recordvalue.cpp +++ b/doomsday/libdeng2/src/data/recordvalue.cpp @@ -36,7 +36,7 @@ RecordValue::RecordValue(Record *record, OwnershipFlags o) if(!_ownership.testFlag(OwnsRecord)) { // If we don't own it, someone may delete the record. - _record->audienceForDeletion += this; + _record->audienceForDeletion() += this; } } @@ -44,7 +44,7 @@ RecordValue::RecordValue(Record &record) : _record(&record), _ownership(0), _oldOwnership(0) { // Someone may delete the record. - _record->audienceForDeletion += this; + _record->audienceForDeletion() += this; } RecordValue::~RecordValue() @@ -72,7 +72,7 @@ void RecordValue::setRecord(Record *record) } else if(_record) { - _record->audienceForDeletion -= this; + _record->audienceForDeletion() -= this; } _record = record; @@ -81,7 +81,7 @@ void RecordValue::setRecord(Record *record) if(_record) { // Since we don't own it, someone may delete the record. - _record->audienceForDeletion += this; + _record->audienceForDeletion() += this; } } diff --git a/doomsday/libdeng2/src/data/refuge.cpp b/doomsday/libdeng2/src/data/refuge.cpp index 94ff7a1ab4..5098724dc4 100644 --- a/doomsday/libdeng2/src/data/refuge.cpp +++ b/doomsday/libdeng2/src/data/refuge.cpp @@ -41,7 +41,7 @@ Refuge::Refuge(String const &persistentPath) : d(new Instance) catch(Error const &er) { LOG_AS("Refuge"); - LOG_RES_VERBOSE("\"%s\" could not be read: %s") << persistentPath << er.asText(); + LOGDEV_RES_MSG("\"%s\" could not be read: %s") << persistentPath << er.asText(); } } diff --git a/doomsday/libdeng2/src/data/refvalue.cpp b/doomsday/libdeng2/src/data/refvalue.cpp index ef1d8de1cb..5f2a431c02 100644 --- a/doomsday/libdeng2/src/data/refvalue.cpp +++ b/doomsday/libdeng2/src/data/refvalue.cpp @@ -27,7 +27,7 @@ RefValue::RefValue(Variable *variable) : _variable(variable) { if(_variable) { - _variable->audienceForDeletion.add(this); + _variable->audienceForDeletion() += this; } } @@ -35,7 +35,7 @@ RefValue::~RefValue() { if(_variable) { - _variable->audienceForDeletion.remove(this); + _variable->audienceForDeletion() -= this; } } diff --git a/doomsday/libdeng2/src/data/variable.cpp b/doomsday/libdeng2/src/data/variable.cpp index ef1dd483f8..92a979aebd 100644 --- a/doomsday/libdeng2/src/data/variable.cpp +++ b/doomsday/libdeng2/src/data/variable.cpp @@ -31,29 +31,67 @@ #include "de/Writer" #include "de/Log" -using namespace de; +namespace de { + +DENG2_PIMPL_NOREF(Variable) +{ + String name; + + /// Value of the variable. + Value *value; + + /// Mode flags. + Flags mode; + + Instance() : value(0) {} + + Instance(Instance const &other) + : name (other.name) + , value(other.value->duplicate()) + , mode (other.mode) + {} + + ~Instance() + { + delete value; + } + + DENG2_PIMPL_AUDIENCE(Deletion) + DENG2_PIMPL_AUDIENCE(Change) +}; + +DENG2_AUDIENCE_METHOD(Variable, Deletion) +DENG2_AUDIENCE_METHOD(Variable, Change) Variable::Variable(String const &name, Value *initial, Flags const &m) - : _name(name), _value(0), _mode(m) + : d(new Instance) { + d->name = name; + d->mode = m; + std::auto_ptr v(initial); if(!initial) { v.reset(new NoneValue); } - verifyName(_name); + verifyName(d->name); verifyValid(*v); - _value = v.release(); + d->value = v.release(); } Variable::Variable(Variable const &other) - : ISerializable(), _name(other._name), _value(other._value->duplicate()), _mode(other._mode) + : ISerializable() + , d(new Instance(*other.d)) {} Variable::~Variable() { - DENG2_FOR_AUDIENCE(Deletion, i) i->variableBeingDeleted(*this); - delete _value; + DENG2_FOR_AUDIENCE2(Deletion, i) i->variableBeingDeleted(*this); +} + +String const &Variable::name() const +{ + return d->name; } Variable &Variable::operator = (Value *v) @@ -72,11 +110,11 @@ Variable &Variable::set(Value *v) verifyWritable(*v); verifyValid(*v); - QScopedPointer oldValue(_value); // old value deleted afterwards - _value = val.take(); + QScopedPointer oldValue(d->value); // old value deleted afterwards + d->value = val.take(); // We'll only determine if actual change occurred if someone is interested. - if(!audienceForChange.isEmpty()) + if(!audienceForChange().isEmpty()) { bool notify = true; try @@ -91,7 +129,7 @@ Variable &Variable::set(Value *v) if(notify) { - DENG2_FOR_AUDIENCE(Change, i) i->variableValueChanged(*this, *_value); + DENG2_FOR_AUDIENCE2(Change, i) i->variableValueChanged(*this, *d->value); } } return *this; @@ -105,14 +143,24 @@ Variable &Variable::set(Value const &v) Value const &Variable::value() const { - DENG2_ASSERT(_value != 0); - return *_value; + DENG2_ASSERT(d->value != 0); + return *d->value; } Value &Variable::value() { - DENG2_ASSERT(_value != 0); - return *_value; + DENG2_ASSERT(d->value != 0); + return *d->value; +} + +Value *Variable::valuePtr() +{ + return d->value; +} + +Value const *Variable::valuePtr() const +{ + return d->value; } Record const &Variable::valueAsRecord() const @@ -142,30 +190,30 @@ Variable::operator ddouble () const Variable::Flags Variable::mode() const { - return _mode; + return d->mode; } void Variable::setMode(Flags const &flags, FlagOp operation) { - applyFlagOperation(_mode, flags, operation); + applyFlagOperation(d->mode, flags, operation); } Variable &Variable::setReadOnly() { - _mode |= ReadOnly; + d->mode |= ReadOnly; return *this; } bool Variable::isValid(Value const &v) const { /// @todo Make sure this actually works and add func, record, ref. - if((dynamic_cast(&v) && !_mode.testFlag(AllowNone)) || - (dynamic_cast(&v) && !_mode.testFlag(AllowNumber)) || - (dynamic_cast(&v) && !_mode.testFlag(AllowText)) || - (dynamic_cast(&v) && !_mode.testFlag(AllowArray)) || - (dynamic_cast(&v) && !_mode.testFlag(AllowDictionary)) || - (dynamic_cast(&v) && !_mode.testFlag(AllowBlock)) || - (dynamic_cast(&v) && !_mode.testFlag(AllowTime))) + if((dynamic_cast(&v) && !d->mode.testFlag(AllowNone)) || + (dynamic_cast(&v) && !d->mode.testFlag(AllowNumber)) || + (dynamic_cast(&v) && !d->mode.testFlag(AllowText)) || + (dynamic_cast(&v) && !d->mode.testFlag(AllowArray)) || + (dynamic_cast(&v) && !d->mode.testFlag(AllowDictionary)) || + (dynamic_cast(&v) && !d->mode.testFlag(AllowBlock)) || + (dynamic_cast(&v) && !d->mode.testFlag(AllowTime))) { return false; } @@ -179,16 +227,16 @@ void Variable::verifyValid(Value const &v) const { /// @throw InvalidError Value @a v is not allowed by the variable. throw InvalidError("Variable::verifyValid", - "Value type is not allowed by the variable '" + _name + "'"); + "Value type is not allowed by the variable '" + d->name + "'"); } } void Variable::verifyWritable(Value const &attemptedNewValue) { - if(_mode & ReadOnly) + if(d->mode & ReadOnly) { - if(_value && typeid(*_value) == typeid(attemptedNewValue) && - !_value->compare(attemptedNewValue)) + if(d->value && typeid(*d->value) == typeid(attemptedNewValue) && + !d->value->compare(attemptedNewValue)) { // This is ok: the value doesn't change. return; @@ -196,7 +244,7 @@ void Variable::verifyWritable(Value const &attemptedNewValue) /// @throw ReadOnlyError The variable is in read-only mode. throw ReadOnlyError("Variable::verifyWritable", - "Variable '" + _name + "' is in read-only mode"); + "Variable '" + d->name + "' is in read-only mode"); } } @@ -211,26 +259,28 @@ void Variable::verifyName(String const &s) void Variable::operator >> (Writer &to) const { - if(!_mode.testFlag(NoSerialize)) + if(!d->mode.testFlag(NoSerialize)) { - to << _name << duint32(_mode) << *_value; + to << d->name << duint32(d->mode) << *d->value; } } void Variable::operator << (Reader &from) { duint32 modeFlags = 0; - from >> _name >> modeFlags; - _mode = Flags(modeFlags); - delete _value; + from >> d->name >> modeFlags; + d->mode = Flags(modeFlags); + delete d->value; try { - _value = Value::constructFrom(from); + d->value = Value::constructFrom(from); } catch(Error const &) { // Always need to have a value. - _value = new NoneValue(); + d->value = new NoneValue(); throw; } } + +} // namespace de diff --git a/doomsday/libdeng2/src/data/ziparchive.cpp b/doomsday/libdeng2/src/data/ziparchive.cpp index 270bca8d9f..0af8b4e531 100644 --- a/doomsday/libdeng2/src/data/ziparchive.cpp +++ b/doomsday/libdeng2/src/data/ziparchive.cpp @@ -606,14 +606,23 @@ void ZipArchive::operator >> (Writer &to) const to.seek(writer.offset()); } -bool ZipArchive::recognize(File const &file) +static bool recognizeZipExtension(String const &ext) { - // For now, just check the name. - String ext = file.name().fileNameExtension().lower(); return (ext == ".pack" || ext == ".demo" || ext == ".save" || ext == ".addon" || ext == ".box" || ext == ".pk3" || ext == ".zip"); } +bool ZipArchive::recognize(File const &file) +{ + // For now, just check the name. + return recognizeZipExtension(file.name().fileNameExtension().lower()); +} + +bool ZipArchive::recognize(NativePath const &path) +{ + return recognizeZipExtension(path.toString().fileNameExtension().lower()); +} + void ZipArchive::ZipEntry::update() { if(data) diff --git a/doomsday/libdeng2/src/filesys/archiveentryfile.cpp b/doomsday/libdeng2/src/filesys/archiveentryfile.cpp index a348b8ab1f..e6ee375dd4 100644 --- a/doomsday/libdeng2/src/filesys/archiveentryfile.cpp +++ b/doomsday/libdeng2/src/filesys/archiveentryfile.cpp @@ -32,8 +32,8 @@ ArchiveEntryFile::~ArchiveEntryFile() { DENG2_GUARD(this); - DENG2_FOR_AUDIENCE(Deletion, i) i->fileBeingDeleted(*this); - audienceForDeletion.clear(); + DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this); + audienceForDeletion().clear(); deindex(); } diff --git a/doomsday/libdeng2/src/filesys/archivefeed.cpp b/doomsday/libdeng2/src/filesys/archivefeed.cpp index 5757ae626a..b6227402e8 100644 --- a/doomsday/libdeng2/src/filesys/archivefeed.cpp +++ b/doomsday/libdeng2/src/filesys/archivefeed.cpp @@ -29,9 +29,10 @@ namespace de { DENG2_PIMPL(ArchiveFeed) +, DENG2_OBSERVES(File, Deletion) { /// File where the archive is stored (in a serialized format). - File &file; + File *file; /// The archive can be physically stored here, as Archive doesn't make a /// copy of the buffer. @@ -45,9 +46,9 @@ DENG2_PIMPL(ArchiveFeed) /// The feed whose archive this feed is using. ArchiveFeed *parentFeed; - Instance(Public *feed, File &f) : Base(feed), file(f), arch(0), parentFeed(0) + Instance(Public *feed, File &f) : Base(feed), file(&f), arch(0), parentFeed(0) { - /// @todo Observe the file for deletion. + file->audienceForDeletion() += this; // If the file happens to be a byte array file, we can use it // directly to store the Archive. @@ -73,29 +74,53 @@ DENG2_PIMPL(ArchiveFeed) Instance(Public *feed, ArchiveFeed &parentFeed, String const &path) : Base(feed), file(parentFeed.d->file), arch(0), basePath(path), parentFeed(&parentFeed) - {} + { + file->audienceForDeletion() += this; + } ~Instance() { + if(file) + { + file->audienceForDeletion() -= this; + } + if(arch) { - // If modified, the archive is written back to the file. - if(arch->modified()) - { - LOG_RES_MSG("Updating archive in ") << file.description(); + writeIfModified(); + delete arch; + } + } - // Make sure we have either a compressed or uncompressed version of - // each entry in memory before destroying the source file. - arch->cache(); + void writeIfModified() + { + if(!file || !arch) return; - file.clear(); - Writer(file) << *arch; - } - else - { - LOG_RES_VERBOSE("Not updating archive in %s (not changed)") << file.description(); - } - delete arch; + // If modified, the archive is written back to the file. + if(arch->modified()) + { + LOG_RES_MSG("Updating archive in ") << file->description(); + + // Make sure we have either a compressed or uncompressed version of + // each entry in memory before destroying the source file. + arch->cache(); + + file->clear(); + Writer(*file) << *arch; + } + else + { + LOG_RES_VERBOSE("Not updating archive in %s (not changed)") << file->description(); + } + } + + void fileBeingDeleted(File const &deleted) + { + if(file == &deleted) + { + // Ensure that changes are saved and detach from the file. + writeIfModified(); + file = 0; } } @@ -125,18 +150,19 @@ DENG2_PIMPL(ArchiveFeed) String entry = basePath / *i; std::auto_ptr archFile(new ArchiveEntryFile(*i, archive(), entry)); + // Use the status of the entry within the archive. archFile->setStatus(archive().entryStatus(entry)); // Create a new file that accesses this feed's archive and interpret the contents. - File *file = folder.fileSystem().interpret(archFile.release()); - folder.add(file); + File *f = folder.fileSystem().interpret(archFile.release()); + folder.add(f); // We will decide on pruning this. - file->setOriginFeed(&self); + f->setOriginFeed(&self); // Include the file in the main index. - folder.fileSystem().index(*file); + folder.fileSystem().index(*f); } // Also populate subfolders. @@ -165,7 +191,11 @@ ArchiveFeed::~ArchiveFeed() String ArchiveFeed::description() const { - return "archive in " + d->file.description(); + if(d->file) + { + return "archive in " + d->file->description(); + } + return "archive in (no file)"; } void ArchiveFeed::populate(Folder &folder) diff --git a/doomsday/libdeng2/src/filesys/file.cpp b/doomsday/libdeng2/src/filesys/file.cpp index 79dda257b4..96564b2671 100644 --- a/doomsday/libdeng2/src/filesys/file.cpp +++ b/doomsday/libdeng2/src/filesys/file.cpp @@ -26,38 +26,71 @@ #include "de/NumberValue" #include "de/Guard" -using namespace de; +namespace de { -File::File(String const &fileName) - : _parent(0), _originFeed(0), _name(fileName) +DENG2_PIMPL_NOREF(File) { - _source = this; + /// The parent folder. + Folder *parent; + + /// The source file (NULL for non-interpreted files). + File *source; + + /// Feed that generated the file. This feed is called upon when the file needs + /// to be pruned. May also be NULL. + Feed *originFeed; + + /// Name of the file. + String name; + + /// Status of the file. + Status status; + + /// Mode flags. + Flags mode; + + /// File information. + Record info; + + Instance(String const &fileName) + : parent(0) + , originFeed(0) + , name(fileName) {} + + DENG2_PIMPL_AUDIENCE(Deletion) +}; + +DENG2_AUDIENCE_METHOD(File, Deletion) + +File::File(String const &fileName) : d(new Instance(fileName)) +{ + d->source = this; // Create the default set of info variables common to all files. - _info.add(new Variable("name", new Accessor(*this, Accessor::NAME), Accessor::VARIABLE_MODE)); - _info.add(new Variable("path", new Accessor(*this, Accessor::PATH), Accessor::VARIABLE_MODE)); - _info.add(new Variable("type", new Accessor(*this, Accessor::TYPE), Accessor::VARIABLE_MODE)); - _info.add(new Variable("size", new Accessor(*this, Accessor::SIZE), Accessor::VARIABLE_MODE)); - _info.add(new Variable("modifiedAt", new Accessor(*this, Accessor::MODIFIED_AT), Accessor::VARIABLE_MODE)); + d->info.add(new Variable("name", new Accessor(*this, Accessor::NAME), Accessor::VARIABLE_MODE)); + d->info.add(new Variable("path", new Accessor(*this, Accessor::PATH), Accessor::VARIABLE_MODE)); + d->info.add(new Variable("type", new Accessor(*this, Accessor::TYPE), Accessor::VARIABLE_MODE)); + d->info.add(new Variable("size", new Accessor(*this, Accessor::SIZE), Accessor::VARIABLE_MODE)); + d->info.add(new Variable("modifiedAt", new Accessor(*this, Accessor::MODIFIED_AT), Accessor::VARIABLE_MODE)); } File::~File() { DENG2_GUARD(this); - DENG2_FOR_AUDIENCE(Deletion, i) i->fileBeingDeleted(*this); + DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this); flush(); - if(_source != this) + if(d->source != this) { // If we own a source, get rid of it. - delete _source; - _source = 0; + delete d->source; + d->source = 0; } - if(_parent) + if(d->parent) { // Remove from parent folder. - _parent->remove(this); + d->parent->remove(this); } deindex(); } @@ -80,6 +113,11 @@ FileSystem &File::fileSystem() return DENG2_APP->fileSystem(); } +String const &File::name() const +{ + return d->name; +} + String File::description() const { DENG2_GUARD(this); @@ -109,11 +147,26 @@ String File::describe() const return "abstract File"; } +void File::setParent(Folder *parent) +{ + d->parent = parent; +} + +Folder *File::parent() const +{ + return d->parent; +} + void File::setOriginFeed(Feed *feed) { DENG2_GUARD(this); - _originFeed = feed; + d->originFeed = feed; +} + +Feed *File::originFeed() const +{ + return d->originFeed; } String const File::path() const @@ -121,7 +174,7 @@ String const File::path() const DENG2_GUARD(this); String thePath = name(); - for(Folder *i = _parent; i; i = i->_parent) + for(Folder *i = d->parent; i; i = i->d->parent) { thePath = i->name() / thePath; } @@ -132,34 +185,34 @@ void File::setSource(File *source) { DENG2_GUARD(this); - if(_source != this) + if(d->source != this) { // Delete the old source. - delete _source; + delete d->source; } - _source = source; + d->source = source; } File const *File::source() const { DENG2_GUARD(this); - if(_source != this) + if(d->source != this) { - return _source->source(); + return d->source->source(); } - return _source; + return d->source; } File *File::source() { DENG2_GUARD(this); - if(_source != this) + if(d->source != this) { - return _source->source(); + return d->source->source(); } - return _source; + return d->source; } void File::setStatus(Status const &status) @@ -167,13 +220,13 @@ void File::setStatus(Status const &status) DENG2_GUARD(this); // The source file status is the official one. - if(this != _source) + if(this != d->source) { - _source->setStatus(status); + d->source->setStatus(status); } else { - _status = status; + d->status = status; } } @@ -181,36 +234,46 @@ File::Status const &File::status() const { DENG2_GUARD(this); - if(this != _source) + if(this != d->source) { - return _source->status(); + return d->source->status(); } - return _status; + return d->status; } void File::setMode(Flags const &newMode) { DENG2_GUARD(this); - if(this != _source) + if(this != d->source) { - _source->setMode(newMode); + d->source->setMode(newMode); } else { - _mode = newMode; + d->mode = newMode; } } +Record const &File::info() const +{ + return d->info; +} + +Record &File::info() +{ + return d->info; +} + File::Flags const &File::mode() const { DENG2_GUARD(this); - if(this != _source) + if(this != d->source) { - return _source->mode(); + return d->source->mode(); } - return _mode; + return d->mode; } void File::verifyWriteAccess() @@ -289,3 +352,5 @@ Value *File::Accessor::duplicateContent() const } return new TextValue(*this); } + +} // namespace de diff --git a/doomsday/libdeng2/src/filesys/folder.cpp b/doomsday/libdeng2/src/filesys/folder.cpp index bfd4b25340..1475617a88 100644 --- a/doomsday/libdeng2/src/filesys/folder.cpp +++ b/doomsday/libdeng2/src/filesys/folder.cpp @@ -40,8 +40,8 @@ Folder::~Folder() { DENG2_GUARD(this); - DENG2_FOR_AUDIENCE(Deletion, i) i->fileBeingDeleted(*this); - audienceForDeletion.clear(); + DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this); + audienceForDeletion().clear(); deindex(); diff --git a/doomsday/libdeng2/src/filesys/libraryfile.cpp b/doomsday/libdeng2/src/filesys/libraryfile.cpp index c069b9533a..3595557a73 100644 --- a/doomsday/libdeng2/src/filesys/libraryfile.cpp +++ b/doomsday/libdeng2/src/filesys/libraryfile.cpp @@ -34,8 +34,8 @@ LibraryFile::LibraryFile(File *source) LibraryFile::~LibraryFile() { - DENG2_FOR_AUDIENCE(Deletion, i) i->fileBeingDeleted(*this); - audienceForDeletion.clear(); + DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this); + audienceForDeletion().clear(); deindex(); delete _library; diff --git a/doomsday/libdeng2/src/filesys/nativefile.cpp b/doomsday/libdeng2/src/filesys/nativefile.cpp index de5baa25a6..5df855fdc9 100644 --- a/doomsday/libdeng2/src/filesys/nativefile.cpp +++ b/doomsday/libdeng2/src/filesys/nativefile.cpp @@ -31,8 +31,8 @@ NativeFile::~NativeFile() { DENG2_GUARD(this); - DENG2_FOR_AUDIENCE(Deletion, i) i->fileBeingDeleted(*this); - audienceForDeletion.clear(); + DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this); + audienceForDeletion().clear(); close(); deindex(); @@ -98,7 +98,8 @@ void NativeFile::get(Offset at, Byte *values, Size count) const { /// @throw IByteArray::OffsetError The region specified for reading extends /// beyond the bounds of the file. - throw OffsetError("NativeFile::get", "Cannot read past end of file"); + throw OffsetError("NativeFile::get", description() + ": cannot read past end of file " + + String("(%1[+%2] > %3)").arg(at).arg(count).arg(size())); } in.seek(at); in.read(reinterpret_cast(values), count); diff --git a/doomsday/libdeng2/src/filesys/packagefolder.cpp b/doomsday/libdeng2/src/filesys/packagefolder.cpp index f771fcbc6d..7595a92c7f 100644 --- a/doomsday/libdeng2/src/filesys/packagefolder.cpp +++ b/doomsday/libdeng2/src/filesys/packagefolder.cpp @@ -29,8 +29,8 @@ PackageFolder::PackageFolder(File &sourceArchiveFile, String const &name) : Fold PackageFolder::~PackageFolder() { - DENG2_FOR_AUDIENCE(Deletion, i) i->fileBeingDeleted(*this); - audienceForDeletion.clear(); + DENG2_FOR_AUDIENCE2(Deletion, i) i->fileBeingDeleted(*this); + audienceForDeletion().clear(); deindex(); } diff --git a/doomsday/libdeng2/src/game/savedsessionrepository.cpp b/doomsday/libdeng2/src/game/savedsessionrepository.cpp index f6b5873523..bd1ef51002 100644 --- a/doomsday/libdeng2/src/game/savedsessionrepository.cpp +++ b/doomsday/libdeng2/src/game/savedsessionrepository.cpp @@ -56,14 +56,14 @@ DENG2_PIMPL(SavedSessionRepository) : Base(i) , folder(0) { - App::app().audienceForGameUnload += this; - //App::app().audienceForGameChange += this; + App::app().audienceForGameUnload() += this; + //App::app().audienceForGameChange() += this; } ~Instance() { - App::app().audienceForGameUnload += this; - //App::app().audienceForGameChange += this; + App::app().audienceForGameUnload() += this; + //App::app().audienceForGameChange() += this; clearSessions(); } diff --git a/doomsday/libdeng2/src/scriptsys/function.cpp b/doomsday/libdeng2/src/scriptsys/function.cpp index 787cbadf5f..c59a78d21c 100644 --- a/doomsday/libdeng2/src/scriptsys/function.cpp +++ b/doomsday/libdeng2/src/scriptsys/function.cpp @@ -96,7 +96,7 @@ Function::~Function() if(d->globals) { // Stop observing the namespace. - d->globals->audienceForDeletion.remove(this); + d->globals->audienceForDeletion() -= this; } } @@ -230,7 +230,7 @@ void Function::setGlobals(Record *globals) if(!d->globals) { d->globals = globals; - d->globals->audienceForDeletion.add(this); + d->globals->audienceForDeletion() += this; } /* else if(d->globals != globals) diff --git a/doomsday/libdeng2/src/scriptsys/scriptsystem.cpp b/doomsday/libdeng2/src/scriptsys/scriptsystem.cpp index 482c7acc45..c11789a4c5 100644 --- a/doomsday/libdeng2/src/scriptsys/scriptsystem.cpp +++ b/doomsday/libdeng2/src/scriptsys/scriptsystem.cpp @@ -85,14 +85,14 @@ DENG2_PIMPL(ScriptSystem), DENG2_OBSERVES(Record, Deletion) DENG2_FOR_EACH(NativeModules, i, nativeModules) { - i.value()->audienceForDeletion -= this; + i.value()->audienceForDeletion() -= this; } } void addNativeModule(String const &name, Record &module) { nativeModules.insert(name, &module); // not owned - module.audienceForDeletion += this; + module.audienceForDeletion() += this; } void recordBeingDeleted(Record &record) diff --git a/doomsday/libdeng2/src/widgets/action.cpp b/doomsday/libdeng2/src/widgets/action.cpp index 57b0ba3d4c..c5da823433 100644 --- a/doomsday/libdeng2/src/widgets/action.cpp +++ b/doomsday/libdeng2/src/widgets/action.cpp @@ -20,12 +20,22 @@ namespace de { +DENG2_PIMPL_NOREF(Action) +{ + DENG2_PIMPL_AUDIENCE(Triggered) +}; + +DENG2_AUDIENCE_METHOD(Action, Triggered) + +Action::Action() : d(new Instance) +{} + Action::~Action() {} void Action::trigger() { - DENG2_FOR_AUDIENCE(Triggered, i) + DENG2_FOR_AUDIENCE2(Triggered, i) { i->actionTriggered(*this); } diff --git a/doomsday/libdeng2/src/widgets/rulerectangle.cpp b/doomsday/libdeng2/src/widgets/rulerectangle.cpp index 21379dc52f..859b7a8dfd 100644 --- a/doomsday/libdeng2/src/widgets/rulerectangle.cpp +++ b/doomsday/libdeng2/src/widgets/rulerectangle.cpp @@ -47,6 +47,8 @@ DENG2_PIMPL(RuleRectangle) // The output rules. IndirectRule *outputRules[MAX_OUTPUT_RULES]; + Rule *midX; + Rule *midY; Instance(Public *i) : Base(i) { @@ -61,11 +63,16 @@ DENG2_PIMPL(RuleRectangle) outputRules[i] = new IndirectRule; } + midX = holdRef(*outputRules[OutLeft] + *outputRules[OutWidth] / 2); + midY = holdRef(*outputRules[OutTop] + *outputRules[OutHeight] / 2); + debugName = QString("0x%1").arg(dintptr(thisPublic), 0, 16); } ~Instance() { + releaseRef(midX); + releaseRef(midY); releaseRef(normalizedAnchorX); releaseRef(normalizedAnchorY); @@ -241,6 +248,16 @@ Rule const &RuleRectangle::height() const return *d->outputRules[Instance::OutHeight]; } +Rule const &RuleRectangle::midX() const +{ + return *d->midX; +} + +Rule const &RuleRectangle::midY() const +{ + return *d->midY; +} + RuleRectangle &RuleRectangle::setInput(Rule::Semantic inputRule, Rule const &rule) { d->setInputRule(inputRule, rule); diff --git a/doomsday/libdeng2/src/widgets/widget.cpp b/doomsday/libdeng2/src/widgets/widget.cpp index 0e0c3998c3..3fa0d0099a 100644 --- a/doomsday/libdeng2/src/widgets/widget.cpp +++ b/doomsday/libdeng2/src/widgets/widget.cpp @@ -60,8 +60,18 @@ DENG2_PIMPL(Widget) } index.clear(); } + + DENG2_PIMPL_AUDIENCE(Deletion) + DENG2_PIMPL_AUDIENCE(ParentChange) + DENG2_PIMPL_AUDIENCE(ChildAddition) + DENG2_PIMPL_AUDIENCE(ChildRemoval) }; +DENG2_AUDIENCE_METHOD(Widget, Deletion) +DENG2_AUDIENCE_METHOD(Widget, ParentChange) +DENG2_AUDIENCE_METHOD(Widget, ChildAddition) +DENG2_AUDIENCE_METHOD(Widget, ChildRemoval) + Widget::Widget(String const &name) : d(new Instance(this, name)) {} @@ -72,7 +82,7 @@ Widget::~Widget() root().setFocus(0); } - audienceForParentChange.clear(); + audienceForParentChange().clear(); // Remove from parent automatically. if(d->parent) @@ -81,7 +91,7 @@ Widget::~Widget() } // Notify everyone else. - DENG2_FOR_AUDIENCE(Deletion, i) i->widgetBeingDeleted(*this); + DENG2_FOR_AUDIENCE2(Deletion, i) i->widgetBeingDeleted(*this); } Id Widget::id() const @@ -272,11 +282,11 @@ Widget &Widget::add(Widget *child) } // Notify. - DENG2_FOR_AUDIENCE(ChildAddition, i) + DENG2_FOR_AUDIENCE2(ChildAddition, i) { i->widgetChildAdded(*child); } - DENG2_FOR_EACH_OBSERVER(ParentChangeAudience, i, child->audienceForParentChange) + DENG2_FOR_EACH_OBSERVER(ParentChangeAudience, i, child->audienceForParentChange()) { i->widgetParentChanged(*child, 0, this); } @@ -309,11 +319,11 @@ Widget *Widget::remove(Widget &child) } // Notify. - DENG2_FOR_AUDIENCE(ChildRemoval, i) + DENG2_FOR_AUDIENCE2(ChildRemoval, i) { i->widgetChildRemoved(child); } - DENG2_FOR_EACH_OBSERVER(ParentChangeAudience, i, child.audienceForParentChange) + DENG2_FOR_EACH_OBSERVER(ParentChangeAudience, i, child.audienceForParentChange()) { i->widgetParentChanged(child, this, 0); } diff --git a/doomsday/libdeng2/widgets.pri b/doomsday/libdeng2/widgets.pri index 25eca5b3f3..03cba00184 100644 --- a/doomsday/libdeng2/widgets.pri +++ b/doomsday/libdeng2/widgets.pri @@ -1,17 +1,19 @@ -HEADERS += \ +publicHeaders(root, \ include/de/Action \ include/de/Animation \ include/de/AnimationVector \ include/de/ConstantRule \ include/de/IndirectRule \ include/de/OperatorRule \ + include/de/Rule \ include/de/RuleBank \ include/de/RuleRectangle \ include/de/RootWidget \ include/de/ScalarRule \ - include/de/Widget + include/de/Widget \ +) -HEADERS += \ +publicHeaders(widgets, \ include/de/widgets/action.h \ include/de/widgets/animation.h \ include/de/widgets/animationvector.h \ @@ -24,7 +26,8 @@ HEADERS += \ include/de/widgets/rulebank.h \ include/de/widgets/rules.h \ include/de/widgets/scalarrule.h \ - include/de/widgets/widget.h + include/de/widgets/widget.h \ +) SOURCES += \ src/widgets/action.cpp \ diff --git a/doomsday/libgui/include/de/gui/atlas.h b/doomsday/libgui/include/de/gui/atlas.h index 42705e9715..473a09138c 100644 --- a/doomsday/libgui/include/de/gui/atlas.h +++ b/doomsday/libgui/include/de/gui/atlas.h @@ -109,13 +109,13 @@ class LIBGUI_PUBLIC Atlas : public Lockable * repositioned for some reasons (e.g., defragmentation). Normally once * allocated, content will remain at its initial place. */ - DENG2_DEFINE_AUDIENCE(Reposition, void atlasContentRepositioned(Atlas &)) + DENG2_DEFINE_AUDIENCE2(Reposition, void atlasContentRepositioned(Atlas &)) /** * Audience that will be notified when an allocation fails due to the atlas * being so full that there is no room for the new image. */ - DENG2_DEFINE_AUDIENCE(OutOfSpace, void atlasOutOfSpace(Atlas &)) + DENG2_DEFINE_AUDIENCE2(OutOfSpace, void atlasOutOfSpace(Atlas &)) public: /** diff --git a/doomsday/libgui/include/de/gui/canvas.h b/doomsday/libgui/include/de/gui/canvas.h index df88bf08fc..480b8da46c 100644 --- a/doomsday/libgui/include/de/gui/canvas.h +++ b/doomsday/libgui/include/de/gui/canvas.h @@ -57,29 +57,29 @@ class LIBGUI_PUBLIC Canvas : public QGLWidget, public KeyEventSource, public Mou * screen. Note that the notification comes straight from the event loop * (timer signal) instead of during a paint event. */ - DENG2_DEFINE_AUDIENCE(GLReady, void canvasGLReady(Canvas &)) + DENG2_DEFINE_AUDIENCE2(GLReady, void canvasGLReady(Canvas &)) /** * Notified when the canvas's GL state needs to be initialized. This is * called immediately before drawing the contents of the canvas for the * first time (during a paint event). */ - DENG2_DEFINE_AUDIENCE(GLInit, void canvasGLInit(Canvas &)) + DENG2_DEFINE_AUDIENCE2(GLInit, void canvasGLInit(Canvas &)) /** * Notified when a canvas's size has changed. */ - DENG2_DEFINE_AUDIENCE(GLResize, void canvasGLResized(Canvas &)) + DENG2_DEFINE_AUDIENCE2(GLResize, void canvasGLResized(Canvas &)) /** * Notified when drawing of the canvas contents has been requested. */ - DENG2_DEFINE_AUDIENCE(GLDraw, void canvasGLDraw(Canvas &)) + DENG2_DEFINE_AUDIENCE2(GLDraw, void canvasGLDraw(Canvas &)) /** * Notified when the canvas gains or loses input focus. */ - DENG2_DEFINE_AUDIENCE(FocusChange, void canvasFocusChanged(Canvas &, bool hasFocus)) + DENG2_DEFINE_AUDIENCE2(FocusChange, void canvasFocusChanged(Canvas &, bool hasFocus)) public: explicit Canvas(CanvasWindow *parent, QGLWidget* shared = 0); diff --git a/doomsday/libgui/include/de/gui/gluniform.h b/doomsday/libgui/include/de/gui/gluniform.h index 066db6c79d..d126ec3a4c 100644 --- a/doomsday/libgui/include/de/gui/gluniform.h +++ b/doomsday/libgui/include/de/gui/gluniform.h @@ -67,12 +67,12 @@ class LIBGUI_PUBLIC GLUniform /** * Notified when the value of the uniform changes. */ - DENG2_DEFINE_AUDIENCE(ValueChange, void uniformValueChanged(GLUniform &)) + DENG2_DEFINE_AUDIENCE2(ValueChange, void uniformValueChanged(GLUniform &)) /** * Notified when the uniform instance is deleted. */ - DENG2_DEFINE_AUDIENCE(Deletion, void uniformDeleted(GLUniform &)) + DENG2_DEFINE_AUDIENCE2(Deletion, void uniformDeleted(GLUniform &)) public: GLUniform(char const *nameInShader, Type uniformType); diff --git a/doomsday/libgui/include/de/gui/guiapp.h b/doomsday/libgui/include/de/gui/guiapp.h index 74620eb5ad..8e6ea2a15e 100644 --- a/doomsday/libgui/include/de/gui/guiapp.h +++ b/doomsday/libgui/include/de/gui/guiapp.h @@ -48,11 +48,14 @@ class LIBGUI_PUBLIC GuiApp : public QApplication, public App, /** * Notified when a Canvas is recreated. */ - DENG2_DEFINE_AUDIENCE(GLContextChange, void appGLContextChanged()) + DENG2_DEFINE_AUDIENCE2(GLContextChange, void appGLContextChanged()) public: GuiApp(int &argc, char **argv); + void setMetadata(String const &orgName, String const &orgDomain, + String const &appName, String const &appVersion); + bool notify(QObject *receiver, QEvent *event); /** diff --git a/doomsday/libgui/include/de/gui/keyeventsource.h b/doomsday/libgui/include/de/gui/keyeventsource.h index 49d5fc9671..eb0fa89bf3 100644 --- a/doomsday/libgui/include/de/gui/keyeventsource.h +++ b/doomsday/libgui/include/de/gui/keyeventsource.h @@ -32,10 +32,14 @@ namespace de { class LIBGUI_PUBLIC KeyEventSource { public: - DENG2_DEFINE_AUDIENCE(KeyEvent, void keyEvent(KeyEvent const &)) + DENG2_DEFINE_AUDIENCE2(KeyEvent, void keyEvent(KeyEvent const &)) public: + KeyEventSource(); virtual ~KeyEventSource() {} + +private: + DENG2_PRIVATE(d) }; } // namespace de diff --git a/doomsday/libgui/include/de/gui/mouseeventsource.h b/doomsday/libgui/include/de/gui/mouseeventsource.h index b6ea7e773a..f23d93a2ad 100644 --- a/doomsday/libgui/include/de/gui/mouseeventsource.h +++ b/doomsday/libgui/include/de/gui/mouseeventsource.h @@ -38,12 +38,15 @@ class LIBGUI_PUBLIC MouseEventSource Trapped }; - DENG2_DEFINE_AUDIENCE(MouseStateChange, void mouseStateChanged(State)) - - DENG2_DEFINE_AUDIENCE(MouseEvent, void mouseEvent(MouseEvent const &)) + DENG2_DEFINE_AUDIENCE2(MouseStateChange, void mouseStateChanged(State)) + DENG2_DEFINE_AUDIENCE2(MouseEvent, void mouseEvent(MouseEvent const &)) public: + MouseEventSource(); virtual ~MouseEventSource() {} + +private: + DENG2_PRIVATE(d) }; } // namespace de diff --git a/doomsday/libgui/include/de/gui/persistentcanvaswindow.h b/doomsday/libgui/include/de/gui/persistentcanvaswindow.h index a7c2e91dbd..717095b90d 100644 --- a/doomsday/libgui/include/de/gui/persistentcanvaswindow.h +++ b/doomsday/libgui/include/de/gui/persistentcanvaswindow.h @@ -78,7 +78,7 @@ class LIBGUI_PUBLIC PersistentCanvasWindow : public CanvasWindow * changes are queued, the notification is made only after all the changes * have been applied. */ - DENG2_DEFINE_AUDIENCE(AttributeChange, void windowAttributesChanged(PersistentCanvasWindow &)) + DENG2_DEFINE_AUDIENCE2(AttributeChange, void windowAttributesChanged(PersistentCanvasWindow &)) public: /** diff --git a/doomsday/libgui/libgui.pro b/doomsday/libgui/libgui.pro index ce28e677fc..bc9bd3c494 100644 --- a/doomsday/libgui/libgui.pro +++ b/doomsday/libgui/libgui.pro @@ -45,7 +45,7 @@ else:unix { } # Public headers. -HEADERS += \ +publicHeaders(root, \ include/de/Atlas \ include/de/AtlasTexture \ include/de/Canvas \ @@ -78,7 +78,9 @@ HEADERS += \ include/de/PersistentCanvasWindow \ include/de/RowAtlasAllocator \ include/de/VertexBuilder \ - \ +) + +publicHeaders(gui, \ include/de/gui/atlas.h \ include/de/gui/atlastexture.h \ include/de/gui/canvas.h \ @@ -115,7 +117,8 @@ HEADERS += \ include/de/gui/opengl.h \ include/de/gui/persistentcanvaswindow.h \ include/de/gui/rowatlasallocator.h \ - include/de/gui/vertexbuilder.h + include/de/gui/vertexbuilder.h \ +) # Sources and private headers. SOURCES += \ @@ -147,7 +150,9 @@ SOURCES += \ src/imagebank.cpp \ src/kdtreeatlasallocator.cpp \ src/keyevent.cpp \ + src/keyeventsource.cpp \ src/mouseevent.cpp \ + src/mouseeventsource.cpp \ src/nativefont.cpp \ src/qtnativefont.h \ src/persistentcanvaswindow.cpp \ @@ -170,6 +175,12 @@ else { unix:!macx: SOURCES += src/imKStoUCS_x11.c +scripts.files = \ + modules/gui.de + +OTHER_FILES += \ + $$scripts.files + # Installation --------------------------------------------------------------- macx { @@ -185,3 +196,9 @@ else { INSTALLS += target target.path = $$DENG_LIB_DIR } + +deng_sdk { + INSTALLS *= target scripts + target.path = $$DENG_SDK_LIB_DIR + scripts.path = $$DENG_SDK_DIR/modules +} diff --git a/doomsday/libdeng2/modules/gui.de b/doomsday/libgui/modules/gui.de similarity index 52% rename from doomsday/libdeng2/modules/gui.de rename to doomsday/libgui/modules/gui.de index dc6c5b4d6b..b53792227d 100644 --- a/doomsday/libdeng2/modules/gui.de +++ b/doomsday/libgui/modules/gui.de @@ -1,6 +1,6 @@ -# The Doomsday Engine Project -- libdeng2 +# The Doomsday Engine Project # -# Copyright (c) 2013 Jaakko Keränen +# Copyright (c) 2013-2014 Jaakko Keränen # # This program is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by @@ -18,6 +18,58 @@ #---------------------------------------------------------------------------- # Graphical user interface +import Version + +def setDefaults(d) + # Applies the default configuration settings. + # - d: Record where to set the values. + try + import DisplayMode + + # The default audio and video subsystems. + d.video = 'opengl' + d.audio = 'fmod' + + # Window manager defaults. + record d.window + d.window.fsaa = True # Remove this (should be window-specific). + + # Configure the main window. + record d.window.main + d.window.main.showFps = False + d.window.main.center = True + d.window.main.fsaa = True + d.window.main.vsync = True + + # The default window parameters depend on the original display mode. + mode = DisplayMode.originalMode() + + # By default the fullscreen resolution is the desktop resolution. + d.window.main.fullSize = [mode['width'], mode['height']] + + # In windowed mode mode, leave some space on the sides so that + # the first switch to windowed mode does not place the window in an + # inconvenient location. The reduction is done proportionally. + offx = mode['width'] * 0.15 + offy = mode['height'] * 0.15 + d.window.main.rect = [offx, offy, + mode['width'] - 2*offx, + mode['height'] - 2*offy] + d.window.main.colorDepth = mode['depth'] + + if Version.OS == 'windows' or Version.OS == 'macx' + d.window.main.fullscreen = True + d.window.main.maximize = False + else + d.window.main.fullscreen = False + d.window.main.maximize = True + end + + catch NotFoundError + # DisplayMode isn't available on the server. + end +end + def scale(value, factor) # Scales a value by 'factor'. If 'value' is a text string, the # suffixes "pt" and "px" (point, pixel) are retained in the result. diff --git a/doomsday/libgui/src/atlas.cpp b/doomsday/libgui/src/atlas.cpp index fc53624cea..66ee4af311 100644 --- a/doomsday/libgui/src/atlas.cpp +++ b/doomsday/libgui/src/atlas.cpp @@ -139,13 +139,19 @@ DENG2_PIMPL(Atlas) markFullyChanged(); mayDefrag = false; - DENG2_FOR_PUBLIC_AUDIENCE(Reposition, i) + DENG2_FOR_PUBLIC_AUDIENCE2(Reposition, i) { i->atlasContentRepositioned(self); } } + + DENG2_PIMPL_AUDIENCE(Reposition) + DENG2_PIMPL_AUDIENCE(OutOfSpace) }; +DENG2_AUDIENCE_METHOD(Atlas, Reposition) +DENG2_AUDIENCE_METHOD(Atlas, OutOfSpace) + Atlas::Atlas(Flags const &flags, Size const &totalSize) : d(new Instance(this, flags, totalSize)) {} @@ -252,7 +258,7 @@ Id Atlas::alloc(Image const &image) { LOG_GL_XVERBOSE("Atlas is full with %.1f%% usage") << d->usedPercentage()*100; - DENG2_FOR_AUDIENCE(OutOfSpace, i) + DENG2_FOR_AUDIENCE2(OutOfSpace, i) { i->atlasOutOfSpace(*this); } diff --git a/doomsday/libgui/src/canvas.cpp b/doomsday/libgui/src/canvas.cpp index 662cf79795..16d3f4840c 100644 --- a/doomsday/libgui/src/canvas.cpp +++ b/doomsday/libgui/src/canvas.cpp @@ -101,7 +101,7 @@ DENG2_PIMPL(Canvas) mouseGrabbed = true; - DENG2_FOR_PUBLIC_AUDIENCE(MouseStateChange, i) + DENG2_FOR_PUBLIC_AUDIENCE2(MouseStateChange, i) { i->mouseStateChanged(Trapped); } @@ -119,7 +119,7 @@ DENG2_PIMPL(Canvas) // Tell the mouse driver that the mouse is untrapped. mouseGrabbed = false; - DENG2_FOR_PUBLIC_AUDIENCE(MouseStateChange, i) + DENG2_FOR_PUBLIC_AUDIENCE2(MouseStateChange, i) { i->mouseStateChanged(Untrapped); } @@ -171,7 +171,7 @@ DENG2_PIMPL(Canvas) } #endif - DENG2_FOR_PUBLIC_AUDIENCE(KeyEvent, i) + DENG2_FOR_PUBLIC_AUDIENCE2(KeyEvent, i) { i->keyEvent(KeyEvent(ev->isAutoRepeat()? KeyEvent::Repeat : ev->type() == QEvent::KeyPress? KeyEvent::Pressed : @@ -215,8 +215,20 @@ DENG2_PIMPL(Canvas) /// @todo Double buffering is not really needed in manual FB mode. framebuf.swapBuffers(self, mode); } + + DENG2_PIMPL_AUDIENCE(GLReady) + DENG2_PIMPL_AUDIENCE(GLInit) + DENG2_PIMPL_AUDIENCE(GLResize) + DENG2_PIMPL_AUDIENCE(GLDraw) + DENG2_PIMPL_AUDIENCE(FocusChange) }; +DENG2_AUDIENCE_METHOD(Canvas, GLReady) +DENG2_AUDIENCE_METHOD(Canvas, GLInit) +DENG2_AUDIENCE_METHOD(Canvas, GLResize) +DENG2_AUDIENCE_METHOD(Canvas, GLDraw) +DENG2_AUDIENCE_METHOD(Canvas, FocusChange) + Canvas::Canvas(CanvasWindow* parent, QGLWidget* shared) : QGLWidget(parent, shared), d(new Instance(this, parent)) { @@ -299,16 +311,16 @@ bool Canvas::isGLReady() const void Canvas::copyAudiencesFrom(Canvas const &other) { - audienceForGLReady = other.audienceForGLReady; - audienceForGLInit = other.audienceForGLInit; - audienceForGLResize = other.audienceForGLResize; - audienceForGLDraw = other.audienceForGLDraw; - audienceForFocusChange = other.audienceForFocusChange; + d->audienceForGLReady = other.d->audienceForGLReady; + d->audienceForGLInit = other.d->audienceForGLInit; + d->audienceForGLResize = other.d->audienceForGLResize; + d->audienceForGLDraw = other.d->audienceForGLDraw; + d->audienceForFocusChange = other.d->audienceForFocusChange; - audienceForKeyEvent = other.audienceForKeyEvent; + audienceForKeyEvent() = other.audienceForKeyEvent(); - audienceForMouseStateChange = other.audienceForMouseStateChange; - audienceForMouseEvent = other.audienceForMouseEvent; + audienceForMouseStateChange() = other.audienceForMouseStateChange(); + audienceForMouseEvent() = other.audienceForMouseEvent(); } GLTarget &Canvas::renderTarget() const @@ -336,7 +348,7 @@ void Canvas::initializeGL() #endif GLInfo::glInit(); - DENG2_FOR_AUDIENCE(GLInit, i) i->canvasGLInit(*this); + DENG2_FOR_AUDIENCE2(GLInit, i) i->canvasGLInit(*this); } void Canvas::resizeGL(int w, int h) @@ -372,7 +384,7 @@ void Canvas::updateSize() d->currentSize = d->pendingSize; d->reconfigureFramebuffer(); - DENG2_FOR_AUDIENCE(GLResize, i) i->canvasGLResized(*this); + DENG2_FOR_AUDIENCE2(GLResize, i) i->canvasGLResized(*this); } void Canvas::showEvent(QShowEvent* ev) @@ -424,7 +436,7 @@ void Canvas::notifyReady() LOG_GL_WARNING("OpenGL 2.0 is not supported!"); LOGDEV_GL_XVERBOSE("Notifying GL ready"); - DENG2_FOR_AUDIENCE(GLReady, i) i->canvasGLReady(*this); + DENG2_FOR_AUDIENCE2(GLReady, i) i->canvasGLReady(*this); // This Canvas instance might have been destroyed now. } @@ -446,7 +458,7 @@ void Canvas::paintGL() // Make sure any changes to the state stack become effective. GLState::current().apply(); - DENG2_FOR_AUDIENCE(GLDraw, i) i->canvasGLDraw(*this); + DENG2_FOR_AUDIENCE2(GLDraw, i) i->canvasGLDraw(*this); LIBGUI_ASSERT_GL_OK(); } @@ -456,7 +468,7 @@ void Canvas::focusInEvent(QFocusEvent*) LOG_AS("Canvas"); LOG_INPUT_VERBOSE("Gained focus"); - DENG2_FOR_AUDIENCE(FocusChange, i) i->canvasFocusChanged(*this, true); + DENG2_FOR_AUDIENCE2(FocusChange, i) i->canvasFocusChanged(*this, true); } void Canvas::focusOutEvent(QFocusEvent*) @@ -467,7 +479,7 @@ void Canvas::focusOutEvent(QFocusEvent*) // Automatically ungrab the mouse if focus is lost. d->ungrabMouse(); - DENG2_FOR_AUDIENCE(FocusChange, i) i->canvasFocusChanged(*this, false); + DENG2_FOR_AUDIENCE2(FocusChange, i) i->canvasFocusChanged(*this, false); } void Canvas::keyPressEvent(QKeyEvent *ev) @@ -507,7 +519,7 @@ void Canvas::mousePressEvent(QMouseEvent *ev) ev->accept(); - DENG2_FOR_AUDIENCE(MouseEvent, i) + DENG2_FOR_AUDIENCE2(MouseEvent, i) { i->mouseEvent(MouseEvent(translateButton(ev->button()), MouseEvent::Pressed, Vector2i(ev->pos().x(), ev->pos().y()))); @@ -524,7 +536,7 @@ void Canvas::mouseReleaseEvent(QMouseEvent* ev) ev->accept(); - DENG2_FOR_AUDIENCE(MouseEvent, i) + DENG2_FOR_AUDIENCE2(MouseEvent, i) { i->mouseEvent(MouseEvent(translateButton(ev->button()), MouseEvent::Released, Vector2i(ev->pos().x(), ev->pos().y()))); @@ -538,7 +550,7 @@ void Canvas::mouseMoveEvent(QMouseEvent *ev) // Absolute events are only emitted when the mouse is untrapped. if(!d->mouseGrabbed) { - DENG2_FOR_AUDIENCE(MouseEvent, i) + DENG2_FOR_AUDIENCE2(MouseEvent, i) { i->mouseEvent(MouseEvent(MouseEvent::Absolute, Vector2i(ev->pos().x(), ev->pos().y()))); @@ -559,7 +571,7 @@ void Canvas::wheelEvent(QWheelEvent *ev) int axis = (ev->orientation() == Qt::Horizontal? 0 : 1); int dir = (ev->delta() < 0? -1 : 1); - DENG2_FOR_AUDIENCE(MouseEvent, i) + DENG2_FOR_AUDIENCE2(MouseEvent, i) { i->mouseEvent(MouseEvent(MouseEvent::FineAngle, axis == 0? Vector2i(ev->delta(), 0) : @@ -571,7 +583,7 @@ void Canvas::wheelEvent(QWheelEvent *ev) { d->wheelDir[axis] = dir; - DENG2_FOR_AUDIENCE(MouseEvent, i) + DENG2_FOR_AUDIENCE2(MouseEvent, i) { i->mouseEvent(MouseEvent(MouseEvent::Step, axis == 0? Vector2i(dir, 0) : diff --git a/doomsday/libgui/src/canvaswindow.cpp b/doomsday/libgui/src/canvaswindow.cpp index c4a08f9a2e..1a8a498c03 100644 --- a/doomsday/libgui/src/canvaswindow.cpp +++ b/doomsday/libgui/src/canvaswindow.cpp @@ -105,7 +105,7 @@ DENG2_PIMPL(CanvasWindow) canvas->makeCurrent(); LIBGUI_ASSERT_GL_OK(); - DENG2_FOR_EACH_OBSERVER(Canvas::GLInitAudience, i, canvas->audienceForGLInit) + DENG2_FOR_EACH_OBSERVER(Canvas::GLInitAudience, i, canvas->audienceForGLInit()) { i->canvasGLInit(*canvas); } @@ -127,7 +127,7 @@ DENG2_PIMPL(CanvasWindow) } // Restore the old focus change audience. - canvas->audienceForFocusChange = canvasFocusAudience; + canvas->audienceForFocusChange() = canvasFocusAudience; LOGDEV_GL_MSG("Canvas replaced with %p") << de::dintptr(canvas); } @@ -139,8 +139,8 @@ CanvasWindow::CanvasWindow() // Create the drawing canvas for this window. setCentralWidget(d->canvas = new Canvas(this)); // takes ownership - d->canvas->audienceForGLReady += this; - d->canvas->audienceForGLDraw += this; + d->canvas->audienceForGLReady() += this; + d->canvas->audienceForGLDraw() += this; // All input goes to the canvas. d->canvas->setFocus(); @@ -166,8 +166,8 @@ void CanvasWindow::recreateCanvas() // Steal the focus change audience temporarily so no spurious focus // notifications are sent. - d->canvasFocusAudience = canvas().audienceForFocusChange; - canvas().audienceForFocusChange.clear(); + d->canvasFocusAudience = canvas().audienceForFocusChange(); + canvas().audienceForFocusChange().clear(); // We'll re-trap the mouse after the new canvas is ready. d->mouseWasTrapped = canvas().isMouseTrapped(); @@ -178,7 +178,7 @@ void CanvasWindow::recreateCanvas() // Create the replacement Canvas. Once it's created and visible, we'll // finish the switch-over. d->recreated = new Canvas(this, d->canvas); - d->recreated->audienceForGLReady += this; + d->recreated->audienceForGLReady() += this; //d->recreated->setGeometry(d->canvas->geometry()); d->recreated->show(); diff --git a/doomsday/libgui/src/font_richformat.cpp b/doomsday/libgui/src/font_richformat.cpp index 5eee7696b3..e36abe1218 100644 --- a/doomsday/libgui/src/font_richformat.cpp +++ b/doomsday/libgui/src/font_richformat.cpp @@ -254,8 +254,8 @@ String Font::RichFormat::initFromStyledText(String const &styledText) { clear(); - d->esc.audienceForEscapeSequence += d; - d->esc.audienceForPlainText += d; + d->esc.audienceForEscapeSequence() += d; + d->esc.audienceForPlainText() += d; d->esc.parse(styledText); diff --git a/doomsday/libgui/src/glframebuffer.cpp b/doomsday/libgui/src/glframebuffer.cpp index 9093049876..b74062f925 100644 --- a/doomsday/libgui/src/glframebuffer.cpp +++ b/doomsday/libgui/src/glframebuffer.cpp @@ -54,13 +54,13 @@ DENG2_PIMPL(GLFramebuffer) , uMvpMatrix("uMvpMatrix", GLUniform::Mat4) , uBufTex ("uTex", GLUniform::Sampler2D) { - pDefaultSampleCount.audienceForChange += this; + pDefaultSampleCount.audienceForChange() += this; //DENG2_GUI_APP->audienceForGLContextChange += this; } ~Instance() { - pDefaultSampleCount.audienceForChange -= this; + pDefaultSampleCount.audienceForChange() -= this; //DENG2_GUI_APP->audienceForGLContextChange -= this; } diff --git a/doomsday/libgui/src/glprogram.cpp b/doomsday/libgui/src/glprogram.cpp index b4d736a903..69b64f067e 100644 --- a/doomsday/libgui/src/glprogram.cpp +++ b/doomsday/libgui/src/glprogram.cpp @@ -140,8 +140,8 @@ DENG2_PIMPL(GLProgram) { foreach(GLUniform const *u, bound) { - u->audienceForValueChange -= this; - u->audienceForDeletion -= this; + u->audienceForValueChange() -= this; + u->audienceForDeletion() -= this; } texturesChanged = false; bound.clear(); @@ -351,8 +351,8 @@ GLProgram &GLProgram::bind(GLUniform const &uniform) d->bound.insert(&uniform); d->changed.insert(&uniform); - uniform.audienceForValueChange += d; - uniform.audienceForDeletion += d; + uniform.audienceForValueChange() += d; + uniform.audienceForDeletion() += d; if(uniform.type() == GLUniform::Sampler2D) { @@ -370,8 +370,8 @@ GLProgram &GLProgram::unbind(GLUniform const &uniform) d->bound.remove(&uniform); d->changed.remove(&uniform); - uniform.audienceForValueChange -= d.get(); - uniform.audienceForDeletion -= d.get(); + uniform.audienceForValueChange() -= d.get(); + uniform.audienceForDeletion() -= d.get(); if(uniform.type() == GLUniform::Sampler2D) { diff --git a/doomsday/libgui/src/glstate.cpp b/doomsday/libgui/src/glstate.cpp index 1f9e058711..22acf202f7 100644 --- a/doomsday/libgui/src/glstate.cpp +++ b/doomsday/libgui/src/glstate.cpp @@ -104,11 +104,11 @@ namespace internal } void set(GLTarget *trg) { if(_target) { - _target->audienceForDeletion -= this; + _target->audienceForDeletion() -= this; } _target = trg; if(_target) { - _target->audienceForDeletion += this; + _target->audienceForDeletion() += this; } } CurrentTarget &operator = (GLTarget *trg) { diff --git a/doomsday/libgui/src/gluniform.cpp b/doomsday/libgui/src/gluniform.cpp index e57eea7d1d..a0b90e437a 100644 --- a/doomsday/libgui/src/gluniform.cpp +++ b/doomsday/libgui/src/gluniform.cpp @@ -71,7 +71,7 @@ DENG2_PIMPL(GLUniform) ~Instance() { - DENG2_FOR_PUBLIC_AUDIENCE(Deletion, i) i->uniformDeleted(self); + DENG2_FOR_PUBLIC_AUDIENCE2(Deletion, i) i->uniformDeleted(self); switch(type) { @@ -90,7 +90,7 @@ DENG2_PIMPL(GLUniform) break; case Sampler2D: - if(value.tex) value.tex->audienceForDeletion -= this; + if(value.tex) value.tex->audienceForDeletion() -= this; break; default: @@ -141,7 +141,7 @@ DENG2_PIMPL(GLUniform) void markAsChanged() { - DENG2_FOR_PUBLIC_AUDIENCE(ValueChange, i) + DENG2_FOR_PUBLIC_AUDIENCE2(ValueChange, i) { i->uniformValueChanged(self); } @@ -158,8 +158,14 @@ DENG2_PIMPL(GLUniform) } } } + + DENG2_PIMPL_AUDIENCE(Deletion) + DENG2_PIMPL_AUDIENCE(ValueChange) }; +DENG2_AUDIENCE_METHOD(GLUniform, Deletion) +DENG2_AUDIENCE_METHOD(GLUniform, ValueChange) + GLUniform::GLUniform(char const *nameInShader, Type uniformType) : d(new Instance(this, QLatin1String(nameInShader), uniformType)) {} @@ -280,12 +286,12 @@ GLUniform &GLUniform::operator = (GLTexture const *texture) if(d->value.tex != texture) { // We will observe the texture this uniform refers to. - if(d->value.tex) d->value.tex->audienceForDeletion -= d; + if(d->value.tex) d->value.tex->audienceForDeletion() -= d; d->value.tex = texture; d->markAsChanged(); - if(d->value.tex) d->value.tex->audienceForDeletion += d; + if(d->value.tex) d->value.tex->audienceForDeletion() += d; } return *this; } diff --git a/doomsday/libgui/src/guiapp.cpp b/doomsday/libgui/src/guiapp.cpp index 79b897e68a..6353c38b33 100644 --- a/doomsday/libgui/src/guiapp.cpp +++ b/doomsday/libgui/src/guiapp.cpp @@ -34,16 +34,32 @@ DENG2_PIMPL(GuiApp) Instance(Public *i) : Base(i) { - loop.audienceForIteration += self; + loop.audienceForIteration() += self; } + + DENG2_PIMPL_AUDIENCE(GLContextChange) }; +DENG2_AUDIENCE_METHOD(GuiApp, GLContextChange) + GuiApp::GuiApp(int &argc, char **argv) : QApplication(argc, argv), App(applicationFilePath(), arguments()), d(new Instance(this)) {} +void GuiApp::setMetadata(String const &orgName, String const &orgDomain, + String const &appName, String const &appVersion) +{ + setName(appName); + + // Qt metadata. + setOrganizationName (orgName); + setOrganizationDomain(orgDomain); + setApplicationName (appName); + setApplicationVersion(appVersion); +} + bool GuiApp::notify(QObject *receiver, QEvent *event) { try @@ -68,8 +84,8 @@ void GuiApp::notifyDisplayModeChanged() void GuiApp::notifyGLContextChanged() { - qDebug() << "notifying GL context change" << audienceForGLContextChange.size(); - DENG2_FOR_AUDIENCE(GLContextChange, i) i->appGLContextChanged(); + qDebug() << "notifying GL context change" << audienceForGLContextChange().size(); + DENG2_FOR_AUDIENCE2(GLContextChange, i) i->appGLContextChanged(); } int GuiApp::execLoop() diff --git a/doomsday/libgui/src/keyeventsource.cpp b/doomsday/libgui/src/keyeventsource.cpp new file mode 100644 index 0000000000..ab8dd6935e --- /dev/null +++ b/doomsday/libgui/src/keyeventsource.cpp @@ -0,0 +1,34 @@ +/** @file keyeventsource.cpp + * + * @authors Copyright (c) 2014 Jaakko Keränen + * + * @par License + * LGPL: http://www.gnu.org/licenses/lgpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. You should have received a copy of + * the GNU Lesser General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/KeyEventSource" + +namespace de { + +DENG2_PIMPL_NOREF(KeyEventSource) +{ + DENG2_PIMPL_AUDIENCE(KeyEvent) +}; + +DENG2_AUDIENCE_METHOD(KeyEventSource, KeyEvent) + +KeyEventSource::KeyEventSource() : d(new Instance) +{} + +} // namespace de + diff --git a/doomsday/libgui/src/mouseeventsource.cpp b/doomsday/libgui/src/mouseeventsource.cpp new file mode 100644 index 0000000000..6484672746 --- /dev/null +++ b/doomsday/libgui/src/mouseeventsource.cpp @@ -0,0 +1,35 @@ +/** @file mouseeventsource.cpp + * + * @authors Copyright (c) 2014 Jaakko Keränen + * + * @par License + * LGPL: http://www.gnu.org/licenses/lgpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. This program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser + * General Public License for more details. You should have received a copy of + * the GNU Lesser General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "de/MouseEventSource" + +namespace de { + +DENG2_PIMPL_NOREF(MouseEventSource) +{ + DENG2_PIMPL_AUDIENCE(MouseStateChange) + DENG2_PIMPL_AUDIENCE(MouseEvent) +}; + +DENG2_AUDIENCE_METHOD(MouseEventSource, MouseStateChange) +DENG2_AUDIENCE_METHOD(MouseEventSource, MouseEvent) + +MouseEventSource::MouseEventSource() : d(new Instance) +{} + +} // namespace de diff --git a/doomsday/libgui/src/nativefont.cpp b/doomsday/libgui/src/nativefont.cpp index f06df24396..6a846f03ab 100644 --- a/doomsday/libgui/src/nativefont.cpp +++ b/doomsday/libgui/src/nativefont.cpp @@ -70,7 +70,7 @@ NativeFont::NativeFont(String const &family) : d(new Instance(this)) setFamily(family); } -NativeFont::NativeFont(NativeFont const &other) : d(new Instance(this)) +NativeFont::NativeFont(NativeFont const &other) : Asset(other), d(new Instance(this)) { *this = other; } diff --git a/doomsday/libgui/src/persistentcanvaswindow.cpp b/doomsday/libgui/src/persistentcanvaswindow.cpp index 241a7c3772..086f928529 100644 --- a/doomsday/libgui/src/persistentcanvaswindow.cpp +++ b/doomsday/libgui/src/persistentcanvaswindow.cpp @@ -50,15 +50,17 @@ static QRect desktopRect() static QRect centeredQRect(Vector2ui const &size) { - QSize screenSize = desktopRect().size(); + Vector2ui const screenSize(desktopRect().size().width(), + desktopRect().size().height()); + Vector2ui const clamped = size.min(screenSize); LOGDEV_GL_XVERBOSE("centeredGeometry: Current desktop rect %i x %i") - << screenSize.width() << screenSize.height(); + << screenSize.x << screenSize.y; return QRect(desktopRect().topLeft() + - QPoint((screenSize.width() - size.x) / 2, - (screenSize.height() - size.y) / 2), - QSize(size.x, size.y)); + QPoint((screenSize.x - clamped.x) / 2, + (screenSize.y - clamped.y) / 2), + QSize(clamped.x, clamped.y)); } static Rectanglei centeredRect(Vector2ui const &size) @@ -737,7 +739,7 @@ DENG2_PIMPL(PersistentCanvasWindow) } // The queue is now empty; all modifications to state have been applied. - DENG2_FOR_PUBLIC_AUDIENCE(AttributeChange, i) + DENG2_FOR_PUBLIC_AUDIENCE2(AttributeChange, i) { i->windowAttributesChanged(self); } @@ -761,8 +763,12 @@ DENG2_PIMPL(PersistentCanvasWindow) return st; } + + DENG2_PIMPL_AUDIENCE(AttributeChange) }; +DENG2_AUDIENCE_METHOD(PersistentCanvasWindow, AttributeChange) + PersistentCanvasWindow::PersistentCanvasWindow(String const &id) : d(new Instance(this, id)) { @@ -896,7 +902,7 @@ void PersistentCanvasWindow::moveEvent(QMoveEvent *) d->state.setFlag(Instance::State::Centered, false); // Notify. - DENG2_FOR_AUDIENCE(AttributeChange, i) + DENG2_FOR_AUDIENCE2(AttributeChange, i) { i->windowAttributesChanged(*this); } diff --git a/doomsday/libshell/libshell.pro b/doomsday/libshell/libshell.pro index 0c487494bc..ce71194bf7 100644 --- a/doomsday/libshell/libshell.pro +++ b/doomsday/libshell/libshell.pro @@ -23,7 +23,7 @@ DEFINES += __LIBSHELL__ INCLUDEPATH += include # Public headers. -HEADERS += \ +publicHeaders(shell, \ include/de/shell/AbstractLineEditor \ include/de/shell/AbstractLink \ include/de/shell/Action \ @@ -73,7 +73,8 @@ HEADERS += \ include/de/shell/serverfinder.h \ include/de/shell/textcanvas.h \ include/de/shell/textrootwidget.h \ - include/de/shell/textwidget.h + include/de/shell/textwidget.h \ +) # Sources and private headers. SOURCES += \ @@ -116,3 +117,8 @@ else { INSTALLS += target target.path = $$DENG_LIB_DIR } + +deng_sdk { + INSTALLS *= target + target.path = $$DENG_SDK_LIB_DIR +} diff --git a/doomsday/macros.pri b/doomsday/macros.pri index 97f0078b3e..c816dcbed4 100644 --- a/doomsday/macros.pri +++ b/doomsday/macros.pri @@ -24,6 +24,21 @@ defineTest(echo) { } } +defineReplace(findLibDir) { + # Determines the appropriate library directory given prefix $$1 + prefix = $$1 + dir = $$prefix/lib + contains(QMAKE_HOST.arch, x86_64) { + exists($$prefix/lib64) { + dir = $$prefix/lib64 + } + exists($$prefix/lib/x86_64-linux-gnu) { + dir = $$prefix/lib/x86_64-linux-gnu + } + } + return($$dir) +} + defineTest(useLibDir) { btype = "" win32 { @@ -31,8 +46,16 @@ defineTest(useLibDir) { else: btype = "/Release" } exists($${1}$${btype}) { - LIBS += -L$${1}$${btype} - export(LIBS) + win32 { + LIBS += -L$${1}$${btype} + export(LIBS) + } + else { + # Specify this library directory first to ensure it overrides the + # system library directory. + QMAKE_LFLAGS = -L$${1}$${btype} $$QMAKE_LFLAGS + export(QMAKE_LFLAGS) + } return(true) } return(false) @@ -64,3 +87,20 @@ macx { doPostLink("install_name_tool -id @executable_path/../DengPlugins/$${1}.bundle/Versions/$$2/$$1 $${1}.bundle/Versions/$$2/$$1") } } + +defineTest(publicHeaders) { + # 1: id ("root" for the main include dir) + # 2: header files + deng_sdk { + dir = $$1 + contains(1, root): dir = . + eval(sdk_headers_$${1}.files += $$2) + eval(sdk_headers_$${1}.path = $$DENG_SDK_HEADER_DIR/$$dir) + INSTALLS *= sdk_headers_$$1 + export(INSTALLS) + export(sdk_headers_$${1}.files) + export(sdk_headers_$${1}.path) + } + HEADERS += $$2 + export(HEADERS) +} diff --git a/doomsday/sdk.pro b/doomsday/sdk.pro new file mode 100644 index 0000000000..b6e723a5a1 --- /dev/null +++ b/doomsday/sdk.pro @@ -0,0 +1,33 @@ +# The Doomsday Engine Project +# Copyright (c) 2014 Jaakko Keränen + +greaterThan(QT_MAJOR_VERSION, 4): cache() + +!deng_sdk: error(\"deng_sdk\" must be defined in CONFIG!) + +include(config.pri) + +TEMPLATE = subdirs +CONFIG += ordered +SUBDIRS = \ + build \ + libdeng2 \ + libshell + +!deng_noclient|macx { + SUBDIRS += \ + libgui \ + libappfw +} + +SUBDIRS += \ + tests + +#SUBDIRS += postbuild + +OTHER_FILES += doomsday_sdk.pri + +# Install the main .pri file of the SDK. +INSTALLS += sdk_pri +sdk_pri.files = doomsday_sdk.pri +sdk_pri.path = $$DENG_SDK_DIR diff --git a/doomsday/server/src/serverapp.cpp b/doomsday/server/src/serverapp.cpp index 58f11ec8b5..c19b05bdc5 100644 --- a/doomsday/server/src/serverapp.cpp +++ b/doomsday/server/src/serverapp.cpp @@ -103,10 +103,8 @@ ServerApp::ServerApp(int &argc, char **argv) QNetworkProxyFactory::setUseSystemConfiguration(true); // Metadata. - setOrganizationDomain ("dengine.net"); - setOrganizationName ("Deng Team"); - setApplicationName ("Doomsday Server"); - setApplicationVersion (DOOMSDAY_VERSION_BASE); + setMetadata("Deng Team", "dengine.net", "Doomsday Server", DOOMSDAY_VERSION_BASE); + setUnixHomeFolderName(".doomsday"); setTerminateFunc(handleAppTerminate); diff --git a/doomsday/tests/test_glsandbox/testwindow.cpp b/doomsday/tests/test_glsandbox/testwindow.cpp index 049d5b3783..5a96be082e 100644 --- a/doomsday/tests/test_glsandbox/testwindow.cpp +++ b/doomsday/tests/test_glsandbox/testwindow.cpp @@ -81,9 +81,9 @@ DENG2_OBSERVES(Bank, Load) // Use this as the main window. setMain(i); - self.canvas().audienceForGLInit += this; - self.canvas().audienceForGLResize += this; - Clock::appClock().audienceForTimeChange += this; + self.canvas().audienceForGLInit() += this; + self.canvas().audienceForGLResize() += this; + Clock::appClock().audienceForTimeChange() += this; uColor = Vector4f(.5f, .75f, .5f, 1); atlas->setTotalSize(Vector2ui(256, 256)); @@ -91,7 +91,7 @@ DENG2_OBSERVES(Bank, Load) imageBank.add("rtt.cube", "/data/graphics/testpic.png"); //imageBank.loadAll(); - imageBank.audienceForLoad += this; + imageBank.audienceForLoad() += this; } void canvasGLInit(Canvas &cv)