[Patch] Clipboard events

scrawl

17-10-2013 11:30:23

I use SDL to add clipboard behaviour (cut, copy & paste) to a MyGUI program. Code:

if (arg.keysym.sym == SDLK_v && (arg.keysym.mod & SDL_Keymod(KMOD_CTRL)))
{
char* text = SDL_GetClipboardText();

if (text)
{
std::vector<unsigned long> unicode = utf8ToUnicode(std::string(text));
for (std::vector<unsigned long>::iterator it = unicode.begin(); it != unicode.end(); ++it)
MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it);
}
}

The problem is that MyGUI never enters text while you have CTRL held down. Therefore, using Ctrl+v never actually pastes text :(

A workaround is here, but it is far from optimal:
MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::LeftControl);
MyGUI::InputManager::getInstance().injectKeyRelease(MyGUI::KeyCode::RightControl);

I would appreciate if there was a way to directly insert text into MyGUI, without paying attention to modifiers pressed.

Altren

17-10-2013 15:02:35

Instead of manually printing characters ou should use clipboard manager:
MyGUI::ClipboardManaager::getInstance().setClipboardData("Text", yourClipboardString);
and then Ctrl+V would work just fine.

scrawl

17-10-2013 15:37:24

Wow cool, wasn't even aware that existed - going to try this out now :)

scrawl

17-10-2013 16:16:10

MyGUI's clipboard manager doesn't work for me. And I've found out why:
I use SDL's text input api, which means there is a separation between text input events and regular key events.
For key events, I do
MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::Enum(kc), 0);
And for text input events, I do

for (std::vector<unsigned long>::iterator it = unicode.begin(); it != unicode.end(); ++it)
MyGUI::InputManager::getInstance().injectKeyPress(MyGUI::KeyCode::None, *it);

The problem is in EditBox.cpp:

else if (_char != 0)
{

...
else if (_key == KeyCode::C)
{
commandCopy();



This will only work if both text and keycode are injected at the same time. Is there any reason it has to be this way?

And, I have another idea for the next version: Provide clipboard events in ClipboardManager, so that it's possible to put the text copied by MyGUI into the system clipboard. (Right now there's a hardcoded implementation for Windows clipboard in MyGUI, which is pretty awful - better to leave it to the user in an event so that SDL or any other library can be hooked up with the clipboard)

Altren

19-10-2013 17:02:25

else if (_char != 0)I don't even have such line in my code. And it should work just fine without char.
And yeah, I agree about needed changes for ClipboardManager.

scrawl

19-10-2013 17:11:37

else if (_char != 0)I don't even have such line in my code. And it should work just fine without char.

Yep, I can see this is already fixed in SVN. The version I had here was 3.2.

scrawl

27-01-2014 01:14:06

Hi again,

I just tried out my project with MyGUI SVN. It is working well.

And yeah, I agree about needed changes for ClipboardManager.


Would you be interested in a patch for these changes? I could prepare one.

Altren

27-01-2014 13:31:44

Yes, patch would be good.

scrawl

27-01-2014 18:00:55

Here is the clipboard event patch.


Index: MyGUIEngine/include/MyGUI_ClipboardManager.h
===================================================================
--- MyGUIEngine/include/MyGUI_ClipboardManager.h (revision 5292)
+++ MyGUIEngine/include/MyGUI_ClipboardManager.h (working copy)
@@ -24,30 +24,41 @@
void initialise();
void shutdown();

- /** Set current data in clipboard
+ /** Set current data in clipboard and trigger eventClipboardChanged. To be used by widgets.
@param _type of data (for example "Text")
@param _data
*/
void setClipboardData(const std::string& _type, const std::string& _data);
+
/** Clear specific type data
@param _type of data to delete (for example "Text")
*/
void clearClipboardData(const std::string& _type);
+
/** Get specific type data
@param _type of data to get (for example "Text")
*/
std::string getClipboardData(const std::string& _type);

+ /*events:*/
+ /** Event : Clipboard content was changed via setClipboardData.\n
+ signature : void method(const std::string& _type, const std::string& _data)\n
+ @param _type of data (for example "Text")
+ @param _data
+ */
+ delegates::CMultiDelegate2<const std::string&, const std::string&> eventClipboardChanged;
+
+ /** Event : The content of the clipboard is being requested via getClipboardData.\n
+ Delegates of this event can modify the _data argument in-place to change the data returned by getClipboardData.
+ signature : void method(const std::string& _type, std::string& _data)\n
+ @param _type of data (for example "Text")
+ @param _data
+ */
+ delegates::CMultiDelegate2<const std::string&, std::string&> eventClipboardRequested;
+
private:
MapString mClipboardData;

-#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
- // дескриптор нашего главного окна
- size_t mHwnd;
- // строка, которую мы положили в буфер обмена винды
- UString mPutTextInClipboard;
-#endif
-
bool mIsInitialise;
};

