Initial release
This commit is contained in:
@@ -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 = ¶ms.preferences;
|
||||
commonDirectories = ¶ms.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
|
||||
+210
@@ -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;
|
||||
}
|
||||
+21
@@ -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
|
||||
Vendored
+59
@@ -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
|
||||
+28
@@ -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
|
||||
+129
@@ -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
|
||||
+36
@@ -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
|
||||
+137
@@ -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
|
||||
+46
@@ -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
|
||||
+470
@@ -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
|
||||
+31
@@ -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
|
||||
+67
@@ -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
|
||||
+15
@@ -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
|
||||
+15
@@ -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
|
||||
+733
@@ -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);
|
||||
}
|
||||
+27
@@ -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
|
||||
Vendored
+109
@@ -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
|
||||
+27
@@ -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
|
||||
+36
@@ -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
|
||||
+20
@@ -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 ()];
|
||||
}
|
||||
+29
@@ -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
|
||||
+843
@@ -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
|
||||
+333
@@ -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;
|
||||
}
|
||||
Vendored
+137
@@ -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
|
||||
Vendored
+39
@@ -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
|
||||
+135
@@ -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
|
||||
+79
@@ -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
|
||||
+83
@@ -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
|
||||
+34
@@ -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
|
||||
+1090
File diff suppressed because it is too large
Load Diff
+34
@@ -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
|
||||
+350
@@ -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
|
||||
+1125
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
|
||||
Reference in New Issue
Block a user