Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,549 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "application.h"
#include "../../lib/cframe.h"
#include "../include/appinit.h"
#include "../include/iapplication.h"
#include "../include/icommand.h"
#include "../include/imenubuilder.h"
#include "../include/iwindowcontroller.h"
#include "shareduiresources.h"
#include "window.h"
#include <algorithm>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
//------------------------------------------------------------------------
class Application final : public IPlatformApplication
{
public:
static Application& instance ();
Application () = default;
void setDelegate (Standalone::Application::DelegatePtr&& delegate);
void setConfiguration (Standalone::Application::Configuration&& config);
// IApplication
IPreference& getPreferences () const override;
const CommandLineArguments& getCommandLineArguments () const override;
const ISharedUIResources& getSharedUIResources () const override;
const ICommonDirectories& getCommonDirectories () const override;
Standalone::Application::IDelegate& getDelegate () const override;
WindowPtr createWindow (const WindowConfiguration& config,
const WindowControllerPtr& controller) override;
const WindowList& getWindows () const override { return windows; }
AlertResult showAlertBox (const AlertBoxConfig& config) override;
void showAlertBoxForWindow (const AlertBoxForWindowConfig& config) override;
void registerCommand (const Command& command, char16_t defaultCommandKey = 0) override;
bool executeCommand (const Command& command) override;
void enableTooltips (bool state) override;
void quit () override;
// IWindowListener
void onSizeChanged (const IWindow& window, const CPoint& newSize) override {};
void onPositionChanged (const IWindow& window, const CPoint& newPosition) override {};
void onShow (const IWindow& window) override;
void onHide (const IWindow& window) override {};
void onClosed (const IWindow& window) override;
void onActivated (const IWindow& window) override;
void onDeactivated (const IWindow& window) override {};
// ICommandHandler
bool canHandleCommand (const Command& command) override;
bool handleCommand (const Command& command) override;
// IPlatformApplication
void init (const InitParams& params) override;
CommandList getCommandList (const Platform::IWindow* window) override;
const CommandList& getKeyCommandList () override;
bool canQuit () override;
bool dontClosePopupOnDeactivation (Platform::IWindow* window) override;
const Configuration& getConfiguration () const override { return config; }
private:
void registerStandardCommands ();
bool doCommandHandling (const Command& command, bool checkOnly);
CommandList getCommandList (const Interface& context, const IMenuBuilder* menuBuilder);
bool inQuit () const { return hasBit (flags, flagInQuit); }
void setInQuit (bool state) { setBit (flags, flagInQuit, state); }
WindowList windows;
Standalone::Application::DelegatePtr delegate;
IPreference* preferences {nullptr};
ICommonDirectories* commonDirectories {nullptr};
PlatformCallbacks platform;
CommandList commandList;
CommandLineArguments commandLineArguments;
Configuration config;
uint64_t flags {flagTooltipsEnabled};
uint16_t commandIDCounter {0};
enum Flags
{
flagInQuit = 1 << 0,
flagTooltipsEnabled = 1 << 1,
};
};
//------------------------------------------------------------------------
Application& Application::instance ()
{
static Application app;
return app;
}
//------------------------------------------------------------------------
void Application::init (const InitParams& params)
{
preferences = &params.preferences;
commonDirectories = &params.commonDirectories;
commandLineArguments = std::move (params.cmdArgs);
platform = std::move (params.callbacks);
// TODO: make command registration configurable
registerStandardCommands ();
if (!params.openFiles.empty ())
getDelegate ().openFiles (params.openFiles);
getDelegate ().finishLaunching ();
}
//------------------------------------------------------------------------
void Application::registerStandardCommands ()
{
registerCommand (Commands::About);
registerCommand (Commands::Preferences);
registerCommand (Commands::Quit, 'q');
registerCommand (Commands::CloseWindow, 'w');
registerCommand (Commands::Undo, 'z');
registerCommand (Commands::Redo, 'Z');
registerCommand (Commands::Cut, 'x');
registerCommand (Commands::Copy, 'c');
registerCommand (Commands::Paste, 'v');
registerCommand (Commands::Delete, 0x8);
registerCommand (Commands::SelectAll, 'a');
}
//------------------------------------------------------------------------
void Application::setDelegate (Standalone::Application::DelegatePtr&& inDelegate)
{
delegate = std::move (inDelegate);
}
//------------------------------------------------------------------------
void Application::setConfiguration (Standalone::Application::Configuration&& configuration)
{
using namespace VSTGUI::Standalone::Application;
for (auto c : configuration)
{
switch (c.first)
{
case ConfigKey::UseCompressedUIDescriptionFiles:
{
vstgui_assert (c.second.type == ConfigValue::Type::Integer);
config.useCompressedUIDescriptionFiles = c.second.value.integer != 0;
break;
}
case ConfigKey::ShowCommandsInContextMenu:
{
vstgui_assert (c.second.type == ConfigValue::Type::Integer);
config.showCommandsInWindowContextMenu = c.second.value.integer != 0;
break;
}
}
}
}
//------------------------------------------------------------------------
Standalone::Application::IDelegate& Application::getDelegate () const
{
vstgui_assert (delegate.get (), "Delegate cannot be nullptr");
return *(delegate.get ());
}
//------------------------------------------------------------------------
IPreference& Application::getPreferences () const
{
vstgui_assert (preferences);
return *preferences;
}
//------------------------------------------------------------------------
const Application::CommandLineArguments& Application::getCommandLineArguments () const
{
return commandLineArguments;
}
//------------------------------------------------------------------------
const ISharedUIResources& Application::getSharedUIResources () const
{
return Detail::getSharedUIResources ();
}
//------------------------------------------------------------------------
const ICommonDirectories& Application::getCommonDirectories () const
{
vstgui_assert (commonDirectories);
return *commonDirectories;
}
//------------------------------------------------------------------------
WindowPtr Application::createWindow (const WindowConfiguration& inConfig,
const WindowControllerPtr& controller)
{
auto window = makeWindow (inConfig, controller);
if (window)
{
windows.emplace_back (window);
window->registerWindowListener (this);
}
return window;
}
//------------------------------------------------------------------------
AlertResult Application::showAlertBox (const AlertBoxConfig& inConfig)
{
if (platform.showAlert)
return platform.showAlert (inConfig);
return AlertResult::Error;
}
//------------------------------------------------------------------------
void Application::showAlertBoxForWindow (const AlertBoxForWindowConfig& inConfig)
{
vstgui_assert (inConfig.window);
if (platform.showAlertForWindow)
platform.showAlertForWindow (inConfig);
}
//------------------------------------------------------------------------
void Application::enableTooltips (bool state)
{
setBit (flags, flagTooltipsEnabled, state);
for (auto& window : windows)
{
if (auto frame = staticPtrCast<IPlatformWindowAccess> (window)->getFrame ())
frame->enableTooltips (state);
}
}
//------------------------------------------------------------------------
void Application::quit ()
{
if (inQuit () || !canQuit ())
return;
setInQuit (true);
if (platform.quit)
platform.quit ();
setInQuit (false);
}
//------------------------------------------------------------------------
bool Application::canQuit ()
{
if (!delegate->canQuit ())
return false;
auto currentWindows = windows; // make a copy
for (auto& window : currentWindows)
{
if (window->getController () && !window->getController ()->canClose (*window))
return false;
}
return true;
}
//------------------------------------------------------------------------
auto Application::getCommandList (const Interface& context, const IMenuBuilder* menuBuilder)
-> CommandList
{
CommandList menuCommandList;
if (menuBuilder)
{
for (auto& catList : commandList)
{
if (catList.second.empty ())
continue;
if (!menuBuilder->showCommandGroupInMenu (context, catList.first))
continue;
auto catListCopy = catList;
for (auto it = catListCopy.second.begin (); it != catListCopy.second.end ();)
{
auto current = it++;
if (!menuBuilder->showCommandInMenu (context, *current))
it = catListCopy.second.erase (current);
}
if (catListCopy.second.empty ())
continue;
if (auto func = menuBuilder->getCommandGroupSortFunction (context, catListCopy.first))
{
std::sort (catListCopy.second.begin (), catListCopy.second.end (),
[&] (const CommandWithKey& lhs, const CommandWithKey& rhs) {
return func (lhs.name, rhs.name);
});
}
for (auto it = ++catListCopy.second.begin (); it != catListCopy.second.end (); ++it)
{
if (menuBuilder->prependMenuSeparator (context, *it))
{
CommandWithKey separator {};
separator.name = CommandName::MenuSeparator;
it = catListCopy.second.emplace (it, std::move (separator));
++it;
}
}
menuCommandList.emplace_back (std::move (catListCopy));
}
return menuCommandList;
}
menuCommandList.clear ();
for (auto& catList : commandList)
{
if (catList.second.empty ())
continue;
auto catListCopy = catList;
if (catList.first == CommandGroup::Edit)
{
for (auto it = ++catListCopy.second.begin (); it != catListCopy.second.end (); ++it)
{
if (it->name == CommandName::Cut || it->name == CommandName::SelectAll)
{
CommandWithKey separator {};
separator.name = CommandName::MenuSeparator;
it = catListCopy.second.emplace (it, std::move (separator));
++it;
}
}
}
menuCommandList.emplace_back (std::move (catListCopy));
}
return menuCommandList;
}
//------------------------------------------------------------------------
auto Application::getCommandList (const Platform::IWindow* window) -> CommandList
{
if (window)
{
for (auto& w : getWindows ())
{
if (staticPtrCast<IPlatformWindowAccess> (w)->getPlatformWindow ().get () == window)
{
auto menuBuilder = w->getController ()->getWindowMenuBuilder (*w.get ());
if (!menuBuilder)
menuBuilder = delegate.get ()->dynamicCast<IMenuBuilder> ();
return getCommandList (asInterface<IWindow> (*w.get ()), menuBuilder);
}
}
return {};
}
return getCommandList (asInterface<IApplication> (*this),
delegate.get ()->dynamicCast<IMenuBuilder> ());
}
//------------------------------------------------------------------------
auto Application::getKeyCommandList () -> const CommandList&
{
return commandList;
}
//------------------------------------------------------------------------
void Application::registerCommand (const Command& command, char16_t defaultCommandKey)
{
CommandWithKey c;
c.group = command.group;
c.name = command.name;
c.defaultKey = defaultCommandKey;
c.id = ++commandIDCounter;
bool added = false;
for (auto& entry : commandList)
{
if (entry.first == command.group)
{
for (auto& cmd : entry.second)
{
if (cmd == command)
return; // already registered
}
entry.second.emplace_back (c);
added = true;
break;
}
}
if (!added)
commandList.push_back ({command.group, {c}});
if (platform.onCommandUpdate)
platform.onCommandUpdate ();
}
//------------------------------------------------------------------------
bool Application::executeCommand (const Command& command)
{
if (!windows.empty ())
{
if (auto commandHandler = dynamicPtrCast<ICommandHandler> (windows.front ()))
{
if (commandHandler->canHandleCommand (command))
{
if (commandHandler->handleCommand (command))
return true;
}
}
}
if (canHandleCommand (command))
return handleCommand (command);
return false;
}
//------------------------------------------------------------------------
bool Application::canHandleCommand (const Command& command)
{
return doCommandHandling (command, true);
}
//------------------------------------------------------------------------
bool Application::handleCommand (const Command& command)
{
return doCommandHandling (command, false);
}
//------------------------------------------------------------------------
bool Application::doCommandHandling (const Command& command, bool checkOnly)
{
bool result = false;
if (auto commandHandler = dynamic_cast<ICommandHandler*> (delegate.get ()))
result = checkOnly ? commandHandler->canHandleCommand (command) :
commandHandler->handleCommand (command);
if (!result)
{
if (command == Commands::Quit)
{
if (!checkOnly)
{
quit ();
return true;
}
return delegate->canQuit ();
}
else if (command == Commands::About)
{
if (!checkOnly)
{
delegate->showAboutDialog ();
return true;
}
return delegate->hasAboutDialog ();
}
else if (command == Commands::Preferences)
{
if (!checkOnly)
{
delegate->showPreferenceDialog ();
return true;
}
return delegate->hasPreferenceDialog ();
}
}
return result;
}
//------------------------------------------------------------------------
void Application::onShow (const IWindow& window)
{
if (auto frame = static_cast<const IPlatformWindowAccess&> (window).getFrame ())
frame->enableTooltips (hasBit (flags, flagTooltipsEnabled));
}
//------------------------------------------------------------------------
template <typename Cont>
typename Cont::const_iterator findWindow (const Cont& c, const IWindow& window)
{
return std::find_if (c.begin (), c.end (),
[&] (const WindowPtr& w) { return &window == w.get (); });
}
//------------------------------------------------------------------------
void Application::onClosed (const IWindow& window)
{
auto it = findWindow (windows, window);
if (it != windows.end ())
windows.erase (it);
}
//------------------------------------------------------------------------
void Application::onActivated (const IWindow& window)
{
// move the window in the window list to the first position
auto it = findWindow (windows, window);
if (it != windows.begin ())
{
auto windowPtr = *it;
windows.erase (it);
windows.insert (windows.begin (), windowPtr);
}
}
static std::vector<Platform::IWindow*> popupClosePreventionList;
//------------------------------------------------------------------------
bool Application::dontClosePopupOnDeactivation (Platform::IWindow* window)
{
return std::find (popupClosePreventionList.begin (), popupClosePreventionList.end (), window) !=
popupClosePreventionList.end ();
}
//------------------------------------------------------------------------
PreventPopupClose::PreventPopupClose (IWindow& window)
{
if (auto pwa = static_cast<IPlatformWindowAccess*> (&window))
{
if ((platformWindow = dynamicPtrCast<Platform::IWindow> (pwa->getPlatformWindow ())))
popupClosePreventionList.emplace_back (platformWindow.get ());
}
}
//------------------------------------------------------------------------
PreventPopupClose::~PreventPopupClose () noexcept
{
auto it = std::find (popupClosePreventionList.begin (), popupClosePreventionList.end (),
platformWindow.get ());
if (it != popupClosePreventionList.end ())
{
popupClosePreventionList.erase (it);
}
platformWindow->activate ();
}
//------------------------------------------------------------------------
} // Detail
//------------------------------------------------------------------------
IApplication& IApplication::instance ()
{
return Detail::Application::instance ();
}
//------------------------------------------------------------------------
namespace Application {
//------------------------------------------------------------------------
Init::Init (DelegatePtr&& delegate, Configuration&& config)
{
CView::kDirtyCallAlwaysOnMainThread = true;
Detail::Application::instance ().setDelegate (std::move (delegate));
Detail::Application::instance ().setConfiguration (std::move (config));
}
//------------------------------------------------------------------------
} // Application
} // Standalone
} // VSTGUI
@@ -0,0 +1,94 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../include/ialertbox.h"
#include "../include/iapplication.h"
#include "../include/icommand.h"
#include "../include/iwindowlistener.h"
#include "platform/iplatformwindow.h"
#include <functional>
#include <vector>
namespace VSTGUI {
namespace Standalone {
namespace Detail {
//------------------------------------------------------------------------
struct CommandWithKey : Command
{
char16_t defaultKey;
uint16_t id;
};
//------------------------------------------------------------------------
struct Configuration
{
bool useCompressedUIDescriptionFiles {false};
bool showCommandsInWindowContextMenu {false};
};
//------------------------------------------------------------------------
struct PlatformCallbacks
{
using OnCommandUpdateFunc = std::function<void ()>;
using QuitFunc = std::function<void ()>;
using AlertFunc = std::function<AlertResult (const AlertBoxConfig&)>;
using AlertForWindowFunc = std::function<void (const AlertBoxForWindowConfig&)>;
QuitFunc quit;
OnCommandUpdateFunc onCommandUpdate;
AlertFunc showAlert;
AlertForWindowFunc showAlertForWindow;
};
//------------------------------------------------------------------------
class IPlatformApplication : public IApplication, public IWindowListener, public ICommandHandler
{
public:
using CommandWithKeyList = std::vector<CommandWithKey>;
using CommandListPair = std::pair<UTF8String, CommandWithKeyList>;
using CommandList = std::vector<CommandListPair>;
using OpenFilesList = std::vector<UTF8String>;
struct InitParams
{
IPreference& preferences;
ICommonDirectories& commonDirectories;
IApplication::CommandLineArguments&& cmdArgs;
PlatformCallbacks&& callbacks;
OpenFilesList openFiles;
};
virtual void init (const InitParams& params) = 0;
virtual CommandList getCommandList (const Platform::IWindow* window = nullptr) = 0;
virtual const CommandList& getKeyCommandList () = 0;
virtual bool canQuit () = 0;
virtual bool dontClosePopupOnDeactivation (Platform::IWindow* window) = 0;
virtual const Configuration& getConfiguration () const = 0;
};
//------------------------------------------------------------------------
inline IPlatformApplication* getApplicationPlatformAccess ()
{
return static_cast<IPlatformApplication*> (&IApplication::instance ());
}
//------------------------------------------------------------------------
class PreventPopupClose
{
public:
PreventPopupClose (IWindow& window);
~PreventPopupClose () noexcept;
private:
std::shared_ptr<Platform::IWindow> platformWindow;
};
//------------------------------------------------------------------------
} // Detail
} // Standalone
} // VSTGUI
@@ -0,0 +1,46 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../include/iasync.h"
#include "../../lib/tasks.h"
//------------------------------------------------------------------------
namespace VSTGUI::Standalone::Async {
// Compatibility Layer to support previous API
//------------------------------------------------------------------------
struct Queue
{
const Tasks::Queue* queue {nullptr};
Queue (const Tasks::Queue& q) : queue (&q) {}
const Tasks::Queue& get () const { return *queue; }
};
//------------------------------------------------------------------------
const QueuePtr& mainQueue ()
{
static QueuePtr q = std::make_shared<Queue> (Tasks::mainQueue ());
return q;
}
//------------------------------------------------------------------------
const QueuePtr& backgroundQueue ()
{
static QueuePtr q = std::make_shared<Queue> (Tasks::backgroundQueue ());
return q;
}
//------------------------------------------------------------------------
QueuePtr makeSerialQueue (const char* name)
{
return std::make_shared<Queue> (Tasks::makeSerialQueue (name));
}
//------------------------------------------------------------------------
void schedule (QueuePtr queue, Task&& task) { Tasks::schedule (queue->get (), std::move (task)); }
//------------------------------------------------------------------------
} // namespace VSTGUI::Standalone::Async
@@ -0,0 +1,315 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../../lib/cframe.h"
#include "../../lib/controls/cbuttons.h"
#include "../../lib/controls/ctextlabel.h"
#include "../../lib/iviewlistener.h"
#include "../../uidescription/delegationcontroller.h"
#include "../../uidescription/iuidescription.h"
#include "../include/helpers/uidesc/customization.h"
#include "../include/helpers/value.h"
#include "../include/helpers/valuelistener.h"
#include "../include/helpers/windowcontroller.h"
#include "genericalertbox.h"
#include <array>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
namespace {
//------------------------------------------------------------------------
const auto xmlText = R"(<?xml version="1.0" encoding="UTF-8"?>
<vstgui-ui-description version="1">
<template autosize="left right top bottom " background-color="~ BlackCColor" background-color-draw-style="filled" class="CViewContainer" mouse-enabled="true" name="AlertBox" opacity="1" origin="0, 0" size="420, 110" sub-controller="ButtonController" transparent="true" wants-focus="false">
<view autosize="left right top bottom " class="CGradientView" draw-antialiased="true" frame-color="~ BlackCColor" frame-width="-1" gradient="About Background" gradient-angle="0" gradient-style="linear" mouse-enabled="false" opacity="1" origin="0, 0" radial-center="0.5, 0.5" radial-radius="1" round-rect-radius="2" size="420, 110" transparent="false" wants-focus="false"/>
<view autosize="right bottom " class="CTextButton" control-tag="AlertBox.thirdButton" default-value="0.5" font="~ SystemFont" frame-color="~ BlackCColor" frame-color-highlighted="~ BlackCColor" frame-width="-1" gradient="Default TextButton Gradient" gradient-highlighted="Default TextButton Gradient Highlighted" icon-position="left" icon-text-margin="0" kick-style="false" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="90, 80" round-radius="3" size="100, 20" text-alignment="center" text-color="~ BlackCColor" text-color-highlighted="~ WhiteCColor" title="Third" transparent="false" wants-focus="true" wheel-inc-value="0.1"/>
<view autosize="right bottom " class="CTextButton" control-tag="AlertBox.secondButton" default-value="0.5" font="~ SystemFont" frame-color="~ BlackCColor" frame-color-highlighted="~ BlackCColor" frame-width="-1" gradient="Default TextButton Gradient" gradient-highlighted="Default TextButton Gradient Highlighted" icon-position="left" icon-text-margin="0" kick-style="false" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="200, 80" round-radius="3" size="100, 20" text-alignment="center" text-color="~ BlackCColor" text-color-highlighted="~ WhiteCColor" title="OK" transparent="false" wants-focus="true" wheel-inc-value="0.1"/>
<view autosize="right bottom " class="CTextButton" control-tag="AlertBox.firstButton" default-value="0.5" font="~ SystemFont" frame-color="~ BlackCColor" frame-color-highlighted="~ BlackCColor" frame-width="-1" gradient="Default TextButton Gradient" gradient-highlighted="Default TextButton Gradient Highlighted" icon-position="left" icon-text-margin="0" kick-style="false" max-value="1" min-value="0" mouse-enabled="true" opacity="1" origin="310, 80" round-radius="3" size="100, 20" text-alignment="center" text-color="~ BlackCColor" text-color-highlighted="~ WhiteCColor" title="Cancel" transparent="false" wants-focus="true" wheel-inc-value="0.1"/>
<view auto-height="false" autosize="left right top " back-color="~ BlackCColor" background-offset="0, 0" class="CMultiLineTextLabel" control-tag="AlertBox.headline" default-value="0.5" font="~ NormalFontVeryBig" font-antialias="true" font-color="~ BlackCColor" frame-color="~ BlackCColor" frame-width="0" line-layout="wrap" max-value="1" min-value="0" mouse-enabled="false" opacity="1" origin="10, 10" round-rect-radius="6" shadow-color="~ GreyCColor" size="400, 30" style-3D-in="false" style-3D-out="false" style-no-draw="false" style-no-frame="false" style-no-text="false" style-round-rect="false" style-shadow-text="false" text-alignment="center" text-inset="0, 0" text-rotation="0" text-shadow-offset="1, 1" title="This is a test headline" transparent="true" value-precision="2" wants-focus="false" wheel-inc-value="0.1"/>
<view auto-height="false" autosize="left right top " back-color="~ BlackCColor" background-offset="0, 0" class="CMultiLineTextLabel" control-tag="AlertBox.description" default-value="0.5" font="~ SystemFont" font-antialias="true" font-color="~ BlackCColor" frame-color="~ BlackCColor" frame-width="0" line-layout="wrap" max-value="1" min-value="0" mouse-enabled="false" opacity="1" origin="10, 40" round-rect-radius="6" shadow-color="~ RedCColor" size="400, 30" style-3D-in="false" style-3D-out="false" style-no-draw="false" style-no-frame="false" style-no-text="false" style-round-rect="false" style-shadow-text="false" text-alignment="left" text-inset="5, 5" text-rotation="0" text-shadow-offset="1, 1" title="This is a test description" transparent="true" value-precision="2" wants-focus="false" wheel-inc-value="0.1"/>
</template>
<control-tags>
<control-tag name="AlertBox.description" tag="4"/>
<control-tag name="AlertBox.firstButton" tag="0"/>
<control-tag name="AlertBox.headline" tag="3"/>
<control-tag name="AlertBox.secondButton" tag="1"/>
<control-tag name="AlertBox.thirdButton" tag="2"/>
</control-tags>
<colors>
<color name="AlertBox.background" rgba="#ecececff"/>
</colors>
<gradients>
<gradient name="About Background">
<color-stop rgba="#dcdcdcff" start="0"/>
<color-stop rgba="#b3b3b3ff" start="0.5"/>
<color-stop rgba="#b4b4b4ff" start="1"/>
</gradient>
<gradient name="Default TextButton Gradient">
<color-stop rgba="#dcdcdcff" start="0"/>
<color-stop rgba="#b4b4b4ff" start="1"/>
</gradient>
<gradient name="Default TextButton Gradient Highlighted">
<color-stop rgba="#b4b4b4ff" start="0"/>
<color-stop rgba="#646464ff" start="1"/>
</gradient>
<gradient name="Focused TextButton Gradient">
<color-stop rgba="#dcdcdcff" start="0"/>
<color-stop rgba="#9b9b9bff" start="0.660000026226043701171875"/>
<color-stop rgba="#b4b4b4ff" start="1"/>
</gradient>
</gradients>
</vstgui-ui-description>
)";
//------------------------------------------------------------------------
}
//------------------------------------------------------------------------
class AlertBoxController : public UIDesc::IModelBinding,
public UIDesc::CustomizationAdapter,
public ValueListenerAdapter,
public WindowControllerAdapter,
public ViewListenerAdapter,
public std::enable_shared_from_this<AlertBoxController>
{
public:
static constexpr auto Button1TagName = "AlertBox.firstButton";
static constexpr auto Button2TagName = "AlertBox.secondButton";
static constexpr auto Button3TagName = "AlertBox.thirdButton";
AlertBoxController (const AlertBoxConfig& config, const AlertBoxCallback& callback)
: callback (callback)
, firstButtonTitle (config.defaultButton)
, secondButtonTitle (config.secondButton)
, thirdButtonTitle (config.thirdButton)
{
addValue (Value::make (Button1TagName));
addValue (Value::make (Button2TagName));
addValue (Value::make (Button3TagName));
addValue (Value::makeStringListValue ("AlertBox.headline", {config.headline}))
->setActive (false);
addValue (Value::makeStringListValue ("AlertBox.description", {config.description}))
->setActive (false);
if (!firstButtonTitle.empty ())
++usedButtons;
if (!secondButtonTitle.empty ())
++usedButtons;
if (!thirdButtonTitle.empty ())
++usedButtons;
}
void setWindow (const WindowPtr& w)
{
window = w;
window->registerWindowListener (this);
CTextButton* focusButton = nullptr;
switch (usedButtons)
{
case 1: focusButton = buttons[0]; break;
case 2:
case 3: focusButton = buttons[1]; break;
}
if (focusButton)
focusButton->getFrame ()->setFocusView (focusButton);
}
const ValueList& getValues () const override { return values; }
IController* createController (const UTF8StringView& name, IController* parent,
const IUIDescription* uiDesc) override
{
if (name == "ButtonController")
{
return new ButtonController (*this, parent);
}
return nullptr;
}
void onEndEdit (IValue& value) override
{
if (value.getValue () < 0.5)
return;
auto self = shared_from_this ();
if (value.getID () == Button1TagName)
{
if (usedButtons == 1)
alertResult = AlertResult::DefaultButton;
else
alertResult = AlertResult::SecondButton;
}
else if (value.getID () == Button2TagName)
{
alertResult = AlertResult::DefaultButton;
}
else if (value.getID () == Button3TagName)
{
alertResult = AlertResult::ThirdButton;
}
window->close ();
}
void onClosed (const IWindow&) override
{
for (auto button : buttons)
{
if (button)
button->unregisterViewListener (this);
}
if (callback)
{
callback (alertResult);
callback = nullptr;
}
}
void onSetContentView (IWindow& inWindow, const SharedPointer<CFrame>& contentView) override
{
std::vector<CMultiLineTextLabel*> views;
if (contentView->getChildViewsOfType<CMultiLineTextLabel> (views, true) == 0)
return;
CCoord diffY = 0.;
CCoord lastViewBottom = 0.;
for (auto label : views)
{
auto prevSize = label->getViewSize ();
label->setAutoHeight (true);
auto newSize = label->getViewSize ();
diffY += newSize.getHeight () - prevSize.getHeight ();
if (lastViewBottom == 0)
{
lastViewBottom = newSize.bottom;
}
else
{
newSize.offset (0, lastViewBottom - newSize.top);
label->setViewSize (newSize);
}
}
if (diffY == 0.)
return;
auto windowSize = inWindow.getSize ();
windowSize.y += diffY;
inWindow.setSize (windowSize);
contentView->setSize (windowSize.x, windowSize.y);
}
void viewLostFocus (CView* view) override
{
if (auto button = dynamic_cast<CTextButton*> (view))
button->setGradient (normalButtonGradient);
}
void viewTookFocus (CView* view) override
{
if (auto button = dynamic_cast<CTextButton*> (view))
button->setGradient (focusedButtonGradient);
}
void onUIDescriptionParsed (const IUIDescription* uiDesc) override
{
focusedButtonGradient = uiDesc->getGradient ("Focused TextButton Gradient");
normalButtonGradient = uiDesc->getGradient ("Default TextButton Gradient");
}
private:
struct ButtonController : DelegationController
{
ButtonController (AlertBoxController& alertBoxController, IController* parent)
: DelegationController (parent), alertBoxController (alertBoxController)
{
}
CView* verifyView (CView* view, const UIAttributes& attributes,
const IUIDescription* description) override
{
if (auto button = dynamic_cast<CTextButton*> (view))
{
UTF8StringView tagName = description->lookupControlTagName (button->getTag ());
if (!setupButton (button, tagName))
{
view->forget ();
return nullptr;
}
button->registerViewListener (&alertBoxController);
}
return controller->verifyView (view, attributes, description);
}
bool setupButton (CTextButton* button, UTF8StringView name)
{
if (name == Button3TagName)
{
if (alertBoxController.usedButtons < 3)
return false;
button->setTitle (alertBoxController.thirdButtonTitle);
alertBoxController.buttons[2] = button;
return true;
}
if (name == Button2TagName)
{
if (alertBoxController.usedButtons < 2)
return false;
button->setTitle (alertBoxController.firstButtonTitle);
alertBoxController.buttons[1] = button;
return true;
}
if (name == Button1TagName)
{
if (alertBoxController.usedButtons == 1)
button->setTitle (alertBoxController.firstButtonTitle);
else
button->setTitle (alertBoxController.secondButtonTitle);
alertBoxController.buttons[0] = button;
return true;
}
return false;
}
AlertBoxController& alertBoxController;
};
ValuePtr addValue (ValuePtr&& value)
{
value->registerListener (this);
values.emplace_back (std::move (value));
return values.back ();
}
WindowPtr window;
AlertBoxCallback callback;
ValueList values;
UTF8String firstButtonTitle;
UTF8String secondButtonTitle;
UTF8String thirdButtonTitle;
AlertResult alertResult {AlertResult::Error};
uint32_t usedButtons {0};
std::array<CTextButton*, 3> buttons {{nullptr}};
SharedPointer<CGradient> focusedButtonGradient;
SharedPointer<CGradient> normalButtonGradient;
};
//------------------------------------------------------------------------
WindowPtr createAlertBox (const AlertBoxConfig& alertBoxConfig, const AlertBoxCallback& callback)
{
vstgui_assert (callback);
auto controller = std::make_shared<AlertBoxController> (alertBoxConfig, callback);
UIDesc::Config config;
config.windowConfig.type = WindowType::Document;
config.windowConfig.style.transparent ().movableByWindowBackground ();
config.viewName = "AlertBox";
config.uiDescFileName = xmlText;
config.modelBinding = staticPtrCast<UIDesc::IModelBinding> (controller);
config.customization = staticPtrCast<UIDesc::ICustomization> (controller);
auto window = UIDesc::makeWindow (config);
controller->setWindow (window);
return window;
}
//------------------------------------------------------------------------
} // Detail
} // Standalone
} // VSTGUI
@@ -0,0 +1,21 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../include/ialertbox.h"
#include "../include/iuidescwindow.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
using AlertBoxCallback = std::function<void (AlertResult)>;
WindowPtr createAlertBox (const AlertBoxConfig& config, const AlertBoxCallback& callback);
//------------------------------------------------------------------------
} // Detail
} // Standalone
} // VSTGUI
@@ -0,0 +1,646 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../../include/helpers/value.h"
#include "../../../lib/algorithm.h"
#include "../../../lib/dispatchlist.h"
#include "../../include/ivaluelistener.h"
#include <algorithm>
#include <sstream>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
namespace /* anonymous */ {
//------------------------------------------------------------------------
IValue::Type convertStepToValue (IStepValue::StepType step, IStepValue::StepType steps)
{
return static_cast<IValue::Type> (step) / static_cast<IValue::Type> (steps);
}
//------------------------------------------------------------------------
IStepValue::StepType convertValueToStep (IValue::Type value, IStepValue::StepType steps)
{
return std::min (
steps, static_cast<IStepValue::StepType> (value * static_cast<IValue::Type> (steps + 1)));
}
//------------------------------------------------------------------------
class PercentValueConverter : public IValueConverter
{
public:
UTF8String valueAsString (IValue::Type value) const override
{
auto v = static_cast<uint32_t> (value * 100.);
return toString (v) + " %";
}
IValue::Type stringAsValue (const UTF8String& string) const override
{
auto v = UTF8StringView (string).toDouble ();
return v / 100.;
}
IValue::Type plainToNormalized (IValue::Type plain) const override { return plain / 100.; }
IValue::Type normalizedToPlain (IValue::Type normalized) const override
{
return normalized * 100.;
}
};
//------------------------------------------------------------------------
class DefaultValueConverter : public IValueConverter
{
public:
DefaultValueConverter (uint32_t stringPrecision = 40) : stringPrecision (stringPrecision) {}
UTF8String valueAsString (IValue::Type value) const override
{
UTF8String result;
if (value < 0. || value > 1.)
return result;
value = normalizedToPlain (value);
std::stringstream sstream;
sstream.imbue (std::locale::classic ());
sstream.precision (stringPrecision);
if (stringPrecision)
sstream << std::showpoint;
sstream << std::fixed;
sstream << value;
result = sstream.str ();
return result;
}
IValue::Type stringAsValue (const UTF8String& string) const override
{
IValue::Type value;
std::istringstream sstream (string.getString ());
sstream.imbue (std::locale::classic ());
sstream.precision (stringPrecision);
sstream >> value;
value = plainToNormalized (value);
if (sstream.fail () || value < 0. || value > 1.)
return IValue::InvalidValue;
return value;
}
IValue::Type plainToNormalized (IValue::Type plain) const override { return plain; }
IValue::Type normalizedToPlain (IValue::Type normalized) const override { return normalized; }
private:
uint32_t stringPrecision {40};
};
//------------------------------------------------------------------------
class RangeValueConverter : public DefaultValueConverter,
public IRangeValueConverter
{
public:
RangeValueConverter (IValue::Type minValue, IValue::Type maxValue, uint32_t stringPrecision)
: DefaultValueConverter (stringPrecision), minValue (minValue), maxValue (maxValue)
{
}
IValue::Type plainToNormalized (IValue::Type plain) const override
{
return (plain - minValue) / (maxValue - minValue);
}
IValue::Type normalizedToPlain (IValue::Type normalized) const override
{
return normalized * (maxValue - minValue) + minValue;
}
void setRange (IValue::Type _min, IValue::Type _max) override
{
minValue = _min;
maxValue = _max;
}
private:
IValue::Type minValue;
IValue::Type maxValue;
};
//------------------------------------------------------------------------
class StringListValueConverter : public IValueConverter
{
public:
explicit StringListValueConverter (const std::initializer_list<UTF8String>& list)
: strings (list)
{
}
explicit StringListValueConverter (const IStringListValue::StringList& list) : strings (list) {}
UTF8String valueAsString (IValue::Type value) const override
{
if (strings.empty ())
return "";
auto index =
convertValueToStep (value, static_cast<IStepValue::StepType> (strings.size () - 1));
return strings[index];
}
IValue::Type stringAsValue (const UTF8String& string) const override
{
if (auto index = indexOf (strings.begin (), strings.end (), string))
{
return convertStepToValue (static_cast<IStepValue::StepType> (*index),
static_cast<IStepValue::StepType> (strings.size () - 1));
}
return IValue::InvalidValue;
}
IValue::Type plainToNormalized (IValue::Type plain) const override
{
return convertStepToValue (static_cast<IStepValue::StepType> (plain),
static_cast<IStepValue::StepType> (strings.size () - 1));
}
IValue::Type normalizedToPlain (IValue::Type normalized) const override
{
return convertValueToStep (normalized,
static_cast<IStepValue::StepType> (strings.size () - 1));
}
bool updateString (size_t index, const UTF8String& str)
{
if (index < strings.size ())
{
strings[index] = str;
return true;
}
return false;
}
private:
IStringListValue::StringList strings;
};
//------------------------------------------------------------------------
struct ValueBase : public IValue
{
ValueBase (const UTF8String& id) : idString (id) {}
const UTF8String& getID () const override { return idString; }
using Listeners = DispatchList<IValueListener*>;
void registerListener (IValueListener* listener) override { listeners.add (listener); }
void unregisterListener (IValueListener* listener) override { listeners.remove (listener); }
Listeners& getListeners () { return listeners; }
private:
UTF8String idString;
Listeners listeners;
};
//------------------------------------------------------------------------
class StaticStringValue : public ValueBase,
public IValueConverter
{
public:
StaticStringValue (const UTF8String& id, const UTF8String& value)
: ValueBase (id), value (value)
{
}
StaticStringValue (const UTF8String& id, UTF8String&& value)
: ValueBase (id), value (std::move (value))
{
}
void beginEdit () override {}
bool performEdit (Type newValue) override { return false; }
void endEdit () override {}
void setActive (bool state) override {}
bool isActive () const override { return false; }
Type getValue () const override { return 0.; }
bool isEditing () const override { return false; }
const IValueConverter& getConverter () const override { return *this; }
UTF8String valueAsString (IValue::Type) const override { return value; }
IValue::Type stringAsValue (const UTF8String&) const override { return 0.; }
IValue::Type plainToNormalized (IValue::Type) const override { return 0.; }
IValue::Type normalizedToPlain (IValue::Type) const override { return 0.; }
private:
UTF8String value;
};
//------------------------------------------------------------------------
class Value : public ValueBase
{
public:
Value (const UTF8String& id, Type initialValue, const ValueConverterPtr& valueConverter);
void beginEdit () override;
bool performEdit (Type newValue) override;
void endEdit () override;
void setActive (bool state) override;
bool isActive () const override;
Type getValue () const override;
bool isEditing () const override;
const IValueConverter& getConverter () const override;
bool hasValueConverter () const { return valueConverter != nullptr; }
void setValueConverter (const ValueConverterPtr& stringConverter);
const ValueConverterPtr& getValueConverter () const { return valueConverter; }
void dispatchStateChange ();
private:
Type value;
bool active {true};
uint32_t editCount {0};
ValueConverterPtr valueConverter;
};
//------------------------------------------------------------------------
class StringValue : public Value,
public IValueConverter,
public IStringValue
{
public:
StringValue (const UTF8String& id, const UTF8String& value)
: Value (id, 0, nullptr), str (value)
{
}
StringValue (const UTF8String& id, UTF8String&& value)
: Value (id, 0, nullptr), str (std::move (value))
{
}
const IValueConverter& getConverter () const override { return *this; }
UTF8String valueAsString (IValue::Type) const override { return str; }
IValue::Type stringAsValue (const UTF8String& s) const override
{
if (isEditing ())
str = s;
return 0.;
}
IValue::Type plainToNormalized (IValue::Type) const override { return 0.; }
IValue::Type normalizedToPlain (IValue::Type) const override { return 0.; }
void setString (const UTF8String& s) override
{
str = s;
if (isEditing ())
performEdit (0.);
}
const UTF8String& getString () const override { return str; }
private:
mutable UTF8String str;
};
//------------------------------------------------------------------------
class StepValue : public Value,
public IStepValue,
public IValueConverter,
public IMutableStepValue
{
public:
StepValue (const UTF8String& id, StepType initialSteps, Type initialValue,
const ValueConverterPtr& stringConverter);
bool performEdit (Type newValue) override;
StepType getSteps () const override;
IValue::Type stepToValue (StepType step) const override;
StepType valueToStep (IValue::Type) const override;
UTF8String valueAsString (IValue::Type value) const override;
IValue::Type stringAsValue (const UTF8String& string) const override;
IValue::Type plainToNormalized (IValue::Type plain) const override;
IValue::Type normalizedToPlain (IValue::Type normalized) const override;
const IValueConverter& getConverter () const override;
bool setNumSteps (StepType numSteps) override;
private:
StepType steps;
};
//------------------------------------------------------------------------
class StringListValue : public StepValue,
public IStringListValue
{
public:
StringListValue (const UTF8String& id, StepType initialSteps, Type initialValue,
const ValueConverterPtr& stringConverter);
bool setNumSteps (StepType numSteps) override;
bool updateStringList (const StringList& newStrings) override;
bool updateString (size_t index, const StringType& string) override;
};
//------------------------------------------------------------------------
Value::Value (const UTF8String& id, Type initialValue, const ValueConverterPtr& valueConverter)
: ValueBase (id), value (initialValue), valueConverter (valueConverter)
{
}
//------------------------------------------------------------------------
void Value::beginEdit ()
{
++editCount;
if (editCount == 1)
{
getListeners ().forEach ([this] (IValueListener* l) { l->onBeginEdit (*this); });
}
}
//------------------------------------------------------------------------
bool Value::performEdit (Type newValue)
{
if (newValue < 0. || newValue > 1.)
return false;
// if (newValue == value)
// return true;
value = newValue;
getListeners ().forEach ([this] (IValueListener* l) { l->onPerformEdit (*this, value); });
return true;
}
//------------------------------------------------------------------------
void Value::endEdit ()
{
vstgui_assert (editCount > 0);
--editCount;
if (editCount == 0)
{
getListeners ().forEach ([this] (IValueListener* l) { l->onEndEdit (*this); });
}
}
//------------------------------------------------------------------------
void Value::setActive (bool state)
{
if (state == active)
return;
active = state;
dispatchStateChange ();
}
//------------------------------------------------------------------------
bool Value::isActive () const { return active; }
//------------------------------------------------------------------------
Value::Type Value::getValue () const { return value; }
//------------------------------------------------------------------------
bool Value::isEditing () const { return editCount != 0; }
//------------------------------------------------------------------------
const IValueConverter& Value::getConverter () const { return *valueConverter.get (); }
//------------------------------------------------------------------------
void Value::dispatchStateChange ()
{
getListeners ().forEach ([this] (IValueListener* l) { l->onStateChange (*this); });
}
//------------------------------------------------------------------------
void Value::setValueConverter (const ValueConverterPtr& converter) { valueConverter = converter; }
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
StepValue::StepValue (const UTF8String& id, StepType initialSteps, Type initialValue,
const ValueConverterPtr& stringConverter)
: Value (id, initialValue, stringConverter), steps (initialSteps - 1)
{
vstgui_assert (initialSteps > 0);
}
//------------------------------------------------------------------------
bool StepValue::performEdit (Type newValue)
{
return Value::performEdit (stepToValue (valueToStep (newValue)));
}
//------------------------------------------------------------------------
StepValue::StepType StepValue::getSteps () const { return steps + 1; }
//------------------------------------------------------------------------
IValue::Type StepValue::stepToValue (StepType step) const
{
if (steps == 0)
return 0.;
return convertStepToValue (step, steps);
}
//------------------------------------------------------------------------
StepValue::StepType StepValue::valueToStep (IValue::Type value) const
{
return convertValueToStep (value, steps);
}
//------------------------------------------------------------------------
UTF8String StepValue::valueAsString (IValue::Type value) const
{
auto v = valueToStep (value);
return UTF8String (std::to_string (v));
}
//------------------------------------------------------------------------
IValue::Type StepValue::stringAsValue (const UTF8String& string) const
{
StepType v;
std::istringstream sstream (string.getString ());
sstream.imbue (std::locale::classic ());
sstream >> v;
if (sstream.fail () || v > steps)
return IValue::InvalidValue;
return stepToValue (v);
}
//------------------------------------------------------------------------
IValue::Type StepValue::plainToNormalized (IValue::Type plain) const
{
return stepToValue (static_cast<IStepValue::StepType> (plain));
}
//------------------------------------------------------------------------
IValue::Type StepValue::normalizedToPlain (IValue::Type normalized) const
{
return valueToStep (normalized);
}
//------------------------------------------------------------------------
const IValueConverter& StepValue::getConverter () const
{
if (!hasValueConverter ())
return *this;
return Value::getConverter ();
}
//------------------------------------------------------------------------
bool StepValue::setNumSteps (StepType numSteps)
{
if (numSteps == 0)
{
vstgui_assert (numSteps > 0, "numSteps must be greater than zero");
return false;
}
steps = numSteps - 1;
dispatchStateChange ();
return true;
}
//------------------------------------------------------------------------
StringListValue::StringListValue (const UTF8String& id, StepType initialSteps, Type initialValue,
const ValueConverterPtr& stringConverter)
: StepValue (id, initialSteps, initialValue, stringConverter)
{
}
//------------------------------------------------------------------------
bool StringListValue::setNumSteps (StepType numSteps)
{
if (getSteps () == numSteps)
return true;
if (numSteps == 0)
numSteps = 1;
StepValue::setNumSteps (numSteps);
return true;
}
//------------------------------------------------------------------------
bool StringListValue::updateStringList (const StringList& newStrings)
{
setValueConverter (std::make_shared<Detail::StringListValueConverter> (newStrings));
setNumSteps (static_cast<IStepValue::StepType> (newStrings.size ()));
return true;
}
//------------------------------------------------------------------------
bool StringListValue::updateString (size_t index, const StringType& string)
{
if (auto converter = dynamicPtrCast<Detail::StringListValueConverter> (getValueConverter ()))
if (converter->updateString (index, string))
{
dispatchStateChange ();
return true;
}
return false;
}
//------------------------------------------------------------------------
ValueConverterPtr getDefaultConverter ()
{
static ValueConverterPtr gInstance = std::make_shared<Detail::DefaultValueConverter> ();
return gInstance;
}
//------------------------------------------------------------------------
} // anonymous
} // Detail
//------------------------------------------------------------------------
namespace Value {
//------------------------------------------------------------------------
ValuePtr make (const UTF8String& id, IValue::Type initialValue,
const ValueConverterPtr& stringConverter)
{
vstgui_assert (id.empty () == false);
return std::make_shared<Detail::Value> (
id, initialValue,
stringConverter.get () ? stringConverter : Detail::getDefaultConverter ());
}
//------------------------------------------------------------------------
ValuePtr makeStepValue (const UTF8String& id, IStepValue::StepType numSteps,
IValue::Type initialValue, const ValueConverterPtr& stringConverter)
{
vstgui_assert (id.empty () == false);
vstgui_assert (numSteps > 0, "numSteps must be greater than 0");
if (numSteps == 0)
return {};
return std::make_shared<Detail::StepValue> (id, numSteps, initialValue, stringConverter);
}
//------------------------------------------------------------------------
ValuePtr makeStringListValue (const UTF8String& id,
const std::initializer_list<IStringListValue::StringType>& strings,
IValue::Type initialValue)
{
vstgui_assert (id.empty () == false);
return std::make_shared<Detail::StringListValue> (
id, static_cast<IStepValue::StepType> (strings.size ()), initialValue,
std::make_shared<Detail::StringListValueConverter> (strings));
}
//------------------------------------------------------------------------
ValuePtr makeStringListValue (const UTF8String& id, const IStringListValue::StringList& strings)
{
vstgui_assert (id.empty () == false);
return std::make_shared<Detail::StringListValue> (
id, static_cast<IStepValue::StepType> (strings.size ()), 0,
std::make_shared<Detail::StringListValueConverter> (strings));
}
//------------------------------------------------------------------------
ValuePtr makeStaticStringValue (const UTF8String& id, const UTF8String& value)
{
return std::make_shared<Detail::StaticStringValue> (id, value);
}
//------------------------------------------------------------------------
ValuePtr makeStaticStringValue (const UTF8String& id, UTF8String&& value)
{
return std::make_shared<Detail::StaticStringValue> (id, std::move (value));
}
//------------------------------------------------------------------------
ValuePtr makeStringValue (const UTF8String& id, const UTF8String& initialString)
{
return std::make_shared<Detail::StringValue> (id, initialString);
}
//------------------------------------------------------------------------
ValuePtr makeStringValue (const UTF8String& id, UTF8String&& initialString)
{
return std::make_shared<Detail::StringValue> (id, std::move (initialString));
}
//------------------------------------------------------------------------
ValueConverterPtr makePercentConverter ()
{
return std::make_shared<Detail::PercentValueConverter> ();
}
//------------------------------------------------------------------------
ValueConverterPtr makeRangeConverter (IValue::Type minValue, IValue::Type maxValue,
uint32_t stringPrecision)
{
return std::make_shared<Detail::RangeValueConverter> (minValue, maxValue, stringPrecision);
}
//------------------------------------------------------------------------
} // Value
} // Standalone
} // VSTGUI
@@ -0,0 +1,210 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../../application.h"
#include "../../../include/iappdelegate.h"
#include "../../../include/iapplication.h"
#include "../../../../lib/vstguiinit.h"
#include "../../../../lib/vstkeycode.h"
#include "../../../../lib/platform/linux/x11frame.h"
#include "../../../../lib/platform/linux/linuxfactory.h"
#include "../../../../lib/platform/common/fileresourceinputstream.h"
#include "gdkcommondirectories.h"
#include "gdkpreference.h"
#include "gdkwindow.h"
#include "gdkrunloop.h"
#include <gtkmm.h>
#include <libgen.h>
#include <unordered_map>
//------------------------------------------------------------------------
namespace std {
//------------------------------------------------------------------------
template<>
struct hash<VstKeyCode>
{
std::size_t operator() (const VstKeyCode& k) const
{
return ((hash<int32_t> () (k.character) ^ (hash<unsigned char> () (k.modifier) << 1)) >>
1) ^
(hash<unsigned char> () (k.virt) << 1);
}
};
//------------------------------------------------------------------------
} // std
//------------------------------------------------------------------------
bool operator== (const VstKeyCode& k1, const VstKeyCode& k2)
{
return k1.virt == k2.virt && k1.modifier == k2.modifier && k1.character == k2.character;
}
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
using namespace VSTGUI::Standalone::Detail;
//------------------------------------------------------------------------
Glib::RefPtr<Gtk::Application> app;
//------------------------------------------------------------------------
Glib::RefPtr<Gtk::Application> gtkApp ()
{
return app;
}
//------------------------------------------------------------------------
class Application
{
public:
bool init (int argc, char* argv[]);
int run ();
void quit ();
private:
void doCommandUpdate ();
void handleCommand (const CommandWithKey& command);
CommonDirectories commonDirectories;
Preference prefs;
bool isInitialized {false};
};
//------------------------------------------------------------------------
bool Application::init (int argc, char* argv[])
{
gdk_set_allowed_backends ("x11");
getPlatformFactory ().asLinuxFactory ()->setRunLoop (&RunLoop::instance ());
const auto& appInfo = IApplication::instance ().getDelegate ().getInfo ();
app = Gtk::Application::create (argc, argv, appInfo.uri.data ());
Glib::set_application_name (appInfo.name.getString ());
IApplication::CommandLineArguments cmdArgs;
for (auto i = 0; i < argc; ++i)
cmdArgs.push_back (argv[i]);
app->signal_startup ().connect ([cmdArgs = std::move (cmdArgs), this] () mutable {
char result[PATH_MAX];
ssize_t count = readlink ("/proc/self/exe", result, PATH_MAX);
if (count == -1)
::exit (-1);
std::string execPath = dirname (result);
getPlatformFactory ().asLinuxFactory ()->setResourcePath (execPath + "/Resources/");
PlatformCallbacks callbacks;
callbacks.quit = [this] () { quit (); };
callbacks.onCommandUpdate = [this] () { doCommandUpdate (); };
callbacks.showAlert = [] (const AlertBoxConfig& config) { return AlertResult::Error; };
callbacks.showAlertForWindow = [] (const AlertBoxForWindowConfig& config) {
if (config.callback)
config.callback (AlertResult::Error);
};
getPlatformFactory ().asLinuxFactory ()->setScheduleMainQueueTaskFunc ([] (auto&& task) {
auto idleSource = Glib::IdleSource::create ();
idleSource->set_priority (Glib::PRIORITY_DEFAULT);
idleSource->connect ([task = std::move (task), idleSource] () {
task ();
idleSource->destroy ();
return true;
});
idleSource->attach ();
});
auto appAccess = Detail::getApplicationPlatformAccess ();
vstgui_assert (appAccess);
IPlatformApplication::OpenFilesList openFilesList;
/* TODO: fill openFilesList */
appAccess->init ({prefs, commonDirectories, std::move (cmdArgs), std::move (callbacks),
std::move (openFilesList)});
isInitialized = true;
doCommandUpdate ();
});
return true;
}
//------------------------------------------------------------------------
int Application::run ()
{
return app->run ();
}
//------------------------------------------------------------------------
void Application::quit ()
{
app->quit ();
}
//------------------------------------------------------------------------
void Application::handleCommand (const CommandWithKey& command) {}
//------------------------------------------------------------------------
void Application::doCommandUpdate ()
{
if (!isInitialized)
return;
auto mainMenu = Gio::Menu::create ();
auto commandList = Detail::getApplicationPlatformAccess ()->getCommandList ();
for (auto& e : commandList)
{
auto subMenu = Gio::Menu::create ();
for (auto& command : e.second)
{
if (command.name == CommandName::MenuSeparator)
{
continue;
}
auto actionName = command.group.getString () + "." + command.name.getString ();
std::replace (actionName.begin (), actionName.end (), ' ', '_');
auto item = Gio::MenuItem::create (command.name.getString (), actionName);
if (command.defaultKey)
{
std::string accelKey ("<Primary>");
accelKey += command.defaultKey;
// TODO: map virtual characters
item->set_attribute_value ("accel",
Glib::Variant<Glib::ustring>::create (accelKey));
}
subMenu->append_item (item);
if (!app->has_action (actionName))
{
if (auto action = app->add_action (actionName,
[this, command] () { handleCommand (command); }))
{
}
}
}
mainMenu->append_submenu (e.first.getString (), subMenu);
}
app->set_menubar (mainMenu);
}
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
//------------------------------------------------------------------------
int main (int argc, char* argv[])
{
VSTGUI::init (nullptr);
VSTGUI::Standalone::Platform::GDK::Application app;
if (app.init (argc, argv))
{
auto result = app.run ();
VSTGUI::exit ();
return result;
}
return -1;
}
@@ -0,0 +1,21 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include <gtkmm.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
Glib::RefPtr<Gtk::Application> gtkApp ();
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,59 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "gdkcommondirectories.h"
#include "../../../include/iapplication.h"
#include "../../../include/iappdelegate.h"
#include <cstdlib>
#include <sys/stat.h>
#include <sys/types.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
//------------------------------------------------------------------------
Optional<UTF8String> CommonDirectories::get (CommonDirectoryLocation location,
const UTF8String& subDir,
bool create) const
{
// TODO:
UTF8String result;
switch (location)
{
case CommonDirectoryLocation::AppPreferencesPath:
{
auto home = getenv ("HOME");
if (home == nullptr)
return {};
result = home;
result += "/.config/" + IApplication::instance ().getDelegate ().getInfo ().uri + "/";
break;
}
}
if (result.empty ())
return {};
if (!subDir.empty ())
result += subDir + "/";
if (create)
{
struct stat s {};
if (stat (result.data (), &s) == 0)
{
if ((s.st_mode & S_IFMT) != S_IFDIR)
return {};
}
else if (mkdir (result.data (), 0755) != 0)
return {};
}
return Optional<UTF8String> (std::move (result));
}
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,28 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../include/icommondirectories.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
//------------------------------------------------------------------------
class CommonDirectories : public ICommonDirectories
{
public:
Optional<UTF8String> get (CommonDirectoryLocation location,
const UTF8String& subDir,
bool create = false) const override;
};
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,129 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "gdkpreference.h"
#include "../../../include/iapplication.h"
#include "../../../include/icommondirectories.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
namespace {
//------------------------------------------------------------------------
constexpr auto CreateTableSQL = R"__(
CREATE TABLE IF NOT EXISTS "store" (
"key" TEXT NOT NULL PRIMARY KEY,
"value" TEXT NOT NULL
)
)__";
//------------------------------------------------------------------------
constexpr auto GetValueSQL = R"__(SELECT "value" FROM "store" WHERE "key" IS )__";
constexpr auto SetValueSQL = R"__(INSERT INTO "store" VALUES )__";
constexpr auto DeleteValueSQL = R"__(DELETE FROM "store" WHERE key=)__";
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Preference::Preference () {}
//------------------------------------------------------------------------
Preference::~Preference () noexcept
{
if (db)
sqlite3_close (db);
}
//------------------------------------------------------------------------
bool Preference::set (const UTF8String& key, const UTF8String& value)
{
if (!prepare ())
return false;
if (get (key))
{
char* errorMsg = nullptr;
std::string sql = DeleteValueSQL;
sql += "\"" + key + "\"";
sqlite3_exec (db, sql.data (), nullptr, nullptr, &errorMsg);
if (errorMsg)
{
printf ("%s\n", errorMsg);
sqlite3_free (errorMsg);
return false;
}
}
char* errorMsg = nullptr;
std::string sql = SetValueSQL;
sql += "(\"" + key + "\",\"" + value + "\")";
sqlite3_exec (db, sql.data (), nullptr, nullptr, &errorMsg);
if (errorMsg)
{
printf ("%s\n", errorMsg);
sqlite3_free (errorMsg);
return false;
}
return true;
}
//------------------------------------------------------------------------
Optional<UTF8String> Preference::get (const UTF8String& key)
{
if (!prepare ())
return {};
std::string sql = GetValueSQL;
sql += "\"" + key + "\"";
UTF8String result;
char* errorMsg = nullptr;
sqlite3_exec (db, sql.data (),
[](void* userData, int numColumns, char** cols, char** colNames) -> int {
auto result = reinterpret_cast<UTF8String*> (userData);
if (numColumns > 0)
*result = cols[0];
return 0;
},
&result, &errorMsg);
if (errorMsg)
{
printf ("%s\n", errorMsg);
sqlite3_free (errorMsg);
return {};
}
if (result.empty ())
return {};
return Optional<UTF8String> (std::move (result));
}
//------------------------------------------------------------------------
bool Preference::prepare ()
{
if (db)
return true;
auto prefPath = IApplication::instance ().getCommonDirectories ().get (
CommonDirectoryLocation::AppPreferencesPath, "", true);
if (!prefPath)
return false;
*prefPath += "preferences.db";
if (sqlite3_open (prefPath->data (), &db) != 0)
return false;
char* errorMsg = nullptr;
sqlite3_exec (db, CreateTableSQL, [](void*, int, char**, char**) -> int { return 0; }, nullptr,
&errorMsg);
if (errorMsg)
{
printf ("%s\n", errorMsg);
sqlite3_free (errorMsg);
}
return true;
}
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,36 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../include/ipreference.h"
#include <sqlite3.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
//------------------------------------------------------------------------
class Preference : public IPreference
{
public:
Preference ();
~Preference () noexcept;
bool set (const UTF8String& key, const UTF8String& value) override;
Optional<UTF8String> get (const UTF8String& key) override;
private:
bool prepare ();
sqlite3* db{nullptr};
};
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,137 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "gdkrunloop.h"
#include <glib.h>
#include <vector>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
//------------------------------------------------------------------------
RunLoop& RunLoop::instance ()
{
static RunLoop instance;
return instance;
}
//------------------------------------------------------------------------
struct ExternalEventHandler
{
VSTGUI::IEventHandler* eventHandler{nullptr};
GSource* source{nullptr};
GIOChannel* ioChannel{nullptr};
};
//------------------------------------------------------------------------
struct ExternalTimerHandler
{
VSTGUI::ITimerHandler* timerHandler{nullptr};
GSource* source{nullptr};
};
//------------------------------------------------------------------------
struct RunLoop::Impl
{
using EventHandlerVector = std::vector<std::unique_ptr<ExternalEventHandler>>;
using TimerHandlerVector = std::vector<std::unique_ptr<ExternalTimerHandler>>;
GMainContext * mainContext{nullptr};
EventHandlerVector eventHandlers;
TimerHandlerVector timerHandlers;
};
//------------------------------------------------------------------------
RunLoop::RunLoop ()
{
impl = std::unique_ptr<Impl> (new Impl);
impl->mainContext = g_main_context_ref (g_main_context_default ());
}
//------------------------------------------------------------------------
RunLoop::~RunLoop () noexcept
{
g_main_context_unref (impl->mainContext);
}
//------------------------------------------------------------------------
static gboolean eventHandlerProc (GIOChannel* channel, GIOCondition condition, gpointer userData)
{
auto handler = static_cast<VSTGUI::IEventHandler*> (userData);
handler->onEvent ();
return G_SOURCE_CONTINUE;
};
//------------------------------------------------------------------------
bool RunLoop::registerEventHandler (int fd, IEventHandler* handler)
{
std::unique_ptr<ExternalEventHandler> eventHandler (new ExternalEventHandler);
eventHandler->eventHandler = handler;
eventHandler->ioChannel = g_io_channel_unix_new (fd);
eventHandler->source = g_io_create_watch (
eventHandler->ioChannel, static_cast<GIOCondition> (G_IO_IN | G_IO_ERR | G_IO_HUP));
g_source_set_callback (eventHandler->source, reinterpret_cast<GSourceFunc> (eventHandlerProc),
handler, nullptr);
g_source_attach (eventHandler->source, impl->mainContext);
impl->eventHandlers.emplace_back (std::move (eventHandler));
return true;
}
//------------------------------------------------------------------------
bool RunLoop::unregisterEventHandler (IEventHandler* handler)
{
auto it = std::find_if (impl->eventHandlers.begin (), impl->eventHandlers.end (),
[&](const auto& p) { return p->eventHandler == handler; });
if (it != impl->eventHandlers.end ())
{
g_source_destroy ((*it)->source);
g_io_channel_unref ((*it)->ioChannel);
impl->eventHandlers.erase (it);
return true;
}
return false;
}
//------------------------------------------------------------------------
bool RunLoop::registerTimer (uint64_t interval, ITimerHandler* handler)
{
std::unique_ptr<ExternalTimerHandler> timerHandler (new ExternalTimerHandler);
timerHandler->timerHandler = handler;
timerHandler->source = g_timeout_source_new (interval);
g_source_set_callback (timerHandler->source,
[](gpointer userData) -> gboolean {
auto handler = reinterpret_cast<ITimerHandler*> (userData);
handler->onTimer ();
return 1;
},
handler, nullptr);
g_source_attach (timerHandler->source, impl->mainContext);
impl->timerHandlers.emplace_back (std::move (timerHandler));
return true;
}
//------------------------------------------------------------------------
bool RunLoop::unregisterTimer (ITimerHandler* handler)
{
auto it = std::find_if (impl->timerHandlers.begin (), impl->timerHandlers.end (), [&] (const auto& p) {
return p->timerHandler == handler;
});
if (it != impl->timerHandlers.end ())
{
g_source_destroy ((*it)->source);
impl->timerHandlers.erase (it);
return true;
}
return false;
}
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,46 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../../lib/platform/linux/irunloop.h"
#include <memory>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
//------------------------------------------------------------------------
class RunLoop : public VSTGUI::IRunLoop
{
public:
using IEventHandler = VSTGUI::IEventHandler;
using ITimerHandler = VSTGUI::ITimerHandler;
static RunLoop& instance ();
RunLoop ();
~RunLoop () noexcept;
bool registerEventHandler (int fd, IEventHandler* handler) override;
bool unregisterEventHandler (IEventHandler* handler) override;
bool registerTimer (uint64_t interval, ITimerHandler* handler) override;
bool unregisterTimer (ITimerHandler* handler) override;
private:
void forget () override {}
void remember () override {}
struct Impl;
std::unique_ptr<Impl> impl;
};
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,470 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "gdkwindow.h"
#include "gdkapplication.h"
#include "gdkrunloop.h"
#include "../../application.h"
#include "../../../include/iasync.h"
#include "../../../../lib/cframe.h"
#include "../../../../lib/platform/platform_x11.h"
#include <gtkmm.h>
#include <gdk/gdkx.h>
#include <cassert>
#include <vector>
#define VSTGUI_LOG_X11_WINDOW (DEBUG && 1)
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
//------------------------------------------------------------------------
namespace {
/* XEMBED messages */
enum class XEmbedMessage
{
EMBEDDED_NOTIFY = 0,
WINDOW_ACTIVATE = 1,
WINDOW_DEACTIVATE = 2,
REQUEST_FOCUS = 3,
FOCUS_IN = 4,
FOCUS_OUT = 5,
FOCUS_NEXT = 6,
FOCUS_PREV = 7,
/* 8-9 were used for GRAB_KEY/UNGRAB_KEY */
MODALITY_ON = 10,
MODALITY_OFF = 11,
REGISTER_ACCELERATOR = 12,
UNREGISTER_ACCELERATOR = 13,
ACTIVATE_ACCELERATOR = 14,
};
//------------------------------------------------------------------------
void sendXEmbedProtocolMessage (::Window receiver, ::Window parentWindow, XEmbedMessage message,
uint32_t detail = 0, uint32_t xEmbedVersion = 1)
{
auto xDisplay = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
auto xEmbedAtom = XInternAtom (xDisplay, "_XEMBED", true);
if (xEmbedAtom == None)
return;
XEvent ev {};
ev.xclient.type = ClientMessage;
ev.xclient.window = receiver;
ev.xclient.message_type = xEmbedAtom;
ev.xclient.format = 32;
ev.xclient.display = xDisplay;
ev.xclient.data.l[0] = CurrentTime;
ev.xclient.data.l[1] = static_cast<long> (message);
ev.xclient.data.l[2] = detail;
ev.xclient.data.l[3] = parentWindow;
ev.xclient.data.l[4] = xEmbedVersion;
XSendEvent (xDisplay, receiver, False, NoEventMask, &ev);
XSync (xDisplay, False);
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
class Window : public IGdkWindow,
public IWindow,
public std::enable_shared_from_this<Window>
{
public:
~Window () noexcept override;
bool init (const WindowConfiguration& config, IWindowDelegate& delegate);
CPoint getSize () const override;
CPoint getPosition () const override;
double getScaleFactor () const override;
void setSize (const CPoint& newSize) override;
void setPosition (const CPoint& newPosition) override;
void setTitle (const UTF8String& newTitle) override;
void setRepresentedPath (const UTF8String& path) override;
WindowStyle changeStyle (WindowStyle stylesToAdd, WindowStyle stylesToRemove) override;
void show () override;
void hide () override;
void close () override;
void activate () override;
void center () override;
PlatformType getPlatformType () const override;
void* getPlatformHandle () const override;
PlatformFrameConfigPtr prepareFrameConfig (PlatformFrameConfigPtr&& controllerConfig) override;
void onSetContentView (CFrame* frame) override;
private:
void updateGeometryHints ();
void handleEventConfigure (GdkEventConfigure* event);
void sendXEmbedMessage (XEmbedMessage msg, uint32_t data = 0);
static GdkFilterReturn xEventFilter (GdkXEvent* xevent, GdkEvent* event, gpointer data);
CPoint lastPos;
CPoint lastSize;
bool isShown {false};
WindowStyle style;
WindowType type;
IWindowDelegate* delegate {nullptr};
Gtk::ApplicationWindow gtkWindow;
CFrame* contentView {nullptr};
};
//------------------------------------------------------------------------
Window::~Window () noexcept
{
gtkApp ()->remove_window (gtkWindow);
}
//------------------------------------------------------------------------
bool Window::init (const WindowConfiguration& config, IWindowDelegate& inDelegate)
{
gtkWindow.set_events (Gdk::ALL_EVENTS_MASK);
gtkWindow.set_decorated (config.style.hasBorder ());
gtkWindow.set_deletable (config.style.canClose ());
if (config.type == WindowType::Document)
{
gtkWindow.set_type_hint (Gdk::WINDOW_TYPE_HINT_NORMAL);
gtkWindow.set_skip_taskbar_hint (false);
gtkWindow.set_show_menubar (config.style.hasBorder ());
}
else
{
gtkWindow.set_type_hint (Gdk::WINDOW_TYPE_HINT_POPUP_MENU);
gtkWindow.set_skip_taskbar_hint (true);
}
gtkWindow.set_can_focus (true);
gtkWindow.set_title (config.title.getString ());
gtkWindow.signal_map ().connect ([this] () { delegate->onShow (); });
gtkWindow.signal_configure_event ().connect (
[this] (GdkEventConfigure* event) {
handleEventConfigure (event);
return false;
},
false);
gtkWindow.signal_delete_event ().connect ([this] (GdkEventAny*) {
if (delegate->canClose ())
{
close ();
return true;
}
return false;
});
gtkWindow.signal_focus_in_event ().connect ([this] (GdkEventFocus*) {
delegate->onActivated ();
sendXEmbedMessage (XEmbedMessage::WINDOW_ACTIVATE);
sendXEmbedMessage (XEmbedMessage::FOCUS_IN);
return true;
});
gtkWindow.signal_focus_out_event ().connect ([this] (GdkEventFocus*) {
delegate->onDeactivated ();
sendXEmbedMessage (XEmbedMessage::FOCUS_OUT);
sendXEmbedMessage (XEmbedMessage::WINDOW_DEACTIVATE);
if (type == WindowType::Popup)
{
if (!Detail::getApplicationPlatformAccess ()->dontClosePopupOnDeactivation (this))
close ();
}
return true;
});
if (style.isMovableByWindowBackground ())
{
gtkWindow.signal_button_press_event ().connect ([this] (GdkEventButton* event) {
gtkWindow.begin_move_drag (event->button, event->x_root, event->y_root, event->time);
return true;
});
}
gtkApp ()->add_window (gtkWindow);
style = config.style;
type = config.type;
lastSize = config.size;
delegate = &inDelegate;
auto widget = reinterpret_cast<GtkWidget*> (gtkWindow.gobj ());
gtk_widget_realize (widget);
auto gdkWindow = gtkWindow.get_window ();
vstgui_assert (gdkWindow);
gdkWindow->add_filter (xEventFilter, this);
gtkWindow.set_data ("VSTGUIWindow", this);
return true;
}
//------------------------------------------------------------------------
GdkFilterReturn Window::xEventFilter (GdkXEvent* xevent, GdkEvent* event, gpointer data)
{
GdkFilterReturn result = GDK_FILTER_CONTINUE;
auto e = static_cast<XEvent*> (xevent);
switch (e->type)
{
case CreateNotify:
{
auto self = reinterpret_cast<Window*> (data);
::Window topLevelWindow = reinterpret_cast<::Window> (self->getPlatformHandle ());
if (e->xcreatewindow.window == topLevelWindow)
break;
auto childWindow = e->xcreatewindow.window;
auto xDisplay = gdk_x11_display_get_xdisplay (gdk_display_get_default ());
XMapWindow (xDisplay, childWindow);
sendXEmbedProtocolMessage (childWindow, topLevelWindow, XEmbedMessage::EMBEDDED_NOTIFY);
#if 0 // Do not commit
XUnmapWindow (xDisplay, childWindow);
#endif
break;
}
}
return result;
}
//------------------------------------------------------------------------
void Window::updateGeometryHints () {}
//------------------------------------------------------------------------
CPoint Window::getSize () const
{
if (!isShown)
return lastSize;
auto scaleFactor = getScaleFactor ();
CPoint size;
size.x = gtkWindow.get_width () / scaleFactor;
size.y = gtkWindow.get_height () / scaleFactor;
#if VSTGUI_LOG_X11_WINDOW
DebugPrint ("Window::getSize (): %d, %d\n", static_cast<int> (size.x), static_cast<int> (size.y));
#endif
return size;
}
//------------------------------------------------------------------------
CPoint Window::getPosition () const
{
if (!isShown)
return lastPos;
auto scaleFactor = getScaleFactor ();
int x, y;
gtkWindow.get_position (x, y);
CPoint result (x / scaleFactor, y / scaleFactor);
#if VSTGUI_LOG_X11_WINDOW
DebugPrint ("Window::getPosition (): %d, %d\n", static_cast<int> (result.x), static_cast<int> (result.y));
#endif
return result;
}
//------------------------------------------------------------------------
double Window::getScaleFactor () const
{
auto factor = static_cast<double> (gtkWindow.get_scale_factor ());
return factor;
}
//------------------------------------------------------------------------
void Window::setSize (const CPoint& newSize)
{
auto scaleFactor = getScaleFactor ();
auto width = static_cast<int> (std::ceil (newSize.x * scaleFactor));
auto height = static_cast<int> (std::ceil (newSize.y * scaleFactor));
#if VSTGUI_LOG_X11_WINDOW
DebugPrint ("Window::setSize (): %d - %d\n", width, height);
#endif
gtkWindow.resize (width, height);
if (!isShown)
lastSize = newSize;
}
//------------------------------------------------------------------------
void Window::setPosition (const CPoint& newPosition)
{
auto scaleFactor = getScaleFactor ();
auto x = static_cast<int> (std::floor (newPosition.x * scaleFactor));
auto y = static_cast<int> (std::floor (newPosition.y * scaleFactor));
#if VSTGUI_LOG_X11_WINDOW
DebugPrint ("Window::setPosition (): %d - %d\n", x, y);
#endif
gtkWindow.move (x, y);
if (!isShown)
lastPos = newPosition;
}
//------------------------------------------------------------------------
void Window::setTitle (const UTF8String& newTitle)
{
gtkWindow.set_title (newTitle.getString ());
}
//------------------------------------------------------------------------
void Window::setRepresentedPath (const UTF8String& path) {}
//------------------------------------------------------------------------
WindowStyle Window::changeStyle (WindowStyle stylesToAdd, WindowStyle stylesToRemove)
{
// TODO: Implementation
return style;
}
//------------------------------------------------------------------------
void Window::show ()
{
isShown = true;
lastPos = lastSize = {};
updateGeometryHints ();
gtkWindow.show_all ();
activate ();
#if 0
if (type == WindowType::Popup)
gtkWindow.focus (0);
#endif
}
//------------------------------------------------------------------------
void Window::hide ()
{
isShown = false;
gtkWindow.hide ();
}
//------------------------------------------------------------------------
void Window::close ()
{
auto self = shared_from_this ();
auto call = [self] () {
self->gtkWindow.close ();
self->delegate->onClosed ();
};
Async::schedule (Async::mainQueue (), call);
}
//------------------------------------------------------------------------
void Window::activate ()
{
gtkWindow.present ();
}
//------------------------------------------------------------------------
void Window::center () {}
//------------------------------------------------------------------------
PlatformType Window::getPlatformType () const
{
return PlatformType::kX11EmbedWindowID;
}
//------------------------------------------------------------------------
void* Window::getPlatformHandle () const
{
if (auto gdkWindow = gtkWindow.get_window ())
{
auto ptr = const_cast<GdkWindow*> (gdkWindow->gobj ());
return reinterpret_cast<void*> (gdk_x11_window_get_xid (ptr));
}
return nullptr;
}
//------------------------------------------------------------------------
PlatformFrameConfigPtr Window::prepareFrameConfig (PlatformFrameConfigPtr&& controllerConfig)
{
if (controllerConfig)
{
if (auto config = dynamicPtrCast<X11::FrameConfig> (controllerConfig))
{
config->runLoop = &RunLoop::instance ();
return std::move (config);
}
}
auto config = std::make_shared<X11::FrameConfig> ();
config->runLoop = &RunLoop::instance ();
return config;
}
//------------------------------------------------------------------------
void Window::onSetContentView (CFrame* newFrame)
{
contentView = newFrame;
if (contentView)
{
contentView->setZoom (getScaleFactor ());
}
}
//------------------------------------------------------------------------
void Window::sendXEmbedMessage (XEmbedMessage msg, uint32_t data)
{
if (auto x11Frame = dynamic_cast<X11::IX11Frame*> (contentView->getPlatformFrame ()))
{
sendXEmbedProtocolMessage (x11Frame->getX11WindowID (),
reinterpret_cast<::Window> (getPlatformHandle ()), msg, data);
}
}
//------------------------------------------------------------------------
void Window::handleEventConfigure (GdkEventConfigure* event)
{
auto scaleFactor = getScaleFactor ();
CPoint newPos (event->x, event->y);
CPoint newSize (event->width, event->height);
CPoint constraintSize = delegate->constraintSize (newSize);
if (constraintSize != newSize)
{
setSize (constraintSize);
newSize = constraintSize;
}
if (newPos != lastPos)
{
lastPos = newPos;
#if VSTGUI_LOG_X11_WINDOW
DebugPrint ("Window::onPositionChanged (): %d - %d\n", static_cast<int> (newPos.x), static_cast<int> (newPos.y));
#endif
delegate->onPositionChanged (newPos);
}
if (newSize != lastSize)
{
lastSize = newSize;
#if VSTGUI_LOG_X11_WINDOW
DebugPrint ("Window::onSizeChanged (): %d - %d\n", static_cast<int> (newSize.x), static_cast<int> (newSize.y));
#endif
delegate->onSizeChanged (newSize);
if (contentView)
{
newSize.x *= scaleFactor;
newSize.y *= scaleFactor;
contentView->setSize (newSize.x, newSize.y);
}
}
}
//------------------------------------------------------------------------
} // GDK
//------------------------------------------------------------------------
WindowPtr makeWindow (const WindowConfiguration& config, IWindowDelegate& delegate)
{
auto result = std::make_shared<GDK::Window> ();
if (result->init (config, delegate))
return result;
return nullptr;
}
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,31 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../iplatformwindow.h"
extern "C"
{
typedef union _GdkEvent GdkEvent;
typedef struct _GdkWindow GdkWindow;
};
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace GDK {
//------------------------------------------------------------------------
class IGdkWindow : public Interface
{
public:
};
//------------------------------------------------------------------------
} // GDK
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,67 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../lib/platform/iplatformframe.h"
#include "../../include/icommand.h"
#include "../../include/iwindow.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
//------------------------------------------------------------------------
class IWindowDelegate : public ICommandHandler
{
public:
virtual CPoint constraintSize (const CPoint& newSize) = 0;
virtual void onSizeChanged (const CPoint& newSize) = 0;
virtual void onPositionChanged (const CPoint& newPosition) = 0;
virtual void onShow () = 0;
virtual void onHide () = 0;
virtual void onClosed () = 0;
virtual bool canClose () = 0;
virtual void onActivated () = 0;
virtual void onDeactivated () = 0;
};
//------------------------------------------------------------------------
class IWindow : public Interface
{
public:
virtual CPoint getSize () const = 0;
virtual CPoint getPosition () const = 0;
virtual double getScaleFactor () const = 0;
virtual void setSize (const CPoint& newSize) = 0;
virtual void setPosition (const CPoint& newPosition) = 0;
virtual void setTitle (const UTF8String& newTitle) = 0;
virtual void setRepresentedPath (const UTF8String& path) = 0;
virtual WindowStyle changeStyle (WindowStyle stylesToAdd, WindowStyle stylesToRemove) = 0;
virtual void show () = 0;
virtual void hide () = 0;
virtual void close () = 0;
virtual void activate () = 0;
virtual void center () = 0;
virtual PlatformType getPlatformType () const = 0;
virtual void* getPlatformHandle () const = 0;
virtual PlatformFrameConfigPtr prepareFrameConfig (PlatformFrameConfigPtr&& controllerConfig) = 0;
virtual void onSetContentView (CFrame* frame) = 0;
};
//------------------------------------------------------------------------
using WindowPtr = std::shared_ptr<IWindow>;
//------------------------------------------------------------------------
WindowPtr makeWindow (const WindowConfiguration& config, IWindowDelegate& delegate);
//------------------------------------------------------------------------
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,15 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#import "../../application.h"
#import <Foundation/Foundation.h>
//------------------------------------------------------------------------
@interface VSTGUICommand : NSObject
@property VSTGUI::Standalone::Detail::CommandWithKey cmd;
- (const VSTGUI::Standalone::Detail::CommandWithKey&)command;
@end
@@ -0,0 +1,15 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "VSTGUICommand.h"
//------------------------------------------------------------------------
@implementation VSTGUICommand
- (const VSTGUI::Standalone::Detail::CommandWithKey&)command
{
return self->_cmd;
}
@end
@@ -0,0 +1,733 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "../../../../lib/platform/mac/cocoa/cocoahelpers.h"
#import "../../../../lib/platform/mac/macfactory.h"
#import "../../../../lib/vstguiinit.h"
#import "../../../include/iappdelegate.h"
#import "../../../include/iapplication.h"
#import "../../../include/iasync.h"
#import "../../application.h"
#import "../../genericalertbox.h"
#import "../../shareduiresources.h"
#import "../../window.h"
#import "VSTGUICommand.h"
#import "maccommondirectories.h"
#import "macpreference.h"
#import "macutilities.h"
#import "macwindow.h"
#import <Cocoa/Cocoa.h>
#if __has_feature(nullability) == 0
static_assert (false, "Need newer clang compiler!");
#endif
#define VSTGUI_STANDALONE_USE_GENERIC_ALERTBOX_ON_MACOS 0
//------------------------------------------------------------------------
@interface VSTGUIApplicationDelegate : NSObject <NSApplicationDelegate>
{
VSTGUI::Standalone::Platform::Mac::MacPreference prefs;
VSTGUI::Standalone::Platform::Mac::CommonDirectories commonDirecories;
}
@property NSArray<NSString*>* _Nullable startupOpenFiles;
@property BOOL hasFinishedLaunching;
@property BOOL hasTriggeredSetupMainMenu;
@end
using namespace VSTGUI::Standalone;
using VSTGUI::Standalone::Platform::Mac::IMacWindow;
using VSTGUI::Standalone::Detail::IPlatformApplication;
using VSTGUI::Standalone::Detail::CommandWithKey;
using VSTGUI::Standalone::Detail::IPlatformWindowAccess;
using CommandWithKeyList = VSTGUI::Standalone::Detail::IPlatformApplication::CommandWithKeyList;
using VSTGUI::Standalone::Detail::PlatformCallbacks;
//------------------------------------------------------------------------
static CommandWithKeyList getCommandList (const char* _Nonnull group)
{
for (auto& e : Detail::getApplicationPlatformAccess ()->getCommandList ())
{
if (e.first == group)
return e.second;
}
return {};
}
//------------------------------------------------------------------------
@implementation VSTGUIApplicationDelegate
//------------------------------------------------------------------------
- (instancetype _Nonnull)init
{
self = [super init];
if (self)
{
}
return self;
}
//------------------------------------------------------------------------
- (NSApplicationTerminateReply)applicationShouldTerminate:(nonnull NSApplication*)sender
{
if (Detail::getApplicationPlatformAccess ()->canQuit ())
return NSTerminateNow;
return NSTerminateCancel;
}
//------------------------------------------------------------------------
- (IBAction)showAboutDialog:(nullable id)sender
{
if (IApplication::instance ().getDelegate ().hasAboutDialog ())
IApplication::instance ().getDelegate ().showAboutDialog ();
else
[NSApp orderFrontStandardAboutPanel:sender];
}
//------------------------------------------------------------------------
- (IBAction)showPreferenceDialog:(nullable id)sender
{
IApplication::instance ().getDelegate ().showPreferenceDialog ();
}
//------------------------------------------------------------------------
- (IBAction)processCommand:(nullable id)sender
{
if (VSTGUICommand* command = [sender representedObject])
Detail::getApplicationPlatformAccess ()->handleCommand ([command command]);
}
//------------------------------------------------------------------------
- (void)showHelp:(nullable id)sender
{
}
//------------------------------------------------------------------------
- (BOOL)validateMenuItem:(nonnull NSMenuItem*)menuItem
{
if (menuItem.action == @selector (showPreferenceDialog:))
{
if (!IApplication::instance ().getDelegate ().hasPreferenceDialog ())
{
return NO;
}
return YES;
}
else if (menuItem.action == @selector (showAboutDialog:))
{
return YES;
}
else if (VSTGUICommand* command = menuItem.representedObject)
{
return Detail::getApplicationPlatformAccess ()->canHandleCommand ([command command]);
}
else if (menuItem.action == @selector (visualizeRedrawAreas:) || menuItem.action == @selector (useAsynchronousCALayerDrawing:))
{
return YES;
}
return NO;
}
//------------------------------------------------------------------------
- (nonnull SEL)selectorForCommand:(const CommandWithKey&)command
{
if (command == Commands::CloseWindow)
return @selector (performClose:);
else if (command == Commands::Undo)
return @selector (undo);
else if (command == Commands::Redo)
return @selector (redo);
else if (command == Commands::Cut)
return @selector (cut:);
else if (command == Commands::Copy)
return @selector (copy:);
else if (command == Commands::Paste)
return @selector (paste:);
else if (command == Commands::Delete)
return @selector (delete:);
else if (command == Commands::SelectAll)
return @selector (selectAll:);
return @selector (processCommand:);
}
//------------------------------------------------------------------------
- (nonnull NSMenuItem*)createMenuItemFromCommand:(const CommandWithKey&)command
{
if (command.name == CommandName::MenuSeparator)
return [NSMenuItem separatorItem];
NSMenuItem* item = [NSMenuItem new];
item.title = stringFromUTF8String (command.name);
item.action = [self selectorForCommand:command];
if (command.defaultKey)
{
item.keyEquivalent =
[NSString stringWithCharacters:reinterpret_cast<const unichar*> (&command.defaultKey)
length:1];
}
VSTGUICommand* representedObject = [VSTGUICommand new];
representedObject.cmd = command;
item.representedObject = representedObject;
return item;
}
//------------------------------------------------------------------------
- (nonnull NSString*)appName
{
NSDictionary* dict = [[NSBundle mainBundle] infoDictionary];
return dict[(@"CFBundleName")];
}
//------------------------------------------------------------------------
- (nonnull NSMenu*)createAppMenu
{
NSString* appName = [self appName];
NSMenu* menu = [[NSMenu alloc] initWithTitle:appName];
[menu addItemWithTitle:[NSLocalizedString (@"About ", "Menu Item")
stringByAppendingString:appName]
action:@selector (showAboutDialog:)
keyEquivalent:@""];
[menu addItem:[NSMenuItem separatorItem]];
[menu addItemWithTitle:NSLocalizedString (@"Preferences...", "Menu Item")
action:@selector (showPreferenceDialog:)
keyEquivalent:@","];
[menu addItem:[NSMenuItem separatorItem]];
auto commandList = getCommandList (CommandGroup::Application);
if (!commandList.empty ())
{
for (auto& command : commandList)
{
if (command != Commands::About && command != Commands::Preferences &&
command != Commands::Quit && command != Commands::Help)
{
[menu addItem:[self createMenuItemFromCommand:command]];
}
}
}
[menu addItem:[NSMenuItem separatorItem]];
[menu
addItemWithTitle:[NSLocalizedString (@"Hide ", "Menu Item") stringByAppendingString:appName]
action:@selector (hide:)
keyEquivalent:@"h"];
[menu addItemWithTitle:NSLocalizedString (@"Hide Others", "Menu Item")
action:@selector (hideOtherApplications:)
keyEquivalent:@""];
[menu addItemWithTitle:NSLocalizedString (@"Show All", "Menu Item")
action:@selector (unhideAllApplications:)
keyEquivalent:@""];
[menu addItem:[NSMenuItem separatorItem]];
[menu
addItemWithTitle:[NSLocalizedString (@"Quit ", "Menu Item") stringByAppendingString:appName]
action:@selector (terminate:)
keyEquivalent:@"q"];
return menu;
}
//------------------------------------------------------------------------
- (void)fillMenu:(nonnull NSMenu*)menu fromCommandList:(const CommandWithKeyList&)commandList
{
for (auto& command : commandList)
{
[menu addItem:[self createMenuItemFromCommand:command]];
}
}
//------------------------------------------------------------------------
- (nonnull NSMenu*)createWindowsMenu
{
NSMenu* menu =
[[NSMenu alloc] initWithTitle:NSLocalizedString (@"Window", "Window Menu Title")];
[menu addItemWithTitle:NSLocalizedString (@"Minimize", "Menu Item in Window Menu")
action:@selector (performMiniaturize:)
keyEquivalent:@"m"];
[menu addItemWithTitle:NSLocalizedString (@"Zoom", "Menu Item in Window Menu")
action:@selector (performZoom:)
keyEquivalent:@""];
NSMenuItem* item =
[menu addItemWithTitle:NSLocalizedString (@"Fullscreen", "Menu Item in Window Menu")
action:@selector (toggleFullScreen:)
keyEquivalent:@"f"];
item.keyEquivalentModifierMask =
MacEventModifier::CommandKeyMask | MacEventModifier::ControlKeyMask;
[menu addItem:[NSMenuItem separatorItem]];
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 && __clang_major__ >= 9
if (@available (macOS 10.12, *))
{
item = [menu
addItemWithTitle:NSLocalizedString (@"Show Previous Tab", "Menu Item in Window Menu")
action:@selector (selectPreviousTab:)
keyEquivalent:@"\t"];
item.keyEquivalentModifierMask =
MacEventModifier::ShiftKeyMask | MacEventModifier::ControlKeyMask;
item =
[menu addItemWithTitle:NSLocalizedString (@"Show Next Tab", "Menu Item in Window Menu")
action:@selector (selectNextTab:)
keyEquivalent:@"\t"];
item.keyEquivalentModifierMask = MacEventModifier::ControlKeyMask;
[menu addItemWithTitle:NSLocalizedString (@"Move Tab To New Window",
"Menu Item in Window Menu")
action:@selector (moveTabToNewWindow:)
keyEquivalent:@""];
[menu addItemWithTitle:NSLocalizedString (@"Merge All Windows", "Menu Item in Window Menu")
action:@selector (mergeAllWindows:)
keyEquivalent:@""];
item =
[menu addItemWithTitle:NSLocalizedString (@"Show All Tabs", "Menu Item in Window Menu")
action:@selector (toggleTabOverview:)
keyEquivalent:@"\\"];
item.keyEquivalentModifierMask =
MacEventModifier::ShiftKeyMask | MacEventModifier::CommandKeyMask;
[menu addItem:[NSMenuItem separatorItem]];
}
#endif
[menu addItemWithTitle:NSLocalizedString (@"Bring All to Front", "Menu Item in Window Menu")
action:@selector (arrangeInFront:)
keyEquivalent:@""];
[menu addItem:[NSMenuItem separatorItem]];
return menu;
}
//------------------------------------------------------------------------
- (nonnull NSMenu*)createHelpMenu
{
NSMenu* menu = [[NSMenu alloc] initWithTitle:NSLocalizedString (@"Help", "Help Menu Title")];
return menu;
}
//------------------------------------------------------------------------
- (void)setupMainMenu
{
NSMenu* mainMenu = [NSApp mainMenu];
NSMenuItem* appMenuItem = nil;
if (mainMenu == nil || mainMenu.itemArray.count == 1)
{
if (mainMenu == nil)
{
mainMenu = [NSMenu new];
[NSApp setMainMenu:mainMenu];
appMenuItem = [[NSMenuItem alloc] initWithTitle:@"App" action:nil keyEquivalent:@""];
[mainMenu addItem:appMenuItem];
}
NSMenuItem* item =
[[NSMenuItem alloc] initWithTitle:NSLocalizedString (@"Window", "Menu Name")
action:nullptr
keyEquivalent:@""];
NSMenu* windowsMenu = [self createWindowsMenu];
[NSApp setWindowsMenu:windowsMenu];
item.submenu = windowsMenu;
[mainMenu addItem:item];
item = [[NSMenuItem alloc] initWithTitle:NSLocalizedString (@"Help", "Menu Name")
action:nullptr
keyEquivalent:@""];
NSMenu* helpMenu = [self createHelpMenu];
[NSApp setHelpMenu:helpMenu];
item.submenu = helpMenu;
[mainMenu addItem:item];
}
else
{
appMenuItem = [mainMenu itemAtIndex:0];
}
appMenuItem.submenu = [self createAppMenu];
auto commandList = Detail::getApplicationPlatformAccess ()->getCommandList ();
for (auto& e : commandList)
{
if (e.first == CommandGroup::Window)
{
NSMenu* windowsMenu = [NSApp windowsMenu];
for (auto& command : e.second)
{
NSString* title = stringFromUTF8String (command.name);
NSMenuItem* item = [windowsMenu itemWithTitle:title];
if (!item)
[windowsMenu addItem:[self createMenuItemFromCommand:command]];
}
}
else if (e.first == CommandGroup::Application)
{
for (auto& cmd : e.second)
{
if (cmd.name == CommandName::Help)
{
NSMenu* helpMenu = [NSApp helpMenu];
NSMenuItem* item = [helpMenu itemWithTitle:stringFromUTF8String (cmd.name)];
if (!item)
{
item = [self createMenuItemFromCommand:cmd];
NSString* appName = [self appName];
item.title = [appName
stringByAppendingString:NSLocalizedString (@" Help", "Menu Item")];
[helpMenu addItem:item];
}
}
}
}
else
{
NSString* title = stringFromUTF8String (e.first);
NSMenuItem* item = [mainMenu itemWithTitle:title];
if (!item)
{
item = [[NSMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
[mainMenu addItem:item];
NSMenu* menu = [[NSMenu alloc] initWithTitle:title];
item.submenu = menu;
}
else
[item.submenu removeAllItems];
[self fillMenu:item.submenu fromCommandList:e.second];
}
}
NSMenuItem* editMenu = [mainMenu itemWithTitle:NSLocalizedString (@"Edit", "Menu Name")];
if (editMenu && editMenu.submenu)
{
NSMenuItem* showCharacterPanelItem =
[editMenu.submenu itemWithTitle:NSLocalizedString (@"Characters", "Menu Item")];
if (showCharacterPanelItem == nil)
{
[editMenu.submenu addItem:[NSMenuItem separatorItem]];
[editMenu.submenu
addItemWithTitle:NSLocalizedString (@"Emoji & Symbols", "Menu Item in Edit Menu")
action:@selector (orderFrontCharacterPalette:)
keyEquivalent:@""];
}
}
NSMenuItem* debugMenu = [mainMenu itemWithTitle:@"Debug"];
if (debugMenu && debugMenu.submenu && [debugMenu.submenu itemWithTitle:@"Color Panel"] == nil)
{
[debugMenu.submenu addItem:[NSMenuItem separatorItem]];
[debugMenu.submenu addItemWithTitle:@"Color Panel"
action:@selector (orderFrontColorPanel:)
keyEquivalent:@""];
[debugMenu.submenu addItem:[NSMenuItem separatorItem]];
[debugMenu.submenu addItemWithTitle:@"Use Asynchronous CALayer Drawing"
action:@selector (useAsynchronousCALayerDrawing:)
keyEquivalent:@""];
[debugMenu.submenu addItemWithTitle:@"Visualize Redraw Areas"
action:@selector (visualizeRedrawAreas:)
keyEquivalent:@""];
[self updateDebugMenuItems];
}
// move Windows menu to the end
if (auto* windowsMenuItem = [mainMenu itemWithTitle:NSLocalizedString (@"Window", "Menu Name")])
{
[mainMenu removeItem:windowsMenuItem];
[mainMenu addItem:windowsMenuItem];
}
// move Help menu to the end
if (auto* helpMenuItem = [mainMenu itemWithTitle:NSLocalizedString (@"Help", "Menu Name")])
{
[mainMenu removeItem:helpMenuItem];
[mainMenu addItem:helpMenuItem];
}
}
//------------------------------------------------------------------------
- (void)triggerSetupMainMenu
{
if (self.hasTriggeredSetupMainMenu)
return;
self.hasTriggeredSetupMainMenu = YES;
Async::schedule (Async::mainQueue (), [self] () {
[self setupMainMenu];
self.hasTriggeredSetupMainMenu = NO;
});
}
//------------------------------------------------------------------------
- (void)visualizeRedrawAreas:(id)sender
{
auto state = VSTGUI::getPlatformFactory ().asMacFactory ()->enableVisualizeRedrawAreas ();
VSTGUI::getPlatformFactory ().asMacFactory ()->enableVisualizeRedrawAreas (!state);
[self updateDebugMenuItems];
}
//------------------------------------------------------------------------
- (void)useAsynchronousCALayerDrawing:(id)sender
{
auto state = VSTGUI::getPlatformFactory ().asMacFactory ()->getUseAsynchronousLayerDrawing ();
VSTGUI::getPlatformFactory ().asMacFactory ()->setUseAsynchronousLayerDrawing (!state);
[self updateDebugMenuItems];
}
//------------------------------------------------------------------------
- (void)updateDebugMenuItems
{
NSMenuItem* debugMenu = [NSApp.mainMenu itemWithTitle:@"Debug"];
if (debugMenu && debugMenu.submenu)
{
if (auto item = [debugMenu.submenu itemWithTitle:@"Visualize Redraw Areas"])
{
auto state =
VSTGUI::getPlatformFactory ().asMacFactory ()->enableVisualizeRedrawAreas ();
item.state = state ? NSControlStateValueOn : NSControlStateValueOff;
}
if (auto item = [debugMenu.submenu itemWithTitle:@"Use Asynchronous CALayer Drawing"])
{
auto state =
VSTGUI::getPlatformFactory ().asMacFactory ()->getUseAsynchronousLayerDrawing ();
item.state = state ? NSControlStateValueOn : NSControlStateValueOff;
}
}
}
#if !VSTGUI_STANDALONE_USE_GENERIC_ALERTBOX_ON_MACOS
//------------------------------------------------------------------------
- (nonnull NSAlert*)createAlert:(const AlertBoxConfig&)config
{
NSAlert* alert = [NSAlert new];
if (!config.headline.empty ())
alert.messageText = stringFromUTF8String (config.headline);
if (!config.description.empty ())
alert.informativeText = stringFromUTF8String (config.description);
[alert addButtonWithTitle:stringFromUTF8String (config.defaultButton)];
if (!config.secondButton.empty ())
[alert addButtonWithTitle:stringFromUTF8String (config.secondButton)];
if (!config.thirdButton.empty ())
[alert addButtonWithTitle:stringFromUTF8String (config.thirdButton)];
return alert;
}
#endif
//------------------------------------------------------------------------
- (AlertResult)showAlert:(const AlertBoxConfig&)config
{
#if VSTGUI_STANDALONE_USE_GENERIC_ALERTBOX_ON_MACOS
AlertResult result = AlertResult::Error;
auto alertWindow = Detail::createAlertBox (config, [&] (AlertResult r) {
result = r;
[NSApp abortModal];
});
auto platformAlertWindow = VSTGUI::dynamicPtrCast<IPlatformWindowAccess> (alertWindow);
assert (platformAlertWindow);
auto macAlertWindow =
VSTGUI::staticPtrCast<IMacWindow> (platformAlertWindow->getPlatformWindow ());
assert (macAlertWindow);
auto nsWindow = macAlertWindow->getNSWindow ();
macAlertWindow->center ();
macAlertWindow->show ();
[NSApp runModalForWindow:nsWindow];
return result;
#else
NSAlert* alert = [self createAlert:config];
NSModalResponse response = [alert runModal];
if (response == NSAlertSecondButtonReturn)
return AlertResult::SecondButton;
if (response == NSAlertThirdButtonReturn)
return AlertResult::ThirdButton;
return AlertResult::DefaultButton;
#endif
}
//------------------------------------------------------------------------
- (void)showAlertForWindow:(const AlertBoxForWindowConfig&)config
{
auto platformWindowAccess = VSTGUI::dynamicPtrCast<IPlatformWindowAccess> (config.window);
if (!platformWindowAccess)
return;
auto macWindow = VSTGUI::staticPtrCast<IMacWindow> (platformWindowAccess->getPlatformWindow ());
if (!macWindow)
return;
if (macWindow->isPopup ())
{
auto result = [self showAlert:config];
if (config.callback)
config.callback (result);
return;
}
auto callback = std::move (config.callback);
#if VSTGUI_STANDALONE_USE_GENERIC_ALERTBOX_ON_MACOS
struct Params
{
NSWindow* sheet {nullptr};
NSWindow* parent {nullptr};
};
auto params = std::make_shared<Params> ();
params->parent = macWindow->getNSWindow ();
auto parentWindow = config.window;
auto alertWindow = Detail::createAlertBox (config, [=] (AlertResult r) {
if (callback)
callback (r);
[params->parent endSheet:params->sheet];
});
auto platformAlertWindow = VSTGUI::dynamicPtrCast<IPlatformWindowAccess> (alertWindow);
assert (platformAlertWindow);
auto macAlertWindow =
VSTGUI::staticPtrCast<IMacWindow> (platformAlertWindow->getPlatformWindow ());
assert (macAlertWindow);
params->sheet = macAlertWindow->getNSWindow ();
[params->parent beginSheet:params->sheet completionHandler:^(NSModalResponse returnCode) {}];
#else
NSAlert* alert = [self createAlert:config];
[alert beginSheetModalForWindow:macWindow->getNSWindow ()
completionHandler:^(NSModalResponse returnCode) {
if (callback)
{
AlertResult result = AlertResult::Error;
if (returnCode == NSAlertFirstButtonReturn)
result = AlertResult::DefaultButton;
else if (returnCode == NSAlertSecondButtonReturn)
result = AlertResult::SecondButton;
else if (returnCode == NSAlertThirdButtonReturn)
result = AlertResult::ThirdButton;
callback (result);
}
}];
#endif
}
//------------------------------------------------------------------------
- (BOOL)verifyInfoPlistEntries
{
NSDictionary* dict = [[NSBundle mainBundle] infoDictionary];
const auto& appInfo = IApplication::instance ().getDelegate ().getInfo ();
NSString* infoPlistString = dict[(@"CFBundleName")];
if (![stringFromUTF8String (appInfo.name) isEqualToString:infoPlistString])
{
NSLog (@"Warning: CFBundleName is not equal to Application::Info::name");
}
infoPlistString = dict[(@"CFBundleShortVersionString")];
if (![stringFromUTF8String (appInfo.version) isEqualToString:infoPlistString])
{
NSLog (@"Warning: CFBundleShortVersionString is not equal to Application::Info::version");
}
infoPlistString = dict[(@"CFBundleIdentifier")];
if (![stringFromUTF8String (appInfo.uri) isEqualToString:infoPlistString])
{
NSLog (@"Warning: CFBundleIdentifier is not equal to Application::Info::uri");
}
return YES;
}
//------------------------------------------------------------------------
- (void)applicationDidFinishLaunching:(nonnull NSNotification*)notification
{
if ([self verifyInfoPlistEntries] == NO)
{
[NSApp terminate:nil];
return;
}
IApplication::CommandLineArguments cmdArgs;
NSArray* args = [[NSProcessInfo processInfo] arguments];
cmdArgs.reserve ([args count]);
for (NSString* str in args)
{
cmdArgs.emplace_back ([str UTF8String]);
}
VSTGUIApplicationDelegate* Self = self;
PlatformCallbacks callbacks;
callbacks.quit = [] () {
[NSApp performSelector:@selector (terminate:) withObject:nil afterDelay:0];
};
callbacks.onCommandUpdate = [Self] () { [Self triggerSetupMainMenu]; };
callbacks.showAlert = [Self] (const AlertBoxConfig& config) { return [Self showAlert:config]; };
callbacks.showAlertForWindow = [Self] (const AlertBoxForWindowConfig& config) {
return [Self showAlertForWindow:config];
};
auto app = Detail::getApplicationPlatformAccess ();
vstgui_assert (app);
[self setupMainMenu];
IPlatformApplication::OpenFilesList openFilesList;
if (auto filenames = self.startupOpenFiles)
{
openFilesList.reserve (filenames.count);
for (NSString* filename in filenames)
{
openFilesList.emplace_back ([filename UTF8String]);
}
self.startupOpenFiles = nil;
}
self.hasFinishedLaunching = YES;
app->init ({prefs, commonDirecories, std::move (cmdArgs), std::move (callbacks),
std::move (openFilesList)});
}
//------------------------------------------------------------------------
- (void)applicationWillTerminate:(nonnull NSNotification*)notification
{
IApplication::instance ().getDelegate ().onQuit ();
for (NSWindow* window in [NSApp windows])
{
[window close];
}
Detail::cleanupSharedUIResources ();
VSTGUI::exit ();
}
//------------------------------------------------------------------------
- (BOOL)openFilesInternal:(nonnull NSArray<NSString*>*)filenames
{
std::vector<VSTGUI::UTF8String> paths;
paths.reserve (filenames.count);
for (NSString* filename in filenames)
{
paths.emplace_back ([filename UTF8String]);
}
return IApplication::instance ().getDelegate ().openFiles (paths) ? YES : NO;
}
//------------------------------------------------------------------------
- (void)application:(nonnull NSApplication*)sender openFiles:(nonnull NSArray<NSString*>*)filenames
{
if (!self.hasFinishedLaunching)
{
self.startupOpenFiles = filenames;
return;
}
BOOL result = [self openFilesInternal:filenames];
[sender replyToOpenOrPrint:result ? NSApplicationDelegateReplySuccess :
NSApplicationDelegateReplyFailure];
}
@end
//------------------------------------------------------------------------
int main (int argc, const char* _Nonnull* _Nonnull argv)
{
#if DEBUG
struct LeakDetector
{
~LeakDetector () noexcept
{
char* env = getenv ("MallocStackLogging");
if (env && (!strcmp (env, "1") || !strcmp (env, "lite")))
{
char command[1024];
pid_t pid = getpid ();
snprintf (command, std::size (command), "leaks %d", pid);
system (command);
}
}
};
static LeakDetector gLeakDetector;
#endif
VSTGUI::init (CFBundleGetMainBundle ());
VSTGUIApplicationDelegate* delegate = [VSTGUIApplicationDelegate new];
[NSApplication sharedApplication].delegate = delegate;
return NSApplicationMain (argc, argv);
}
@@ -0,0 +1,27 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../include/icommondirectories.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Mac {
//------------------------------------------------------------------------
class CommonDirectories : public ICommonDirectories
{
public:
Optional<UTF8String> get (CommonDirectoryLocation location, const UTF8String& subDir,
bool create = false) const override;
};
//------------------------------------------------------------------------
} // Mac
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,109 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "maccommondirectories.h"
#import "../../../include/iappdelegate.h"
#import "../../../include/iapplication.h"
#import "macutilities.h"
#import <Cocoa/Cocoa.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Mac {
//------------------------------------------------------------------------
namespace {
//------------------------------------------------------------------------
UTF8String createAppPathString ()
{
return IApplication::instance ().getDelegate ().getInfo ().uri;
}
//------------------------------------------------------------------------
NSURL* addSubDirs (NSURL* url, std::vector<const UTF8String*> subDirs)
{
for (const auto& subDir : subDirs)
{
if (!subDir->empty ())
url = [url URLByAppendingPathComponent:stringFromUTF8String (*subDir)];
}
return url;
}
//------------------------------------------------------------------------
Optional<UTF8String> getPath (NSSearchPathDomainMask domain, NSSearchPathDirectory directory,
std::vector<const UTF8String*> subDirs, bool create)
{
auto fileManager = [NSFileManager defaultManager];
auto url = [fileManager URLForDirectory:directory
inDomain:domain
appropriateForURL:nil
create:create ? YES : NO
error:nil];
if (url)
{
url = addSubDirs (url, subDirs);
if (create)
{
if (![fileManager createDirectoryAtURL:url
withIntermediateDirectories:YES
attributes:nil
error:nil])
{
return {};
}
}
return UTF8String ([url.path UTF8String]) + "/";
}
return {};
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Optional<UTF8String> CommonDirectories::get (CommonDirectoryLocation location,
const UTF8String& subDir, bool create) const
{
switch (location)
{
case CommonDirectoryLocation::AppPath:
{
auto url = [[NSBundle mainBundle] bundleURL];
return UTF8String ([url fileSystemRepresentation]);
}
case CommonDirectoryLocation::AppResourcesPath:
{
auto url = [[NSBundle mainBundle] resourceURL];
url = addSubDirs (url, {&subDir});
return UTF8String ([url fileSystemRepresentation]) + "/";
}
case CommonDirectoryLocation::AppPreferencesPath:
{
auto appPath = createAppPathString ();
UTF8String prefPath ("Preferences");
return getPath (NSUserDomainMask, NSLibraryDirectory, {&prefPath, &appPath, &subDir},
create);
}
case CommonDirectoryLocation::AppCachesPath:
{
auto appPath = createAppPathString ();
return getPath (NSUserDomainMask, NSCachesDirectory, {&appPath, &subDir}, create);
}
case CommonDirectoryLocation::UserDocumentsPath:
{
return getPath (NSUserDomainMask, NSDocumentDirectory, {&subDir}, create);
}
}
return {};
}
//------------------------------------------------------------------------
} // Mac
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,27 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#import "../../../include/ipreference.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Mac {
//------------------------------------------------------------------------
class MacPreference : public IPreference
{
public:
bool set (const UTF8String& key, const UTF8String& value) override;
Optional<UTF8String> get (const UTF8String& key) override;
};
//------------------------------------------------------------------------
} // Mac
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,36 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import "macpreference.h"
#import "macutilities.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Mac {
//------------------------------------------------------------------------
bool MacPreference::set (const UTF8String& key, const UTF8String& value)
{
[[NSUserDefaults standardUserDefaults] setObject:stringFromUTF8String (value)
forKey:stringFromUTF8String (key)];
return true;
}
//------------------------------------------------------------------------
Optional<UTF8String> MacPreference::get (const UTF8String& key)
{
NSString* value =
[[NSUserDefaults standardUserDefaults] stringForKey:stringFromUTF8String (key)];
if (value != nil)
return Optional<UTF8String> (UTF8String ([value UTF8String]));
return {};
}
//------------------------------------------------------------------------
} // Mac
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,20 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#import "../../../../lib/platform/mac/macstring.h"
#import <Foundation/Foundation.h>
//------------------------------------------------------------------------
inline NSString* stringFromUTF8String (const VSTGUI::UTF8String& str)
{
auto macStr = dynamic_cast<VSTGUI::MacString*> (str.getPlatformString ());
if (macStr && macStr->getCFString ())
{
return (__bridge NSString*)macStr->getCFString ();
}
return [NSString stringWithUTF8String:str.data ()];
}
@@ -0,0 +1,29 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#import "../iplatformwindow.h"
@class NSWindow;
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Mac {
//------------------------------------------------------------------------
class IMacWindow : public IWindow
{
public:
virtual NSWindow* getNSWindow () const = 0;
virtual bool isPopup () const = 0;
};
//------------------------------------------------------------------------
} // Mac
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,843 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#import <Cocoa/Cocoa.h>
#import "../../../../lib/cframe.h"
#import "../../../../lib/platform/mac/cocoa/cocoahelpers.h"
#import "../../../../lib/platform/mac/macstring.h"
#import "../../../../lib/platform/platform_macos.h"
#import "../../../include/iasync.h"
#import "../../application.h"
#import "../iplatformwindow.h"
#import "VSTGUICommand.h"
#import "macwindow.h"
#if __has_feature(nullability) == 0
static_assert (false, "Need newer clang compiler!");
#endif
#ifndef MAC_OS_X_VERSION_10_11
#define MAC_OS_X_VERSION_10_11 101100
#endif
//------------------------------------------------------------------------
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11
@interface NSWindow (BackwardsCompatibility)
- (void)performWindowDragWithEvent:(NSEvent* _Nonnull)event;
@end
#endif
//------------------------------------------------------------------------
@interface VSTGUITitlebarViewController : NSTitlebarAccessoryViewController
- (void)loadView;
@end
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Mac {
class Window;
//------------------------------------------------------------------------
} // Mac
} // Platform
} // Standalone
} // VSTGUI
//------------------------------------------------------------------------
@interface VSTGUIWindowDelegate : NSObject <NSWindowDelegate>
@property VSTGUI::Standalone::Platform::Mac::Window* _Nullable macWindow;
@end
//------------------------------------------------------------------------
@interface VSTGUIWindow : NSWindow
@property BOOL supportMovableByWindowBackground;
@property BOOL nonClosable;
@end
//------------------------------------------------------------------------
@interface VSTGUIPopup : NSPanel
@property BOOL supportMovableByWindowBackground;
@property BOOL inSendEvent;
@property NSInteger doResignKey;
@property NSInteger doResignKeyStackDepth;
@end
//------------------------------------------------------------------------
@interface VSTGUIPopupDelegate : VSTGUIWindowDelegate
@end
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Mac {
//------------------------------------------------------------------------
static NSPoint getWindowContentRectOffset (NSWindow* window)
{
auto cls = window.contentLayoutRect.size;
auto fs = window.frame.size;
return NSMakePoint (fs.width - cls.width, fs.height - cls.height);
}
//------------------------------------------------------------------------
class Window : public IMacWindow
{
public:
bool init (const WindowConfiguration& config, IWindowDelegate& delegate);
CPoint getSize () const override;
CPoint getPosition () const override;
double getScaleFactor () const override { return 1.; }
void setSize (const CPoint& newSize) override;
void setPosition (const CPoint& newPosition) override;
void setTitle (const UTF8String& newTitle) override;
void setRepresentedPath (const UTF8String& path) override;
WindowStyle changeStyle (WindowStyle stylesToAdd, WindowStyle stylesToRemove) override;
void show () override;
void hide () override;
void close () override;
void activate () override;
void center () override;
PlatformType getPlatformType () const override { return PlatformType::kNSView; };
void* _Nonnull getPlatformHandle () const override
{
return static_cast<void*> ((__bridge void*)contentView);
}
PlatformFrameConfigPtr prepareFrameConfig (PlatformFrameConfigPtr&& controllerConfig) override
{
return std::move (controllerConfig);
}
void onSetContentView (CFrame* _Nullable newFrame) override;
void windowDidResize (CPoint newSize);
void windowWillClose ();
IWindowDelegate& getDelegate () const { return *delegate; }
NSWindow* _Nonnull getNSWindow () const override { return nsWindow; }
bool isPopup () const override;
private:
NSRect validateFrameRect (NSRect r) const;
WindowStyle style;
NSWindow* _Nullable nsWindow {nullptr};
NSView* _Nullable contentView {nullptr};
VSTGUIWindowDelegate* _Nullable nsWindowDelegate {nullptr};
IWindowDelegate* _Nullable delegate {nullptr};
CFrame* _Nullable frame {nullptr};
NSObject* sizeObserver {nullptr};
};
//------------------------------------------------------------------------
bool Window::init (const WindowConfiguration& config, IWindowDelegate& inDelegate)
{
style = config.style;
NSUInteger styleMask = 0;
if (style.hasBorder ())
styleMask |= MacWindowStyleMask::Titled | MacWindowStyleMask::FullSizeContentView;
if (style.canSize ())
styleMask |= MacWindowStyleMask::Resizable | MacWindowStyleMask::Miniaturizable;
if (style.canClose ())
styleMask |= MacWindowStyleMask::Closable;
delegate = &inDelegate;
NSRect contentRect = NSMakeRect (0, 0, config.size.x, config.size.y);
if (config.type == WindowType::Popup)
{
styleMask |= MacWindowStyleMask::Utility;
VSTGUIPopup* popup = [[VSTGUIPopup alloc] initWithContentRect:contentRect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:YES];
popup.becomesKeyOnlyIfNeeded = NO;
popup.level = NSFloatingWindowLevel;
popup.supportMovableByWindowBackground = style.isMovableByWindowBackground ();
nsWindow = popup;
nsWindowDelegate = [VSTGUIPopupDelegate new];
nsWindowDelegate.macWindow = this;
[nsWindow setAnimationBehavior:NSWindowAnimationBehaviorUtilityWindow];
}
else
{
VSTGUIWindow* window = [[VSTGUIWindow alloc] initWithContentRect:contentRect
styleMask:styleMask
backing:NSBackingStoreBuffered
defer:YES];
window.supportMovableByWindowBackground = style.isMovableByWindowBackground ();
nsWindow = window;
nsWindowDelegate = [VSTGUIWindowDelegate new];
nsWindowDelegate.macWindow = this;
[nsWindow setAnimationBehavior:NSWindowAnimationBehaviorNone];
if (!style.canClose ())
window.nonClosable = true;
if (style.canSize ())
{
nsWindow.collectionBehavior =
NSWindowCollectionBehaviorFullScreenPrimary | nsWindow.collectionBehavior;
}
}
if (style.hasBorder ())
{
auto layoutRect = nsWindow.contentLayoutRect;
contentView = [[NSView alloc] initWithFrame:layoutRect];
[nsWindow.contentView addSubview:contentView];
#if DEBUG
auto tbvController = [VSTGUITitlebarViewController new];
tbvController.layoutAttribute = NSLayoutAttributeRight;
[nsWindow addTitlebarAccessoryViewController:tbvController];
#endif
}
else
{
contentView = nsWindow.contentView;
}
nsWindow.collectionBehavior =
NSWindowCollectionBehaviorFullScreenAuxiliary | nsWindow.collectionBehavior;
[nsWindow setDelegate:nsWindowDelegate];
if (style.isTransparent ())
{
nsWindow.backgroundColor = [NSColor clearColor];
nsWindow.opaque = NO;
nsWindow.hasShadow = YES;
}
auto titleMacStr = dynamic_cast<MacString*> (config.title.getPlatformString ());
if (titleMacStr && titleMacStr->getCFString ())
{
nsWindow.title = (__bridge NSString*)titleMacStr->getCFString ();
}
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_12 && __clang_major__ >= 9
if (@available (macOS 10.12, *))
{
if (!config.groupIdentifier.empty ())
{
auto groupMacStr =
dynamic_cast<MacString*> (config.groupIdentifier.getPlatformString ());
if (groupMacStr && groupMacStr->getCFString ())
{
nsWindow.tabbingIdentifier = (__bridge NSString*)groupMacStr->getCFString ();
}
}
else
{
nsWindow.tabbingMode = NSWindowTabbingModeDisallowed;
}
}
#endif
[nsWindow setReleasedWhenClosed:NO];
[nsWindow center];
sizeObserver = [[NSNotificationCenter defaultCenter]
addObserverForName:NSViewFrameDidChangeNotification
object:nsWindow.contentView
queue:nil
usingBlock:[this] (NSNotification* _Nonnull note) {
auto contentViewSize = nsWindow.contentView.frame.size;
windowDidResize ({contentViewSize.width, contentViewSize.height});
}];
return true;
}
//------------------------------------------------------------------------
bool Window::isPopup () const
{
return [nsWindow isKindOfClass:[NSPanel class]];
}
//------------------------------------------------------------------------
void Window::onSetContentView (CFrame* _Nullable newFrame)
{
frame = newFrame;
}
//------------------------------------------------------------------------
void Window::windowDidResize (CPoint newSize)
{
if (contentView != nsWindow.contentView)
{
contentView.frame = nsWindow.contentLayoutRect;
newSize.x = nsWindow.contentLayoutRect.size.width;
newSize.y = nsWindow.contentLayoutRect.size.height;
}
delegate->onSizeChanged (newSize);
if (frame)
frame->setSize (newSize.x, newSize.y);
}
//------------------------------------------------------------------------
void Window::windowWillClose ()
{
if (sizeObserver)
[[NSNotificationCenter defaultCenter] removeObserver:sizeObserver];
sizeObserver = nullptr;
NSWindow* temp = nsWindow;
nsWindowDelegate = nil;
delegate->onClosed ();
// we are now destroyed ! at least we should !
temp.delegate = nil;
temp = nil;
}
//------------------------------------------------------------------------
CPoint Window::getSize () const
{
CPoint p;
NSSize size = [nsWindow contentRectForFrameRect:nsWindow.frame].size;
p.x = size.width;
p.y = size.height;
return p;
}
//------------------------------------------------------------------------
static NSRect getMainScreenRect ()
{
NSScreen* mainScreen = [NSScreen screens][0];
return mainScreen.frame;
}
//------------------------------------------------------------------------
CPoint Window::getPosition () const
{
CPoint p;
NSRect windowRect = [nsWindow contentRectForFrameRect:nsWindow.frame];
p.x = windowRect.origin.x;
p.y = windowRect.origin.y;
p.y = getMainScreenRect ().size.height - (p.y + windowRect.size.height);
return p;
}
//------------------------------------------------------------------------
NSRect Window::validateFrameRect (NSRect r) const
{
BOOL isOnScreen = NO;
for (NSScreen* screen in [NSScreen screens])
{
if (NSIntersectsRect (r, screen.visibleFrame))
{
isOnScreen = YES;
break;
}
}
if (!isOnScreen)
r = [nsWindow constrainFrameRect:r toScreen:[NSScreen mainScreen]];
return r;
}
//------------------------------------------------------------------------
void Window::setSize (const CPoint& newSize)
{
NSRect r = [nsWindow contentRectForFrameRect:nsWindow.frame];
auto offset = getWindowContentRectOffset (nsWindow);
CGFloat diff = (newSize.y + offset.y) - r.size.height;
r.size.width = newSize.x + offset.x;
r.size.height = newSize.y + offset.y;
r.origin.y -= diff;
[nsWindow setFrame:[nsWindow frameRectForContentRect:r]
display:[nsWindow isVisible]
animate:NO];
if (!nsWindow.opaque)
[nsWindow invalidateShadow];
}
//------------------------------------------------------------------------
void Window::setPosition (const CPoint& newPosition)
{
NSRect r = [nsWindow contentRectForFrameRect:nsWindow.frame];
r.origin.x = newPosition.x;
r.origin.y = getMainScreenRect ().size.height - (newPosition.y + r.size.height);
r = validateFrameRect ([nsWindow frameRectForContentRect:r]);
[nsWindow setFrame:r display:[nsWindow isVisible] animate:NO];
}
//------------------------------------------------------------------------
void Window::setTitle (const UTF8String& newTitle)
{
auto titleMacStr = dynamic_cast<MacString*> (newTitle.getPlatformString ());
if (titleMacStr && titleMacStr->getCFString ())
{
nsWindow.title = (__bridge NSString*)titleMacStr->getCFString ();
}
}
//------------------------------------------------------------------------
void Window::setRepresentedPath (const UTF8String& path)
{
auto pathMacStr = dynamic_cast<MacString*> (path.getPlatformString ());
if (pathMacStr && pathMacStr->getCFString ())
{
auto url = [NSURL fileURLWithPath:(__bridge NSString*)pathMacStr->getCFString ()];
nsWindow.representedURL = url;
}
}
//------------------------------------------------------------------------
WindowStyle Window::changeStyle (WindowStyle stylesToAdd, WindowStyle stylesToRemove)
{
auto styleMask = nsWindow.styleMask;
if (stylesToAdd.canSize ())
{
styleMask |= MacWindowStyleMask::Resizable | MacWindowStyleMask::Miniaturizable;
style += WindowStyle ().size ();
}
else if (stylesToRemove.canSize ())
{
styleMask &= ~(MacWindowStyleMask::Resizable | MacWindowStyleMask::Miniaturizable);
style -= WindowStyle ().size ();
}
nsWindow.styleMask = styleMask;
return style;
}
//------------------------------------------------------------------------
void Window::show ()
{
if (![nsWindow isVisible])
{
delegate->onShow ();
[nsWindow makeKeyAndOrderFront:nil];
if (!nsWindow.opaque)
[nsWindow invalidateShadow];
}
}
//------------------------------------------------------------------------
void Window::hide ()
{
delegate->onHide ();
[nsWindow orderOut:nil];
}
//------------------------------------------------------------------------
void Window::close ()
{
[nsWindow performClose:nil];
}
//------------------------------------------------------------------------
void Window::activate ()
{
[nsWindow makeKeyAndOrderFront:nil];
}
//------------------------------------------------------------------------
void Window::center ()
{
[nsWindow center];
}
} // Mac
//------------------------------------------------------------------------
WindowPtr makeWindow (const WindowConfiguration& config, IWindowDelegate& delegate)
{
auto window = std::make_shared<Mac::Window> ();
if (window->init (config, delegate))
return window;
return nullptr;
}
} // Platform
} // Standalone
} // VSTGUI
//------------------------------------------------------------------------
@implementation VSTGUIWindowDelegate
//------------------------------------------------------------------------
- (id)firstResponderIsFieldEditor
{
id firstResponder = [self.macWindow->getNSWindow () firstResponder];
if ([firstResponder isKindOfClass:[NSText class]])
return firstResponder;
id fieldEditor = [self.macWindow->getNSWindow () fieldEditor:NO forObject:self];
return firstResponder == fieldEditor ? fieldEditor : nil;
}
//------------------------------------------------------------------------
- (BOOL)canHandleCommand:(const VSTGUI::Standalone::Command&)command
{
using namespace VSTGUI::Standalone;
if (self.macWindow->getDelegate ().canHandleCommand (command))
return YES;
return Detail::getApplicationPlatformAccess ()->canHandleCommand (command) ? YES : NO;
}
//------------------------------------------------------------------------
- (BOOL)handleCommand:(const VSTGUI::Standalone::Command&)command
{
using namespace VSTGUI::Standalone;
if (self.macWindow->getDelegate ().handleCommand (command))
return YES;
return Detail::getApplicationPlatformAccess ()->handleCommand (command) ? YES : NO;
}
//------------------------------------------------------------------------
- (IBAction)processCommand:(nullable id)sender
{
VSTGUICommand* command = [sender representedObject];
if (command)
[self handleCommand:command.command];
}
//------------------------------------------------------------------------
- (void)undo
{
if (id fieldEditor = [self firstResponderIsFieldEditor])
{
[[[fieldEditor window] undoManager] undo];
return;
}
using namespace VSTGUI::Standalone;
Command command {CommandGroup::Edit, CommandName::Undo};
[self handleCommand:command];
}
//------------------------------------------------------------------------
- (void)redo
{
if (id fieldEditor = [self firstResponderIsFieldEditor])
{
[[[fieldEditor window] undoManager] redo];
return;
}
using namespace VSTGUI::Standalone;
Command command {CommandGroup::Edit, CommandName::Redo};
[self handleCommand:command];
}
//------------------------------------------------------------------------
- (void)copy:(id)sender
{
using namespace VSTGUI::Standalone;
Command command {CommandGroup::Edit, CommandName::Copy};
[self handleCommand:command];
}
//------------------------------------------------------------------------
- (void)cut:(id)sender
{
using namespace VSTGUI::Standalone;
Command command {CommandGroup::Edit, CommandName::Cut};
[self handleCommand:command];
}
//------------------------------------------------------------------------
- (void)paste:(id)sender
{
using namespace VSTGUI::Standalone;
Command command {CommandGroup::Edit, CommandName::Paste};
[self handleCommand:command];
}
//------------------------------------------------------------------------
- (void)selectAll:(id)sender
{
using namespace VSTGUI::Standalone;
Command command {CommandGroup::Edit, CommandName::SelectAll};
[self handleCommand:command];
}
//------------------------------------------------------------------------
- (void) delete:(id)sender
{
using namespace VSTGUI::Standalone;
Command command {CommandGroup::Edit, CommandName::Delete};
[self handleCommand:command];
}
//------------------------------------------------------------------------
- (BOOL)validateMenuItem:(nonnull NSMenuItem*)menuItem
{
SEL action = menuItem.action;
if (action == @selector (undo) || action == @selector (redo))
{
if (id fieldEditor = [self firstResponderIsFieldEditor])
{
BOOL enable = NO;
NSString* itemTitle = nil;
NSUndoManager* undoManager = [[fieldEditor window] undoManager];
if (action == @selector (undo))
{
enable = undoManager.canUndo;
itemTitle = undoManager.undoMenuItemTitle;
}
else
{
enable = undoManager.canRedo;
itemTitle = undoManager.redoMenuItemTitle;
}
menuItem.title = itemTitle;
return enable;
}
else
{
if (action == @selector (undo))
menuItem.title = NSLocalizedString (@"Undo", "Menu Item");
else
menuItem.title = NSLocalizedString (@"Redo", "Menu Item");
}
}
BOOL res = NO;
if (VSTGUICommand* command = menuItem.representedObject)
res = [self canHandleCommand:command.command];
return res;
}
//------------------------------------------------------------------------
- (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)client
{
id fieldEditor = [sender fieldEditor:YES forObject:self];
if (fieldEditor)
[fieldEditor setAllowsUndo:YES];
return fieldEditor;
}
//------------------------------------------------------------------------
- (NSSize)windowWillResize:(nonnull NSWindow*)sender toSize:(NSSize)frameSize
{
NSRect r {};
r.size = frameSize;
r = [sender contentRectForFrameRect:r];
auto offset = VSTGUI::Standalone::Platform::Mac::getWindowContentRectOffset (sender);
VSTGUI::CPoint p (r.size.width - offset.x, r.size.height - offset.y);
p = self.macWindow->getDelegate ().constraintSize (p);
r.size.width = p.x + offset.x;
r.size.height = p.y + offset.y;
r = [sender frameRectForContentRect:r];
return r.size;
}
//------------------------------------------------------------------------
- (void)windowDidMove:(nonnull NSNotification*)notification
{
self.macWindow->getDelegate ().onPositionChanged (self.macWindow->getPosition ());
}
//------------------------------------------------------------------------
- (void)windowWillClose:(nonnull NSNotification*)notification
{
self.macWindow->windowWillClose ();
}
//------------------------------------------------------------------------
- (BOOL)windowShouldClose:(nonnull id)sender
{
return self.macWindow->getDelegate ().canClose ();
}
//------------------------------------------------------------------------
- (void)windowDidBecomeKey:(nonnull NSNotification*)notification
{
self.macWindow->getDelegate ().onActivated ();
}
//------------------------------------------------------------------------
- (void)windowDidResignKey:(nonnull NSNotification*)notification
{
self.macWindow->getDelegate ().onDeactivated ();
}
//------------------------------------------------------------------------
- (void)noResponderFor:(nonnull SEL)eventSelector
{
// prevent Beep
}
@end
//------------------------------------------------------------------------
@implementation VSTGUIWindow
//------------------------------------------------------------------------
- (void)mouseDown:(nonnull NSEvent*)theEvent
{
if (self.supportMovableByWindowBackground &&
[super respondsToSelector:@selector (performWindowDragWithEvent:)])
{
[super performWindowDragWithEvent:theEvent];
}
}
//------------------------------------------------------------------------
- (BOOL)canBecomeKeyWindow
{
return YES;
}
//------------------------------------------------------------------------
- (void)performClose:(nullable id)sender
{
using namespace VSTGUI::Standalone;
if (self.delegate)
{
if (![self.delegate windowShouldClose:self])
return;
}
VSTGUIWindow* window = self;
Async::schedule (Async::mainQueue (), [=] () { [window close]; });
}
//------------------------------------------------------------------------
- (BOOL)validateMenuItem:(nonnull NSMenuItem*)menuItem
{
if ([menuItem action] == @selector (performClose:))
return !self.nonClosable;
return [super validateMenuItem:menuItem];
}
//------------------------------------------------------------------------
- (void)makeKeyAndOrderFront:(nullable id)sender
{
if (!self.visible && [self.title length] > 0)
{
[NSApp addWindowsItem:self title:self.title filename:NO];
}
[super makeKeyAndOrderFront:sender];
}
//------------------------------------------------------------------------
- (void)noResponderFor:(nonnull SEL)eventSelector
{
// prevent Beep
}
//------------------------------------------------------------------------
- (void)endEditingFor:(nullable id)anObject
{
[super endEditingFor:anObject];
if (anObject == [self fieldEditor:NO forObject:anObject])
{
[[self undoManager] removeAllActions];
}
}
@end
//------------------------------------------------------------------------
@implementation VSTGUIPopup
//------------------------------------------------------------------------
- (BOOL)canBecomeKeyWindow
{
return YES;
}
//------------------------------------------------------------------------
- (void)mouseDown:(nonnull NSEvent*)theEvent
{
if (self.supportMovableByWindowBackground &&
[super respondsToSelector:@selector (performWindowDragWithEvent:)])
{
[super performWindowDragWithEvent:theEvent];
}
}
//------------------------------------------------------------------------
- (void)sendEvent:(nonnull NSEvent*)theEvent
{
self.doResignKeyStackDepth++;
self.inSendEvent = YES;
[super sendEvent:theEvent];
self.inSendEvent = NO;
if (self.doResignKey == self.doResignKeyStackDepth)
{
self.doResignKey = NO;
[super resignKeyWindow];
}
self.doResignKeyStackDepth--;
}
//------------------------------------------------------------------------
- (void)resignKeyWindow
{
if (self.inSendEvent)
{
self.doResignKey = self.doResignKeyStackDepth;
}
else
{
[super resignKeyWindow];
}
}
//------------------------------------------------------------------------
- (void)cancelOperation:(nullable id)sender
{
[self resignKeyWindow];
}
//------------------------------------------------------------------------
- (void)performClose:(nullable id)sender
{
using namespace VSTGUI::Standalone;
VSTGUIPopup* popup = self;
Async::schedule (Async::mainQueue (), [=] () { [popup close]; });
}
//------------------------------------------------------------------------
- (void)noResponderFor:(nonnull SEL)eventSelector
{
// prevent Beep
}
@end
//------------------------------------------------------------------------
@implementation VSTGUIPopupDelegate
//------------------------------------------------------------------------
- (void)windowDidResignKey:(nonnull NSNotification*)notification
{
auto app = VSTGUI::Standalone::Detail::getApplicationPlatformAccess ();
if (app->dontClosePopupOnDeactivation (self.macWindow))
return;
[self.macWindow->getNSWindow () close];
}
@end
//------------------------------------------------------------------------
@implementation VSTGUITitlebarViewController
//------------------------------------------------------------------------
- (void)loadView
{
auto control = [NSButton buttonWithTitle:@"ⓔ" target:self action:@selector (doAction:)];
control.showsBorderOnlyWhileMouseInside = NO;
control.bordered = NO;
control.bezelStyle = NSBezelStyleRounded;
self.view = control;
}
//------------------------------------------------------------------------
- (void)doAction:(id)sender
{
using namespace VSTGUI::Standalone;
IApplication::instance ().executeCommand (Commands::Debug::ToggleInlineUIEditor);
}
@end
@@ -0,0 +1,333 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "win32commondirectories.h"
#include "win32preference.h"
#include "win32window.h"
#include "../../../../lib/vstguiinit.h"
#include "../../../../lib/platform/win32/win32dll.h"
#include "../../../../lib/platform/win32/win32factory.h"
#include "../../../../lib/platform/win32/win32support.h"
#include "../../../../lib/platform/platform_win32.h"
#include "../../../include/iappdelegate.h"
#include "../../../include/iapplication.h"
#include "../../../include/iasync.h"
#include "../../application.h"
#include "../../genericalertbox.h"
#include "../../shareduiresources.h"
#include "../../window.h"
#include "../iplatformwindow.h"
#include <array>
#include <chrono>
#include <shellapi.h>
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
#ifndef __clang__
#pragma comment(linker, \
"\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Win32 {
using VSTGUI::Standalone::Detail::IPlatformApplication;
using VSTGUI::Standalone::Detail::CommandWithKey;
using VSTGUI::Standalone::Detail::IPlatformWindowAccess;
using CommandWithKeyList = VSTGUI::Standalone::Detail::IPlatformApplication::CommandWithKeyList;
using VSTGUI::Standalone::Detail::PlatformCallbacks;
//------------------------------------------------------------------------
static IWin32Window* toWin32Window (const VSTGUI::Standalone::WindowPtr& window)
{
auto platformWindow = dynamicPtrCast<Detail::IPlatformWindowAccess> (window);
if (!platformWindow)
return nullptr;
return staticPtrCast<IWin32Window> (platformWindow->getPlatformWindow ()).get ();
}
//------------------------------------------------------------------------
static Optional<std::string> ascendPath (std::string& path, char delimiter = '\\')
{
auto index = path.find_last_of (delimiter);
if (index == std::string::npos)
return {};
path.erase (index);
return Optional<std::string> (std::move (path));
}
//------------------------------------------------------------------------
class Application
{
public:
Application () = default;
void init (HINSTANCE instance, LPWSTR commandLine);
void run ();
void quit ();
void onCommandUpdate ();
AlertResult showAlert (const AlertBoxConfig& config);
void showAlertForWindow (const AlertBoxForWindowConfig& config);
private:
static void dispatchPaintMessages ();
Win32Preference prefs;
CommonDirectories commonDirectories;
bool needCommandUpdate {false};
HACCEL keyboardAccelerators {nullptr};
};
//------------------------------------------------------------------------
void Application::init (HINSTANCE instance, LPWSTR commandLine)
{
WCHAR path[MAX_PATH];
if (SUCCEEDED (GetModuleFileNameW (static_cast<HMODULE> (instance), path, MAX_PATH)))
{
UTF8StringHelper helper (path);
auto utf8Path = std::string (helper.getUTF8String ());
if (auto p = ascendPath (utf8Path))
{
*p += "\\Resources\\";
UTF8String resourcePath (*p);
getPlatformFactory ().asWin32Factory ()->setResourceBasePath (resourcePath);
commonDirectories.setAppResourcePath (resourcePath);
}
}
IApplication::CommandLineArguments cmdArgs;
int numArgs = 0;
auto cmdArgsArray = CommandLineToArgvW (commandLine, &numArgs);
for (int i = 0; i < numArgs; ++i)
{
UTF8StringHelper str (cmdArgsArray[i]);
cmdArgs.emplace_back (str.getUTF8String ());
}
LocalFree (cmdArgsArray);
PlatformCallbacks callbacks;
callbacks.quit = [this] () { quit (); };
callbacks.onCommandUpdate = [this] () {
if (!needCommandUpdate)
{
needCommandUpdate = true;
Async::schedule (Async::mainQueue (), [this] () { onCommandUpdate (); });
}
};
callbacks.showAlert = [this] (const AlertBoxConfig& config) { return showAlert (config); };
callbacks.showAlertForWindow = [this] (const AlertBoxForWindowConfig& config) {
showAlertForWindow (config);
};
auto app = Detail::getApplicationPlatformAccess ();
vstgui_assert (app);
IPlatformApplication::OpenFilesList openFilesList;
/* TODO: fill openFilesList */
app->init ({prefs, commonDirectories, std::move (cmdArgs), std::move (callbacks),
std::move (openFilesList)});
}
//------------------------------------------------------------------------
AlertResult Application::showAlert (const AlertBoxConfig& config)
{
bool alertDone = false;
AlertResult result = AlertResult::Error;
auto callback = [&] (AlertResult r) {
result = r;
alertDone = true;
for (auto& w : IApplication::instance ().getWindows ())
{
if (auto winWindow = toWin32Window (w))
winWindow->setModalWindow (nullptr);
}
};
if (auto window = Detail::createAlertBox (config, callback))
{
auto winModalWindow = toWin32Window (window);
vstgui_assert (winModalWindow);
for (auto& w : IApplication::instance ().getWindows ())
{
if (w == window)
continue;
if (auto winWindow = toWin32Window (w))
winWindow->setModalWindow (window);
}
if (winModalWindow)
{
winModalWindow->center ();
SetCapture (winModalWindow->getHWND ());
}
window->show ();
}
else
return AlertResult::Error;
MSG msg;
BOOL gmResult;
while (!alertDone && (gmResult = GetMessage (&msg, nullptr, 0, 0)))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return result;
}
//------------------------------------------------------------------------
void Application::showAlertForWindow (const AlertBoxForWindowConfig& config)
{
auto callback = config.callback;
auto parentWindow = config.window;
if (auto window = Detail::createAlertBox (config, [=] (AlertResult r) {
auto parentWinWindow = toWin32Window (parentWindow);
vstgui_assert (parentWinWindow);
parentWinWindow->setModalWindow (nullptr);
Async::schedule (Async::mainQueue (), [callback, r, parentWindow] () {
if (callback)
callback (r);
if (auto winWindow = toWin32Window (parentWindow))
winWindow->activate ();
});
}))
{
auto parentWinWindow = toWin32Window (config.window);
vstgui_assert (parentWinWindow);
parentWinWindow->setModalWindow (window);
CRect r;
r.setTopLeft (parentWindow->getPosition ());
r.setSize (parentWindow->getSize ());
CRect r2;
r2.setSize (window->getSize ());
r2.centerInside (r);
window->setPosition (r2.getTopLeft ());
window->show ();
}
}
//------------------------------------------------------------------------
void Application::onCommandUpdate ()
{
if (keyboardAccelerators)
{
DestroyAcceleratorTable (keyboardAccelerators);
keyboardAccelerators = nullptr;
}
auto& windows = IApplication::instance ().getWindows ();
for (auto& w : windows)
{
if (auto winWindow = toWin32Window (w))
winWindow->updateCommands ();
}
std::vector<ACCEL> accels;
for (auto& grp : Detail::getApplicationPlatformAccess ()->getCommandList ())
{
for (auto& e : grp.second)
{
if (e.defaultKey)
{
BYTE virt = FVIRTKEY | FCONTROL;
auto upperKey = toupper (e.defaultKey);
if (upperKey == e.defaultKey)
virt |= FSHIFT;
accels.push_back ({virt, static_cast<WORD> (upperKey), e.id});
}
}
}
if (!accels.empty ())
keyboardAccelerators =
CreateAcceleratorTable (accels.data (), static_cast<int> (accels.size ()));
needCommandUpdate = false;
}
//------------------------------------------------------------------------
void Application::quit ()
{
Async::schedule (Async::mainQueue (), [] () {
auto windows = IApplication::instance ().getWindows (); // Yes, copy the window list
for (auto& w : windows)
{
if (auto winWindow = toWin32Window (w))
winWindow->onQuit ();
}
IApplication::instance ().getDelegate ().onQuit ();
PostQuitMessage (0);
});
}
//------------------------------------------------------------------------
void Application::dispatchPaintMessages ()
{
HWND prevPaintWindow = nullptr;
MSG msg;
while (PeekMessage (&msg, nullptr, WM_PAINT, WM_PAINT, PM_REMOVE | PM_QS_PAINT))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
if (prevPaintWindow == msg.hwnd)
break;
prevPaintWindow = msg.hwnd;
}
}
//------------------------------------------------------------------------
void Application::run ()
{
using namespace std::chrono;
auto lastPaintMessageTime = steady_clock::now ();
MSG msg;
while (GetMessage (&msg, nullptr, 0, 0))
{
if (keyboardAccelerators && TranslateAccelerator (msg.hwnd, keyboardAccelerators, &msg))
continue;
TranslateMessage (&msg);
DispatchMessage (&msg);
if (msg.message == WM_PAINT &&
duration_cast<milliseconds> (steady_clock::now () - lastPaintMessageTime).count () >=
15)
{
dispatchPaintMessages ();
lastPaintMessageTime = steady_clock::now ();
}
}
Detail::cleanupSharedUIResources ();
}
//------------------------------------------------------------------------
} // Win32
} // Platform
} // Standalone
} // VSTGUI
//------------------------------------------------------------------------
int APIENTRY wWinMain (_In_ HINSTANCE instance, _In_opt_ HINSTANCE prevInstance,
_In_ LPWSTR lpCmdLine, _In_ int nCmdShow)
{
HeapSetInformation (nullptr, HeapEnableTerminationOnCorruption, nullptr, 0);
HRESULT hr = OleInitialize (nullptr);
if (FAILED (hr))
return FALSE;
auto& hidpiSupport = VSTGUI::HiDPISupport::instance ();
if (!hidpiSupport.setProcessDpiAwarnessContext (
VSTGUI::HiDPISupport::AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
hidpiSupport.setProcessDpiAwareness (VSTGUI::HiDPISupport::PROCESS_PER_MONITOR_DPI_AWARE);
VSTGUI::init (instance);
VSTGUI::getPlatformFactory ().asWin32Factory ()->useD2DHardwareRenderer (true);
VSTGUI::Standalone::Platform::Win32::Application app;
app.init (instance, lpCmdLine);
app.run ();
VSTGUI::exit ();
OleUninitialize ();
return 0;
}
@@ -0,0 +1,137 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "../../../../lib/platform/win32/win32support.h"
#include "../../../include/iappdelegate.h"
#include "../../../include/iapplication.h"
#include "win32commondirectories.h"
#include <array>
#include <shlobj.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Win32 {
//------------------------------------------------------------------------
namespace {
//------------------------------------------------------------------------
UTF8String GetKnownFolderPathStr (REFKNOWNFOLDERID folderID, bool create)
{
UTF8String res;
PWSTR path;
if (SHGetKnownFolderPath (folderID, create ? KF_FLAG_CREATE : 0, nullptr, &path) == S_OK)
{
res = UTF8StringHelper (path).getUTF8String ();
res += "\\";
CoTaskMemFree (path);
}
return res;
}
//------------------------------------------------------------------------
bool createDirectoryRecursive (const UTF8String& path)
{
UTF8StringHelper helper (path.data ());
auto res = SHCreateDirectoryEx (nullptr, helper.getWideString (), nullptr);
if (!(res == ERROR_SUCCESS || res == ERROR_ALREADY_EXISTS))
return false;
return true;
}
//------------------------------------------------------------------------
bool addSubDir (UTF8String& path, const UTF8String& subDir, bool create)
{
if (!subDir.empty ())
{
path += subDir;
path += "\\";
}
if (create && !createDirectoryRecursive (path))
{
return false;
}
return true;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
CommonDirectories::CommonDirectories ()
{
localAppDataPath = GetKnownFolderPathStr (FOLDERID_LocalAppData, true);
}
//------------------------------------------------------------------------
void CommonDirectories::setAppResourcePath (const UTF8String& path)
{
appResourcePath = path;
}
//------------------------------------------------------------------------
Optional<UTF8String> CommonDirectories::getLocalAppDataPath (const UTF8String& dir,
const UTF8String& subDir,
bool create) const
{
if (!localAppDataPath.empty ())
{
UTF8String result (localAppDataPath);
result += IApplication::instance ().getDelegate ().getInfo ().uri;
result += "\\";
result += dir;
result += "\\";
if (!addSubDir (result, subDir, create))
result = {};
return result;
}
return {};
}
//------------------------------------------------------------------------
Optional<UTF8String> CommonDirectories::getAppPath () const
{
UTF8String appPath;
std::array<wchar_t, 1024> path;
GetModuleFileName (GetModuleHandle (nullptr), path.data (), static_cast<DWORD> (path.size ()));
appPath = UTF8StringHelper (path.data ()).getUTF8String ();
return appPath;
}
//------------------------------------------------------------------------
Optional<UTF8String> CommonDirectories::get (CommonDirectoryLocation location,
const UTF8String& subDir, bool create) const
{
switch (location)
{
case CommonDirectoryLocation::AppPath: return getAppPath ();
case CommonDirectoryLocation::AppResourcesPath:
{
UTF8String result (appResourcePath);
if (!addSubDir (result, subDir, create))
return {};
return result;
}
case CommonDirectoryLocation::AppPreferencesPath:
return getLocalAppDataPath ("Preferences", subDir, create);
case CommonDirectoryLocation::AppCachesPath:
return getLocalAppDataPath ("Caches", subDir, create);
case CommonDirectoryLocation::UserDocumentsPath:
{
auto result = GetKnownFolderPathStr (FOLDERID_Documents, create);
if (result.empty () || !addSubDir (result, subDir, create))
return {};
return result;
}
}
return {};
}
//------------------------------------------------------------------------
} // Win32
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,39 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../include/icommondirectories.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Win32 {
//------------------------------------------------------------------------
class CommonDirectories : public ICommonDirectories
{
public:
CommonDirectories ();
Optional<UTF8String> get (CommonDirectoryLocation location, const UTF8String& subDir,
bool create = false) const override;
void setAppResourcePath (const UTF8String& path);
private:
Optional<UTF8String> getLocalAppDataPath (const UTF8String& dir, const UTF8String& subDir,
bool create) const;
Optional<UTF8String> getAppPath () const;
UTF8String localAppDataPath;
UTF8String appResourcePath;
};
//------------------------------------------------------------------------
} // Win32
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,135 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "win32menu.h"
#include "../../../../lib/platform/win32/winstring.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace {
//------------------------------------------------------------------------
const WCHAR* getWideString (const UTF8String& str)
{
if (auto winStr = dynamic_cast<WinString*> (str.getPlatformString ()))
{
return winStr->getWideString ();
}
return nullptr;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
Win32Menu* Win32Menu::fromHMENU (HMENU menu)
{
MENUINFO info {};
info.cbSize = sizeof (MENUINFO);
info.fMask = MIM_MENUDATA;
GetMenuInfo (menu, &info);
return reinterpret_cast<Win32Menu*> (info.dwMenuData);
}
//------------------------------------------------------------------------
Win32Menu::Win32Menu (UTF8StringView name)
{
title = name;
menu = CreateMenu ();
MENUINFO info {};
info.cbSize = sizeof (MENUINFO);
info.dwStyle = MNS_NOTIFYBYPOS;
info.dwMenuData = reinterpret_cast<ULONG_PTR> (this);
info.fMask = MIM_STYLE | MIM_MENUDATA;
SetMenuInfo (menu, &info);
}
//------------------------------------------------------------------------
Win32Menu::~Win32Menu () noexcept
{
if (menu)
DestroyMenu (menu);
}
//------------------------------------------------------------------------
auto Win32Menu::itemAtIndex (size_t index) const -> ItemPtr
{
if (index < items.size ())
return items[index];
return nullptr;
}
//------------------------------------------------------------------------
size_t Win32Menu::addSeparator ()
{
auto item = std::make_shared<Win32MenuItem> ();
item->flags = Win32MenuItem::Flags::separator;
return addItem (std::move (item));
}
//------------------------------------------------------------------------
size_t Win32Menu::addItem (ItemPtr&& item)
{
items.emplace_back (item);
auto& i = items.back ();
if (i->isSeparator ())
AppendMenu (menu, MF_SEPARATOR, 0, nullptr);
else
{
if (i->key != 0)
{
auto title = i->title.getString ();
auto upper = toupper (i->key);
title += "\tCtrl+";
if (upper == i->key)
title += "Shift+";
title += static_cast<char> (upper);
UTF8String titleStr (title.data ());
AppendMenu (menu, MF_STRING, i->id, getWideString (titleStr));
}
else
AppendMenu (menu, MF_STRING, i->id, getWideString (i->title));
}
return items.size ();
}
//------------------------------------------------------------------------
size_t Win32Menu::addItem (UTF8StringView title, char16_t key, uint32_t id)
{
auto item = std::make_shared<Win32MenuItem> ();
item->title = title;
item->key = key;
item->id = id;
return addItem (std::move (item));
}
//------------------------------------------------------------------------
size_t Win32Menu::addSubMenu (const SubMenuPtr& subMenu)
{
HMENU platformSubMenu = *subMenu;
AppendMenu (menu, MF_STRING | MF_POPUP | MF_ENABLED, reinterpret_cast<UINT_PTR> (platformSubMenu),
getWideString (subMenu->title));
items.push_back (subMenu);
return items.size ();
}
//------------------------------------------------------------------------
void Win32Menu::validateMenuItems (const ValidateFunc& func)
{
for (auto& item : items)
{
if (item->id && func (*item))
{
// update item, currently only the enabled state
EnableMenuItem (*this, item->id, (item->isDisabled () ? MF_DISABLED : MF_ENABLED));
}
}
}
//------------------------------------------------------------------------
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,79 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../../lib/cstring.h"
#include <windows.h>
#include <functional>
#include <vector>
#include <memory>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
struct Win32Menu;
//------------------------------------------------------------------------
struct Win32MenuItem
{
UTF8String title;
char16_t key {0};
uint32_t flags {0};
uint32_t id {0};
enum Flags
{
disabled = 1 << 0,
separator = 1 << 1,
submenu = 1 << 2,
};
bool isDisabled () const { return (flags & Flags::disabled) != 0; }
bool isSeparator () const { return (flags & Flags::separator) != 0; }
bool isSubmenu () const { return (flags & Flags::submenu) != 0; }
void disable () { flags |= Flags::disabled; }
void enable () { flags &= ~Flags::disabled; }
virtual Win32Menu* asMenu () { return nullptr; }
virtual ~Win32MenuItem () = default;
};
//------------------------------------------------------------------------
struct Win32Menu : Win32MenuItem
{
using SubMenuPtr = std::shared_ptr<Win32Menu>;
using ItemPtr = std::shared_ptr<Win32MenuItem>;
using Items = std::vector<ItemPtr>;
Win32Menu (UTF8StringView name);
~Win32Menu () noexcept override;
size_t addSeparator ();
size_t addItem (ItemPtr&& item);
size_t addItem (UTF8StringView title, char16_t key = 0, uint32_t id = 0);
size_t addSubMenu (const SubMenuPtr& subMenu);
ItemPtr itemAtIndex (size_t index) const;
using ValidateFunc = std::function<bool (Win32MenuItem& item)>;
void validateMenuItems (const ValidateFunc& func);
operator HMENU () const { return menu; }
Win32Menu* asMenu () override { return this; }
static Win32Menu* fromHMENU (HMENU menu);
//------------------------------------------------------------------------
private:
HMENU menu {nullptr};
Items items;
};
//------------------------------------------------------------------------
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,83 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "win32preference.h"
#include "../../../../lib/platform/win32/win32support.h"
#include "../../../../lib/platform/win32/winstring.h"
#include "../../../include/iappdelegate.h"
#include "../../../include/iapplication.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Win32 {
//------------------------------------------------------------------------
Win32Preference::Win32Preference ()
{
auto& appInfo = IApplication::instance ().getDelegate ().getInfo ();
vstgui_assert (!appInfo.uri.empty (), "need uri for preferences");
UTF8String path ("SOFTWARE\\" + appInfo.uri.getString ());
auto winStr = dynamic_cast<WinString*> (path.getPlatformString ());
vstgui_assert (winStr);
if (winStr)
{
DWORD dw;
RegCreateKeyEx (HKEY_CURRENT_USER, winStr->getWideString (), 0, REG_NONE,
REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_READ, nullptr, &hKey, &dw);
}
}
//------------------------------------------------------------------------
Win32Preference::~Win32Preference ()
{
RegCloseKey (hKey);
}
//------------------------------------------------------------------------
bool Win32Preference::set (const UTF8String& key, const UTF8String& value)
{
auto keyStr = dynamic_cast<WinString*> (key.getPlatformString ());
auto valueStr = dynamic_cast<WinString*> (value.getPlatformString ());
vstgui_assert (keyStr);
bool res = false;
if (keyStr)
res = SUCCEEDED (
RegSetValueEx (hKey, keyStr->getWideString (), NULL, REG_SZ,
reinterpret_cast<const BYTE*> (valueStr->getWideString ()),
static_cast<DWORD> (wcslen (valueStr->getWideString ()) * 2)));
return res;
}
//------------------------------------------------------------------------
Optional<UTF8String> Win32Preference::get (const UTF8String& key)
{
auto keyStr = dynamic_cast<WinString*> (key.getPlatformString ());
vstgui_assert (keyStr);
DWORD dwType {};
DWORD dwCount {};
if (keyStr && SUCCEEDED (
RegQueryValueEx (hKey, keyStr->getWideString (), nullptr, &dwType, nullptr, &dwCount)) &&
dwType == REG_SZ && dwCount > 0)
{
auto buffer = std::make_unique<uint8_t[]> (dwCount + 1);
if (SUCCEEDED (RegQueryValueEx (hKey, keyStr->getWideString (), nullptr, &dwType,
buffer.get (), &dwCount)))
{
UTF8StringHelper helper (reinterpret_cast<const WCHAR*> (buffer.get ()));
return Optional<UTF8String> (UTF8String (helper));
}
}
return {};
}
//------------------------------------------------------------------------
} // Win32
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,34 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../include/ipreference.h"
#include <windows.h>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Win32 {
//------------------------------------------------------------------------
class Win32Preference : public IPreference
{
public:
Win32Preference ();
~Win32Preference ();
bool set (const UTF8String& key, const UTF8String& value) override;
Optional<UTF8String> get (const UTF8String& key) override;
private:
HKEY hKey {nullptr};
};
//------------------------------------------------------------------------
} // Mac
} // Platform
} // Standalone
} // VSTGUI
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,34 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../../include/fwd.h"
#include "../iplatformwindow.h"
#ifndef _WINDEF_
struct HWND__;
using HWND = HWND__*;
#endif
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Platform {
namespace Win32 {
//------------------------------------------------------------------------
class IWin32Window : public Platform::IWindow
{
public:
virtual void updateCommands () const = 0;
virtual void onQuit () = 0;
virtual HWND getHWND () const = 0;
virtual void setModalWindow (const VSTGUI::Standalone::WindowPtr& modalWindow) = 0;
};
} // Win32
} // Platform
} // Standalone
} // VSTGUI
@@ -0,0 +1,350 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "shareduiresources.h"
#include "../../lib/cbitmap.h"
#include "../../lib/ccolor.h"
#include "../../lib/cfileselector.h"
#include "../../lib/cframe.h"
#include "../../uidescription/compresseduidescription.h"
#include "../../uidescription/cstream.h"
#include "../../uidescription/uiattributes.h"
#include "../include/ialertbox.h"
#include "../include/iappdelegate.h"
#include "../include/iapplication.h"
#include "../include/helpers/preferences.h"
#include "application.h"
#include <unordered_map>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
#if VSTGUI_LIVE_EDITING
//------------------------------------------------------------------------
struct EditFileMap : IEditFileMap
{
using Map = std::unordered_map<std::string, std::string>;
Map fileMap;
void set (const std::string& filename, const std::string& absolutePath) override
{
fileMap.emplace (filename, absolutePath);
}
Optional<const char*> get (const std::string& filename) const override
{
auto it = fileMap.find (filename);
if (it == fileMap.end ())
{
return {};
}
return makeOptional (it->second.data ());
}
};
//------------------------------------------------------------------------
IEditFileMap& getEditFileMap ()
{
static EditFileMap gInstance;
return gInstance;
}
#endif
//------------------------------------------------------------------------
class SharedUIResources : public ISharedUIResources
{
public:
static SharedUIResources& instance () noexcept;
SharedUIResources () noexcept;
void cleanup ();
Optional<CColor> getColor (const UTF8String& name) const override;
Optional<CBitmap*> getBitmap (const UTF8String& name) const override;
Optional<CGradient*> getGradient (const UTF8String& name) const override;
Optional<CFontDesc*> getFont (const UTF8String& name) const override;
SharedPointer<UIDescription> get () const
{
load ();
return uiDesc;
}
private:
bool load () const;
mutable bool loadDone {false};
mutable SharedPointer<UIDescription> uiDesc;
};
//------------------------------------------------------------------------
SharedUIResources& SharedUIResources::instance () noexcept
{
static SharedUIResources gInstance;
return gInstance;
}
//------------------------------------------------------------------------
SharedUIResources::SharedUIResources () noexcept
{
}
//------------------------------------------------------------------------
void SharedUIResources::cleanup ()
{
uiDesc = nullptr;
}
//------------------------------------------------------------------------
bool SharedUIResources::load () const
{
if (loadDone)
return uiDesc != nullptr;
loadDone = true;
if (auto filename = IApplication::instance ().getDelegate ().getSharedUIResourceFilename ())
{
#if VSTGUI_LIVE_EDITING
if (auto absPath = Detail::getEditFileMap ().get (filename))
filename = *absPath;
#endif
SharedPointer<UIDescription> description;
if (Detail::getApplicationPlatformAccess ()
->getConfiguration ()
.useCompressedUIDescriptionFiles)
description = makeOwned<CompressedUIDescription> (filename);
else
description = makeOwned<UIDescription> (filename);
if (!description->parse ())
{
#if VSTGUI_LIVE_EDITING
if (!initUIDescAsNew (*description, nullptr))
return false;
#else
return false;
#endif
}
auto settings = description->getCustomAttributes ("UIDescFilePath", true);
auto filePath = settings->getAttributeValue ("path");
if (filePath)
description->setFilePath (filePath->data ());
uiDesc = std::move (description);
#if VSTGUI_LIVE_EDITING
auto res = Detail::checkAndUpdateUIDescFilePath (
*uiDesc, nullptr, "The resource ui desc file location cannot be found.");
if (res == UIDescCheckFilePathResult::Cancel)
{
IApplication::instance ().quit ();
return false;
}
Detail::getEditFileMap ().set (
IApplication::instance ().getDelegate ().getSharedUIResourceFilename (),
uiDesc->getFilePath ());
if (res == UIDescCheckFilePathResult::NewPathSet)
saveSharedUIDescription ();
#endif
}
return uiDesc != nullptr;
}
//------------------------------------------------------------------------
Optional<CColor> SharedUIResources::getColor (const UTF8String& name) const
{
if (load ())
{
CColor c;
if (uiDesc->getColor (name, c))
return makeOptional (c);
}
return {};
}
//------------------------------------------------------------------------
Optional<CBitmap*> SharedUIResources::getBitmap (const UTF8String& name) const
{
if (load ())
{
if (auto bitmap = uiDesc->getBitmap (name))
{
return makeOptional (bitmap);
}
}
return {};
}
//------------------------------------------------------------------------
Optional<CGradient*> SharedUIResources::getGradient (const UTF8String& name) const
{
if (load ())
{
if (auto gradient = uiDesc->getGradient (name))
{
return makeOptional (gradient);
}
}
return {};
}
//------------------------------------------------------------------------
Optional<CFontDesc*> SharedUIResources::getFont (const UTF8String& name) const
{
if (load ())
{
if (auto font = uiDesc->getFont (name))
{
return makeOptional (font);
}
}
return {};
}
//------------------------------------------------------------------------
const ISharedUIResources& getSharedUIResources ()
{
return SharedUIResources::instance ();
}
//------------------------------------------------------------------------
SharedPointer<UIDescription> getSharedUIDescription ()
{
return SharedUIResources::instance ().get ();
}
//------------------------------------------------------------------------
void cleanupSharedUIResources ()
{
SharedUIResources::instance ().cleanup ();
}
#if VSTGUI_LIVE_EDITING
//------------------------------------------------------------------------
static constexpr auto UIDescPathKey = "VSTGUI::Standalone|Debug|UIDescPath|" __FILE__;
//------------------------------------------------------------------------
static void updateUIDescFilePath (const char* path, UIDescription& uiDesc)
{
uiDesc.setFilePath (path);
auto settings = uiDesc.getCustomAttributes ("UIDescFilePath", true);
settings->setAttribute ("path", uiDesc.getFilePath ());
}
//------------------------------------------------------------------------
UIDescCheckFilePathResult checkAndUpdateUIDescFilePath (UIDescription& uiDesc, CFrame* _frame,
UTF8StringPtr notFoundText)
{
auto originalPath = std::string (uiDesc.getFilePath ());
CFileStream stream;
if (stream.open (originalPath.data (), CFileStream::kReadMode))
return UIDescCheckFilePathResult::Exists;
VSTGUI::Standalone::Preferences prefs;
auto savedPath = prefs.get (UIDescPathKey);
if (savedPath)
{
unixfyPath (originalPath);
if (auto uiDescName = lastPathComponent (originalPath))
{
auto directory = savedPath->getString ();
unixfyPath (directory);
removeLastPathComponent (directory);
directory += unixPathSeparator;
directory += *uiDescName;
if (stream.open (directory.data (), CFileStream::kReadMode))
{
updateUIDescFilePath (directory.data (), uiDesc);
return UIDescCheckFilePathResult::NewPathSet;
}
}
}
SharedPointer<CFrame> frame (_frame);
if (!frame)
frame = makeOwned<CFrame> (CRect (), nullptr);
AlertBoxConfig alertConfig;
alertConfig.headline = notFoundText;
alertConfig.description = uiDesc.getFilePath ();
alertConfig.defaultButton = "Locate";
alertConfig.secondButton = "Close";
auto alertResult = IApplication::instance ().showAlertBox (alertConfig);
if (alertResult == AlertResult::SecondButton)
{
return UIDescCheckFilePathResult::Cancel;
}
auto fs = owned (CNewFileSelector::create (frame, CNewFileSelector::kSelectFile));
if (savedPath)
fs->setInitialDirectory (*savedPath);
fs->setDefaultExtension (CFileExtension ("UIDescription File", "uidesc"));
fs->setTitle ("Please locate the shared resources uidesc file");
if (fs->runModal ())
{
if (fs->getNumSelectedFiles () == 0)
{
return UIDescCheckFilePathResult::Cancel;
}
auto path = fs->getSelectedFile (0);
updateUIDescFilePath (path, uiDesc);
prefs.set (UIDescPathKey, path);
return UIDescCheckFilePathResult::NewPathSet;
}
return UIDescCheckFilePathResult::Cancel;
}
//------------------------------------------------------------------------
bool initUIDescAsNew (UIDescription& uiDesc, CFrame* _frame)
{
SharedPointer<CFrame> frame (_frame);
if (!frame)
frame = makeOwned<CFrame> (CRect (), nullptr);
auto fs = owned (CNewFileSelector::create (frame, CNewFileSelector::kSelectSaveFile));
vstgui_assert (fs, "create new FileSelector failed");
VSTGUI::Standalone::Preferences prefs;
if (auto initPath = prefs.get (UIDescPathKey))
fs->setInitialDirectory (*initPath);
fs->setDefaultSaveName (uiDesc.getFilePath ());
fs->setDefaultExtension (CFileExtension ("UIDescription File", "uidesc"));
fs->setTitle ("Save UIDescription File");
if (fs->runModal ())
{
if (fs->getNumSelectedFiles () == 0)
{
return false;
}
auto path = fs->getSelectedFile (0);
uiDesc.setFilePath (path);
auto settings = uiDesc.getCustomAttributes ("UIDescFilePath", true);
settings->setAttribute ("path", path);
prefs.set (UIDescPathKey, path);
return true;
}
return false;
}
//------------------------------------------------------------------------
void saveSharedUIDescription ()
{
if (auto uiDesc = getSharedUIDescription ())
{
int32_t flags = UIDescription::kWriteImagesIntoUIDescFile |
CompressedUIDescription::kForceWriteCompressedDesc;
if (uiDesc->save (uiDesc->getFilePath (), flags))
return;
AlertBoxConfig config;
config.headline = "Saving the shared resources uidesc file failed.";
IApplication::instance ().showAlertBox (config);
}
}
#endif // VSTGUI_LIVE_EDITING
//------------------------------------------------------------------------
} // Detail
} // Standalone
} // VSTGUI
@@ -0,0 +1,66 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../../uidescription/uidescriptionfwd.h"
#include "../../lib/optional.h"
#include "../include/ishareduiresources.h"
#if VSTGUI_LIVE_EDITING
#include <string>
#endif
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
//------------------------------------------------------------------------
const ISharedUIResources& getSharedUIResources ();
//------------------------------------------------------------------------
SharedPointer<UIDescription> getSharedUIDescription ();
//------------------------------------------------------------------------
void cleanupSharedUIResources ();
//------------------------------------------------------------------------
//------------------------------------------------------------------------
//------------------------------------------------------------------------
#if VSTGUI_LIVE_EDITING
void saveSharedUIDescription ();
//------------------------------------------------------------------------
struct IEditFileMap
{
virtual void set (const std::string& filename, const std::string& absolutePath) = 0;
virtual Optional<const char*> get (const std::string& filename) const = 0;
};
//------------------------------------------------------------------------
IEditFileMap& getEditFileMap ();
//------------------------------------------------------------------------
enum class UIDescCheckFilePathResult
{
Exists,
NewPathSet,
Cancel
};
//------------------------------------------------------------------------
UIDescCheckFilePathResult checkAndUpdateUIDescFilePath (
UIDescription& uiDesc, CFrame* _frame,
UTF8StringPtr notFoundText = "The uidesc file location cannot be found.");
//------------------------------------------------------------------------
bool initUIDescAsNew (UIDescription& uiDesc, CFrame* _frame);
#endif // VSTGUI_LIVE_EDITING
//------------------------------------------------------------------------
} // Detail
} // Standalone
} // VSTGUI
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,514 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#include "window.h"
#include "application.h"
#include "../../lib/cframe.h"
#include "../../lib/controls/coptionmenu.h"
#include "../../lib/dispatchlist.h"
#include "../../lib/events.h"
#include "../../uidescription/icontroller.h"
#include "../include/iapplication.h"
#include "../include/icommand.h"
#include "../include/ipreference.h"
#include "../include/iwindowcontroller.h"
#include "platform/iplatformwindow.h"
#include <sstream>
#include <vector>
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
namespace /*anonymous*/ {
//------------------------------------------------------------------------
UTF8String strFromPositionAndSize (const CPoint& pos, const CPoint& size)
{
std::stringstream str;
str << pos.x << "," << pos.y << "," << size.x << "," << size.y;
return UTF8String (str.str ());
}
//------------------------------------------------------------------------
struct PosAndSize
{
CPoint pos;
CPoint size;
};
static CPoint nullPoint {0, 0};
//------------------------------------------------------------------------
PosAndSize positionAndSizeFromString (const UTF8String& str)
{
PosAndSize r;
std::vector<std::string> elements;
std::stringstream stream (str.getString ());
std::string item;
while (std::getline (stream, item, ','))
elements.emplace_back (item);
if (elements.size () != 4)
return r;
r.pos.x = UTF8StringView (elements[0].data ()).toDouble ();
r.pos.y = UTF8StringView (elements[1].data ()).toDouble ();
r.size.x = UTF8StringView (elements[2].data ()).toDouble ();
r.size.y = UTF8StringView (elements[3].data ()).toDouble ();
return r;
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
class Window : public IPlatformWindowAccess,
public Platform::IWindowDelegate,
public IMouseObserver,
public std::enable_shared_from_this<Window>
{
public:
bool init (const WindowConfiguration& config, const WindowControllerPtr& controller);
// IWindow
const WindowControllerPtr& getController () const override { return controller; }
CPoint getSize () const override { return platformWindow->getSize (); }
CPoint getPosition () const override { return platformWindow->getPosition (); }
double getScaleFactor () const override { return platformWindow->getScaleFactor (); }
CRect getFocusViewRect () const override;
const UTF8String& getTitle () const override { return title; }
WindowType getType () const override { return windowType; }
WindowStyle getStyle () const override { return windowStyle; }
const UTF8String& getAutoSaveFrameName () const override { return autoSaveFrameName; }
void setAutoSaveFrameName (const UTF8String& name) override { autoSaveFrameName = name; }
void setSize (const CPoint& newSize) override;
void setPosition (const CPoint& newPosition) override
{
platformWindow->setPosition (newPosition);
}
void setTitle (const UTF8String& newTitle) override
{
title = newTitle;
platformWindow->setTitle (newTitle);
}
void setContentView (const SharedPointer<CFrame>& newFrame) override;
void setRepresentedPath (const UTF8String& path) override;
WindowStyle changeStyle (WindowStyle stylesToAdd, WindowStyle stylesToRemove) override;
void show () override;
void hide () override { platformWindow->hide (); }
void close () override { platformWindow->close (); }
void activate () override { platformWindow->activate (); }
void registerWindowListener (IWindowListener* listener) override;
void unregisterWindowListener (IWindowListener* listener) override;
// IPlatformWindowAccess
InterfacePtr getPlatformWindow () const override { return platformWindow; }
CFrame* getFrame () const override { return frame; }
// Platform::IWindowDelegate
CPoint constraintSize (const CPoint& newSize) override;
void onSizeChanged (const CPoint& newSize) override;
void onPositionChanged (const CPoint& newPosition) override;
void onShow () override;
void onHide () override;
void onClosed () override;
bool canClose () override;
void onActivated () override;
void onDeactivated () override;
// ICommandHandler
bool canHandleCommand (const Command& command) override;
bool handleCommand (const Command& command) override;
// IMouseObserver
void onMouseEntered (CView*, CFrame* ) override {};
void onMouseExited (CView*, CFrame* ) override {};
void onMouseEvent (MouseEvent& event, CFrame*) override;
private:
WindowControllerPtr controller;
WindowStyle windowStyle;
WindowType windowType;
Platform::WindowPtr platformWindow;
SharedPointer<CFrame> frame;
UTF8String autoSaveFrameName;
UTF8String title;
DispatchList<IWindowListener*> windowListeners;
};
//------------------------------------------------------------------------
bool Window::init (const WindowConfiguration& config, const WindowControllerPtr& inController)
{
title = config.title;
windowStyle = config.style;
windowType = config.type;
platformWindow = Platform::makeWindow (config, *this);
if (platformWindow)
{
if (!config.autoSaveFrameName.empty ())
{
autoSaveFrameName = config.autoSaveFrameName;
}
controller = inController;
}
return platformWindow != nullptr;
}
//------------------------------------------------------------------------
void Window::setSize (const CPoint& newSize)
{
CPoint size (newSize);
if (controller)
size = controller->constraintSize (*this, size);
platformWindow->setSize (size);
}
//------------------------------------------------------------------------
void Window::show ()
{
if (controller)
controller->beforeShow (*this);
bool positionChanged = false;
if (!autoSaveFrameName.empty ())
{
if (auto frameName = IApplication::instance ().getPreferences ().get (autoSaveFrameName))
{
auto ps = positionAndSizeFromString (*frameName);
if (ps.pos != nullPoint && ps.size != nullPoint)
{
setPosition (ps.pos);
setSize (ps.size);
positionChanged = true;
}
}
}
if (!positionChanged && windowStyle.isCentered ())
platformWindow->center ();
platformWindow->show ();
}
//------------------------------------------------------------------------
void Window::setContentView (const SharedPointer<CFrame>& newFrame)
{
if (frame)
{
frame->unregisterMouseObserver (this);
frame->close ();
}
frame = newFrame;
if (!frame)
{
if (controller)
controller->onSetContentView (*this, frame);
return;
}
auto frameConfig =
controller ? controller->createPlatformFrameConfig (platformWindow->getPlatformType ()) :
nullptr;
frameConfig = platformWindow->prepareFrameConfig (std::move (frameConfig));
frame->open (platformWindow->getPlatformHandle (), platformWindow->getPlatformType (),
frameConfig.get ());
frame->registerMouseObserver (this);
platformWindow->onSetContentView (frame);
if (controller)
controller->onSetContentView (*this, frame);
}
//------------------------------------------------------------------------
void Window::setRepresentedPath (const UTF8String& path)
{
platformWindow->setRepresentedPath (path);
}
//------------------------------------------------------------------------
WindowStyle Window::changeStyle (WindowStyle stylesToAdd, WindowStyle stylesToRemove)
{
windowStyle = platformWindow->changeStyle (stylesToAdd, stylesToRemove);
return windowStyle;
}
//------------------------------------------------------------------------
CRect Window::getFocusViewRect () const
{
CRect result;
if (frame)
{
if (auto focusView = frame->getFocusView ())
{
result = focusView->getViewSize ();
focusView->translateToGlobal (result);
}
}
return result;
}
//------------------------------------------------------------------------
CPoint Window::constraintSize (const CPoint& _newSize)
{
CPoint newSize (_newSize);
if (frame)
newSize = frame->checkSizeConstraint (newSize);
return controller ? controller->constraintSize (*this, newSize) : newSize;
}
//------------------------------------------------------------------------
void Window::onSizeChanged (const CPoint& newSize)
{
windowListeners.forEach (
[&] (IWindowListener* listener) { listener->onSizeChanged (*this, newSize); });
if (controller)
controller->onSizeChanged (*this, newSize);
}
//------------------------------------------------------------------------
void Window::onPositionChanged (const CPoint& newPosition)
{
windowListeners.forEach (
[&] (IWindowListener* listener) { listener->onPositionChanged (*this, newPosition); });
if (controller)
controller->onPositionChanged (*this, newPosition);
}
//------------------------------------------------------------------------
void Window::onClosed ()
{
auto self = shared_from_this (); // make sure we live as long as this method executes
if (!autoSaveFrameName.empty ())
IApplication::instance ().getPreferences ().set (
autoSaveFrameName, strFromPositionAndSize (getPosition (), getSize ()));
windowListeners.forEach ([&] (IWindowListener* listener) {
listener->onClosed (*this);
windowListeners.remove (listener);
});
if (controller)
controller->onClosed (*this);
platformWindow->onSetContentView (nullptr);
if (frame)
{
frame->unregisterMouseObserver (this);
frame->remember ();
frame->close ();
frame = nullptr;
}
controller = nullptr;
platformWindow = nullptr;
}
//------------------------------------------------------------------------
void Window::onShow ()
{
windowListeners.forEach ([&] (IWindowListener* listener) { listener->onShow (*this); });
if (controller)
controller->onShow (*this);
}
//------------------------------------------------------------------------
void Window::onHide ()
{
windowListeners.forEach ([&] (IWindowListener* listener) { listener->onHide (*this); });
if (controller)
controller->onHide (*this);
}
//------------------------------------------------------------------------
bool Window::canClose ()
{
return controller ? controller->canClose (*this) : true;
}
//------------------------------------------------------------------------
void Window::onActivated ()
{
windowListeners.forEach ([&] (IWindowListener* listener) { listener->onActivated (*this); });
if (controller)
controller->onActivated (*this);
}
//------------------------------------------------------------------------
void Window::onDeactivated ()
{
windowListeners.forEach ([&] (IWindowListener* listener) { listener->onDeactivated (*this); });
if (controller)
controller->onDeactivated (*this);
}
//------------------------------------------------------------------------
void Window::registerWindowListener (IWindowListener* listener)
{
windowListeners.add (listener);
}
//------------------------------------------------------------------------
void Window::unregisterWindowListener (IWindowListener* listener)
{
windowListeners.remove (listener);
}
//------------------------------------------------------------------------
bool Window::canHandleCommand (const Command& command)
{
if (command == Commands::CloseWindow)
return controller->canClose (*this);
if (auto focusView = frame->getFocusView ())
{
if (auto viewController = getViewController (focusView, false))
{
if (auto commandHandler = dynamic_cast<ICommandHandler*> (viewController))
{
if (commandHandler->canHandleCommand (command))
return true;
}
}
}
if (auto commandHandler = dynamicPtrCast<ICommandHandler> (controller))
{
if (commandHandler->canHandleCommand (command))
return true;
}
return false;
}
//------------------------------------------------------------------------
bool Window::handleCommand (const Command& command)
{
if (command == Commands::CloseWindow)
{
close ();
return true;
}
if (auto focusView = frame->getFocusView ())
{
if (auto viewController = getViewController (focusView, false))
{
if (auto commandHandler = dynamic_cast<ICommandHandler*> (viewController))
{
if (commandHandler->handleCommand (command))
return true;
}
}
}
if (auto commandHandler = dynamicPtrCast<ICommandHandler> (controller))
{
if (commandHandler->handleCommand (command))
return true;
}
return false;
}
//------------------------------------------------------------------------
struct WindowContextMenuCommandHandler : ICommandMenuItemTarget, NonAtomicReferenceCounted
{
WindowContextMenuCommandHandler (Window* window) : window (window) {}
bool validateCommandMenuItem (CCommandMenuItem* item) override
{
Command cmd = {item->getCommandCategory (), item->getCommandName ()};
if (window->canHandleCommand (cmd) || getApplicationPlatformAccess ()->canHandleCommand (cmd))
return true;
item->setEnabled (false);
return false;
}
bool onCommandMenuItemSelected (CCommandMenuItem* item) override
{
Command cmd = {item->getCommandCategory (), item->getCommandName ()};
if (window->handleCommand (cmd))
return true;
return getApplicationPlatformAccess ()->handleCommand (cmd);
}
Window* window;
};
//------------------------------------------------------------------------
void Window::onMouseEvent (MouseEvent& event, CFrame* inFrame)
{
if (event.type != EventType::MouseDown || !event.buttonState.isRight ())
return;
auto contextMenu = makeOwned<COptionMenu> ();
CPoint where (event.mousePosition);
inFrame->getTransform ().transform (where);
CViewContainer::ViewList views;
if (inFrame->getViewsAt (where, views, GetViewOptions ().deep ().includeViewContainer ()))
{
for (const auto& view : views)
{
auto viewController = getViewController (view);
auto contextMenuController = dynamic_cast<IContextMenuController*> (viewController);
auto contextMenuController2 = dynamic_cast<IContextMenuController2*> (viewController);
if (contextMenuController == nullptr && contextMenuController2 == nullptr)
continue;
if (contextMenu->getNbEntries () != 0)
contextMenu->addSeparator ();
CPoint p (event.mousePosition);
view->frameToLocal (p);
if (contextMenuController2)
contextMenuController2->appendContextMenuItems (*contextMenu, view, p);
else if (contextMenuController)
contextMenuController->appendContextMenuItems (*contextMenu, p);
}
}
if (contextMenu->getNbEntries () == 0 &&
getApplicationPlatformAccess ()->getConfiguration ().showCommandsInWindowContextMenu)
{
auto commandList = getApplicationPlatformAccess ()->getCommandList (
staticPtrCast<Platform::IWindow> (getPlatformWindow ()).get ());
if (!commandList.empty ())
{
auto menuHandler = makeOwned<WindowContextMenuCommandHandler> (this);
for (const auto& cat : commandList)
{
auto item = new CMenuItem (cat.first);
auto catMenu = new COptionMenu ();
item->setSubmenu (catMenu);
for (const auto& entry : cat.second)
{
if (entry.name == CommandName::MenuSeparator)
{
catMenu->addSeparator ();
}
else
{
auto catItem =
new CCommandMenuItem ({entry.name, menuHandler, entry.group, entry.name});
catMenu->addEntry (catItem);
}
}
if (catMenu->getNbEntries () > 0)
contextMenu->addEntry (item);
else
item->forget ();
}
}
}
if (contextMenu->getNbEntries () > 0)
{
contextMenu->cleanupSeparators (true);
contextMenu->setStyle (COptionMenu::kPopupStyle | COptionMenu::kMultipleCheckStyle);
contextMenu->popup (inFrame, event.mousePosition);
event.consumed = true;
castMouseDownEvent (event).ignoreFollowUpMoveAndUpEvents (true);
}
}
//------------------------------------------------------------------------
WindowPtr makeWindow (const WindowConfiguration& config, const WindowControllerPtr& controller)
{
auto window = std::make_shared<Detail::Window> ();
if (!window->init (config, controller))
return nullptr;
return window;
}
//------------------------------------------------------------------------
} // Detail
} // Standalone
} // VSTGUI
@@ -0,0 +1,28 @@
// This file is part of VSTGUI. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this
// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE
#pragma once
#include "../include/iwindow.h"
//------------------------------------------------------------------------
namespace VSTGUI {
namespace Standalone {
namespace Detail {
//------------------------------------------------------------------------
WindowPtr makeWindow (const WindowConfiguration& config, const WindowControllerPtr& controller);
//------------------------------------------------------------------------
class IPlatformWindowAccess : public IWindow
{
public:
virtual InterfacePtr getPlatformWindow () const = 0;
virtual CFrame* getFrame () const = 0;
};
//------------------------------------------------------------------------
} // Detail
} // Standalone
} // VSTGUI