Index: MyGUIEngine/src/MyGUI_ClipboardManager.cpp
===================================================================
--- MyGUIEngine/src/MyGUI_ClipboardManager.cpp (revision 5292)
+++ MyGUIEngine/src/MyGUI_ClipboardManager.cpp (working copy)
@@ -9,55 +9,9 @@
#include "MyGUI_Gui.h"
#include "MyGUI_TextIterator.h"

-#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
-#include <windows.h>
-#endif
-
namespace MyGUI
{

-#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
-
- HWND g_hWnd = NULL;
-
- BOOL CALLBACK EnumWindowProc(HWND hWnd, LPARAM lParam)
- {
- DWORD dwProcessID = 0;
- GetWindowThreadProcessId(hWnd, &dwProcessID);
-
- if (dwProcessID != (DWORD)lParam)
- return TRUE;
-
- if (GetParent(hWnd) == NULL)
- {
- // Нашли. hWnd - то что надо
- g_hWnd = hWnd;
- return FALSE;
- }
-
- return TRUE;
- }
-
- BOOL CALLBACK EnumChildWindowProc(HWND hWnd, LPARAM lParam)
- {
- DWORD dwProcessID = 0;
- GetWindowThreadProcessId(hWnd, &dwProcessID);
-
- if (dwProcessID != GetCurrentProcessId())
- return TRUE;
-
- if (GetWindowLongPtr(hWnd, GWLP_HINSTANCE) == lParam)
- {
- // Нашли. hWnd - то что надо
- g_hWnd = hWnd;
- return FALSE;
- }
-
- return TRUE;
- }
-
-#endif
-
template <> ClipboardManager* Singleton<ClipboardManager>::msInstance = nullptr;
template <> const char* Singleton<ClipboardManager>::mClassTypeName = "ClipboardManager";

@@ -71,18 +25,6 @@
MYGUI_ASSERT(!mIsInitialise, getClassTypeName() << " initialised twice");
MYGUI_LOG(Info, "* Initialise: " << getClassTypeName());

-#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
- // берем имя нашего экзешника
- char buf[MAX_PATH];
- GetModuleFileName(0, (LPCH)&buf, MAX_PATH);
- // берем инстанс нашего модуля
- HINSTANCE instance = GetModuleHandle(buf);
-
- EnumChildWindows(GetDesktopWindow(), (WNDENUMPROC)EnumWindowProc, (LPARAM)instance);
- mHwnd = (size_t)g_hWnd;
-
-#endif
-
MYGUI_LOG(Info, getClassTypeName() << " successfully initialized");
mIsInitialise = true;
}
@@ -100,27 +42,7 @@
{
mClipboardData[_type] = _data;

-#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
- if (_type == "Text")
- {
- mPutTextInClipboard = TextIterator::getOnlyText(UString(_data));
- size_t size = (mPutTextInClipboard.size() + 1) * 2;
- //открываем буфер обмена
- if (OpenClipboard((HWND)mHwnd))
- {
- EmptyClipboard(); //очищаем буфер
- HGLOBAL hgBuffer = GlobalAlloc(GMEM_DDESHARE, size);//выделяем память
- wchar_t* chBuffer = hgBuffer ? (wchar_t*)GlobalLock(hgBuffer) : NULL;
- if (chBuffer)
- {
- memcpy(chBuffer, mPutTextInClipboard.asWStr_c_str(), size);
- GlobalUnlock(hgBuffer);//разблокируем память
- SetClipboardData(CF_UNICODETEXT, hgBuffer);//помещаем текст в буфер обмена
- }
- CloseClipboard(); //закрываем буфер обмена
- }
- }
-#endif
+ eventClipboardChanged(_type, _data);
}

void ClipboardManager::clearClipboardData(const std::string& _type)
@@ -131,39 +53,14 @@

std::string ClipboardManager::getClipboardData(const std::string& _type)
{
-#if MYGUI_PLATFORM == MYGUI_PLATFORM_WIN32
- if (_type == "Text")
- {
- UString buff;
- //открываем буфер обмена
- if (OpenClipboard((HWND)mHwnd))
- {
- HANDLE hData = GetClipboardData(CF_UNICODETEXT);//извлекаем текст из буфера обмена
- wchar_t* chBuffer = hData ? (wchar_t*)GlobalLock(hData) : NULL;
- if (chBuffer)
- {
- buff = chBuffer;
- GlobalUnlock(hData);//разблокируем память
- }
- CloseClipboard();//закрываем буфер обмена
- }
- // если в буфере не то что мы ложили, то берем из буфера
- if (mPutTextInClipboard != buff)
- {
- // вставляем теги, если нуно
- const UString& text = TextIterator::toTagsString(buff);
- return text.asUTF8();
- }
-
- MapString::iterator iter = mClipboardData.find(_type);
- if (iter != mClipboardData.end()) return (*iter).second;
- return "";
- }
-#endif
-
+ std::string ret;
MapString::iterator iter = mClipboardData.find(_type);
- if (iter != mClipboardData.end()) return (*iter).second;
- return "";
+ if (iter != mClipboardData.end())
+ ret = (*iter).second;
+
+ // Give delegates a chance to fill the clipboard with data
+ eventClipboardRequested(_type, ret);
+ return ret;
}

} // namespace MyGUI


The windows clipboard implementation has been removed. It could be re-added to Demo programs, but I didn't do that since I don't develop on windows and wouldn't have been able to test it.

Here is a tutorial how to use the new clipboard events with SDL:

Register callbacks:

MyGUI::ClipboardManager::getInstance().eventClipboardChanged += MyGUI::newDelegate(this, &MyClass::onClipboardChanged);
MyGUI::ClipboardManager::getInstance().eventClipboardRequested += MyGUI::newDelegate(this, &MyClass::onClipboardRequested);


And the callbacks look like this:

void MyClass::onClipboardChanged(const std::string &_type, const std::string &_data)
{
if (_type == "Text")
SDL_SetClipboardText(MyGUI::TextIterator::getOnlyText(MyGUI::UString(_data)).asUTF8().c_str());
}

void MyClass::onClipboardRequested(const std::string &_type, std::string &_data)
{
if (_type != "Text")
return;
char* text=0;
text = SDL_GetClipboardText();
if (text)
{
// MyGUI's clipboard might still have color information, to retain that information, only set the new text
// if it actually changed (clipboard inserted by an external application)
if (MyGUI::TextIterator::getOnlyText(_data) != text)
_data = MyGUI::TextIterator::toTagsString(text);
}
SDL_free(text);
}

scrawl

29-01-2014 01:03:41

Updated usage tutorial - added escaping of # characters from external clipboard (TextIterator::toTagsString)

scrawl

27-04-2014 09:42:17

No feedback? :(

Altren

27-04-2014 23:59:55

Sorry, I had no time to integrate this yet, since current version of patch breaks old logic it is necessary to create replacement for it.

scrawl

05-08-2014 23:03:20

I'm not sure I understand. What logic does it break? What needs to be replaced?

Altren

06-08-2014 12:18:50

It removes old logic of clipboard handling under windows, so applying this patch would remove possibility to use clipboard under windows without additional changes in users code. I think that system clipboard should work out of the box.
I have some free time and I'll integrate this patch in next few days (most likely today).

scrawl

06-08-2014 14:19:20

would remove possibility to use clipboard under windows without additional changes in users code. I think that system clipboard should work out of the box.
Okay, sure. How do you want to implement it? Maybe a clipboard delegate that is added by default? Then for users who do not want the default windows clipboard implementation (since it would probably conflict with SDL), they can easily disable it by clearing the clipboard delegates after MyGUI startup.

Altren

06-08-2014 17:18:24

Yes, I used default clipboard event. Custom clipboard event can be used in addition or as replacement for it.

scrawl

06-08-2014 17:52:55

Just tested your commit, works beautifully. Topic solved!