Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 156 additions & 41 deletions modules/highgui/src/window_w32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ using namespace cv;
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <variant>

#ifdef HAVE_OPENGL
#include <memory>
Expand Down Expand Up @@ -119,6 +120,107 @@ static inline void mingw_strcat_s(char *dest, size_t destsz, const char *src){
#define strcat_s mingw_strcat_s
#endif

// assistant class to ease compiling this in both classic ANSI mode and modern UNICODE builds.
//
// the moral of the story for this entire source file: ditch the Win32 A (ANSI) API calls' hack, and
// use what they (MS) hand you as-is (W or A, depending on your compile settings) and transform any
// strings to the type that's needed: ANSI/UTF8 or UCS2/UTF16.
//
// root cause: see also https://stackoverflow.com/questions/33358546/chinese-characters-in-title-bar
// which was someone else with what turned out to be the same exhibit (this code also has
// DefWindowProc calls in the window handlers making the whole thing b0rk if you're not extra-special
// careful in coding this sort of thing.
// Bottom line: as soon as you're using DefWindowProc in *any* way, stick with what Microsoft
// system header files give you as the 'default' Win32 API and deal! TCHAR, TEXT(), etc.
// + Win32UIString class and our personal T() macro for the non-static strings.
class Win32UIString {
std::variant<std::string, std::wstring> s_;

std::wstring UTF8ToWide(const std::string& utf8) {
if (utf8.length() == 0) {
return std::wstring();
}

// compute the length of the buffer we'll need
int charcount = MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, nullptr, 0);

if (charcount == 0) {
return std::wstring();
}

// convert
wchar_t* buf = new wchar_t[charcount];
MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), -1, buf, charcount);
std::wstring result(buf);
delete[] buf;
return result;
}

std::string WideToUTF8(const std::wstring& wide) {
if (wide.length() == 0) {
return std::string();
}

// compute the length of the buffer we'll need
int charcount = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, nullptr, 0, nullptr, nullptr);
if (charcount == 0) {
return std::string();
}

// convert
char* buf = new char[charcount];
WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), -1, buf, charcount, nullptr, nullptr);

std::string result(buf);
delete[] buf;
return result;
}

public:
Win32UIString() = delete;
Win32UIString(const char* str) : s_(str) {}
Win32UIString(const std::string& str) : s_(str) {}
Win32UIString(const wchar_t* str) : s_(str) {}
Win32UIString(const std::wstring& str) : s_(str) {}

#ifdef UNICODE // r_winnt
LPCWSTR operator()() {
return LPCWSTR();
}
#else
LPCSTR operator()() {
return LPCSTR();
}
#endif
LPCWSTR LPCWSTR() {
std::wstring* rv = std::get_if<std::wstring>(&s_);
if (rv != nullptr)
return rv->c_str();
std::wstring r2 = UTF8ToWide(std::get<std::string>(s_));
// can't use the *local* instance, so we push it into the class member first:
s_ = r2;
rv = std::get_if<std::wstring>(&s_);
return rv->c_str();
}
LPCSTR LPCSTR() {
std::string* rv = std::get_if<std::string>(&s_);
if (rv != nullptr)
return rv->c_str();
std::string r2 = WideToUTF8(std::get<std::wstring>(s_));
// can't use the *local* instance, so we push it into the class member first:
s_ = r2;
rv = std::get_if<std::string>(&s_);
return rv->c_str();
}
const char *c_str() {
return LPCSTR();
}
};


#define T(s) Win32UIString(s)()


static void FillBitmapInfo(BITMAPINFO* bmi, int width, int height, int bpp, int origin)
{
CV_Assert(bmi && width >= 0 && height >= 0 && (bpp == 8 || bpp == 24 || bpp == 32));
Expand Down Expand Up @@ -304,10 +406,10 @@ typedef int (CV_CDECL * CvWin32WindowCallback)(HWND, UINT, WPARAM, LPARAM, int*)
static CvWin32WindowCallback hg_on_preprocess = 0, hg_on_postprocess = 0;
static HINSTANCE hg_hinstance = 0;

static const char* const highGUIclassName = "HighGUI class";
static const char* const mainHighGUIclassName = "Main HighGUI class";
#define highGUIclassName TEXT("HighGUI class")
#define mainHighGUIclassName TEXT("Main HighGUI class")

static void icvCleanupHighgui()
static void icvCleanupHighgui(void)
{
cvDestroyAllWindows();
UnregisterClass(highGUIclassName, hg_hinstance);
Expand All @@ -334,7 +436,7 @@ CV_IMPL int cvInitSystem(int, char**)
wndc.lpszClassName = highGUIclassName;
wndc.lpszMenuName = highGUIclassName;
wndc.hIcon = LoadIcon(0, IDI_APPLICATION);
wndc.hCursor = (HCURSOR)LoadCursor(0, (LPSTR)(size_t)IDC_CROSS);
wndc.hCursor = (HCURSOR)LoadCursor(0, (LPCTSTR)(size_t)IDC_CROSS);
wndc.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH);

RegisterClass(&wndc);
Expand Down Expand Up @@ -410,16 +512,16 @@ icvLoadWindowPos(const char* name, CvRect& rect)
rect.x = rect.y = CW_USEDEFAULT;
rect.width = rect.height = 320;

if (RegOpenKeyEx(HKEY_CURRENT_USER,szKey,0,KEY_QUERY_VALUE,&hkey) == ERROR_SUCCESS)
if (RegOpenKeyEx(HKEY_CURRENT_USER,T(szKey),0,KEY_QUERY_VALUE,&hkey) == ERROR_SUCCESS)
{
// Yes we are installed.
DWORD dwType = 0;
DWORD dwSize = sizeof(int);

RegQueryValueEx(hkey, "Left", NULL, &dwType, (BYTE*)&rect.x, &dwSize);
RegQueryValueEx(hkey, "Top", NULL, &dwType, (BYTE*)&rect.y, &dwSize);
RegQueryValueEx(hkey, "Width", NULL, &dwType, (BYTE*)&rect.width, &dwSize);
RegQueryValueEx(hkey, "Height", NULL, &dwType, (BYTE*)&rect.height, &dwSize);
RegQueryValueEx(hkey, TEXT("Left"), NULL, &dwType, (BYTE*)&rect.x, &dwSize);
RegQueryValueEx(hkey, TEXT("Top"), NULL, &dwType, (BYTE*)&rect.y, &dwSize);
RegQueryValueEx(hkey, TEXT("Width"), NULL, &dwType, (BYTE*)&rect.width, &dwSize);
RegQueryValueEx(hkey, TEXT("Height"), NULL, &dwType, (BYTE*)&rect.height, &dwSize);

// Snap rect into closest monitor in case it falls outside it. // Adi Shavit
// set WIN32 RECT to be the loaded size
Expand Down Expand Up @@ -468,18 +570,18 @@ icvSaveWindowPos(const char* name, CvRect rect)
strcpy_s(szKey, 1024, icvWindowPosRootKey);
strcat_s(szKey, 1024, name);

if (RegOpenKeyEx(HKEY_CURRENT_USER,szKey,0,KEY_READ,&hkey) != ERROR_SUCCESS)
if (RegOpenKeyEx(HKEY_CURRENT_USER,T(szKey),0,KEY_READ,&hkey) != ERROR_SUCCESS)
{
HKEY hroot;
DWORD count = 0;
FILETIME oldestTime = { UINT_MAX, UINT_MAX };
char oldestKey[1024];
char currentKey[1024];
TCHAR currentKey[1024];

strcpy_s(rootKey, 1024, icvWindowPosRootKey);
rootKey[strlen(rootKey)-1] = '\0';
if (RegCreateKeyEx(HKEY_CURRENT_USER, rootKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ+KEY_WRITE, 0, &hroot, NULL) != ERROR_SUCCESS)
//RegOpenKeyEx(HKEY_CURRENT_USER,rootKey,0,KEY_READ,&hroot) != ERROR_SUCCESS)
if (RegCreateKeyEx(HKEY_CURRENT_USER, T(rootKey), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_READ+KEY_WRITE, 0, &hroot, NULL) != ERROR_SUCCESS)
//RegOpenKeyEx(HKEY_CURRENT_USER,T(rootKey),0,KEY_READ,&hroot) != ERROR_SUCCESS)
return;

for(;;)
Expand All @@ -495,28 +597,28 @@ icvSaveWindowPos(const char* name, CvRect rect)
oldestTime.dwLowDateTime > accesstime.dwLowDateTime))
{
oldestTime = accesstime;
strcpy_s(oldestKey, 1024, currentKey);
strcpy_s(oldestKey, 1024, Win32UIString(currentKey).c_str());
}
}

if (count >= MAX_RECORD_COUNT)
RegDeleteKey(hroot, oldestKey);
RegDeleteKey(hroot, T(oldestKey));
RegCloseKey(hroot);

if (RegCreateKeyEx(HKEY_CURRENT_USER,szKey,0,NULL,REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &hkey, NULL) != ERROR_SUCCESS)
if (RegCreateKeyEx(HKEY_CURRENT_USER,T(szKey),0,NULL,REG_OPTION_NON_VOLATILE, KEY_WRITE, 0, &hkey, NULL) != ERROR_SUCCESS)
return;
}
else
{
RegCloseKey(hkey);
if (RegOpenKeyEx(HKEY_CURRENT_USER,szKey,0,KEY_WRITE,&hkey) != ERROR_SUCCESS)
if (RegOpenKeyEx(HKEY_CURRENT_USER,T(szKey),0,KEY_WRITE,&hkey) != ERROR_SUCCESS)
return;
}

RegSetValueEx(hkey, "Left", 0, REG_DWORD, (BYTE*)&rect.x, sizeof(rect.x));
RegSetValueEx(hkey, "Top", 0, REG_DWORD, (BYTE*)&rect.y, sizeof(rect.y));
RegSetValueEx(hkey, "Width", 0, REG_DWORD, (BYTE*)&rect.width, sizeof(rect.width));
RegSetValueEx(hkey, "Height", 0, REG_DWORD, (BYTE*)&rect.height, sizeof(rect.height));
RegSetValueEx(hkey, TEXT("Left"), 0, REG_DWORD, (BYTE*)&rect.x, sizeof(rect.x));
RegSetValueEx(hkey, TEXT("Top"), 0, REG_DWORD, (BYTE*)&rect.y, sizeof(rect.y));
RegSetValueEx(hkey, TEXT("Width"), 0, REG_DWORD, (BYTE*)&rect.width, sizeof(rect.width));
RegSetValueEx(hkey, TEXT("Height"), 0, REG_DWORD, (BYTE*)&rect.height, sizeof(rect.height));
RegCloseKey(hkey);
}

Expand Down Expand Up @@ -654,11 +756,11 @@ double cvGetPropTopmost_W32(const char* name)

static double getPropTopmost_(CvWindow& window)
{
LONG style = GetWindowLongA(window.frame, GWL_EXSTYLE); // -20
LONG style = GetWindowLong(window.frame, GWL_EXSTYLE); // -20
if (!style)
{
std::ostringstream errorMsg;
errorMsg << "window(" << window.name << "): failed to retrieve extended window style using GetWindowLongA(); error code: " << GetLastError();
errorMsg << "window(" << window.name << "): failed to retrieve extended window style using GetWindowLong(); error code: " << GetLastError();
CV_Error(Error::StsError, errorMsg.str());
}

Expand Down Expand Up @@ -817,7 +919,7 @@ void setWindowTitle_W32(const std::string& name, const std::string& title)
if (!window)
CV_Error(Error::StsNullPtr, "NULL window");

if (!SetWindowText(window->frame, title.c_str()))
if (!SetWindowText(window->frame, T(title)))
CV_Error_(Error::StsError, ("Failed to set \"%s\" window title to \"%s\"", name.c_str(), title.c_str()));
}

Expand Down Expand Up @@ -1063,15 +1165,22 @@ static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags
defStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
#endif

mainhWnd = CreateWindow(mainHighGUIclassName, name.c_str(), defStyle | WS_OVERLAPPED,
rect.x, rect.y, rect.width, rect.height, 0, 0, hg_hinstance, 0);
auto window = std::make_shared<CvWindow>(name);

mainhWnd = CreateWindowEx(0, mainHighGUIclassName, T(window->name), defStyle | WS_OVERLAPPEDWINDOW,
rect.x, rect.y, rect.width, rect.height, HWND_DESKTOP, nullptr, hg_hinstance, nullptr);
if (!mainhWnd)
CV_Error_(Error::StsError, ("Frame window can not be created: '%s'", name.c_str()));

ShowWindow(mainhWnd, SW_SHOW);

#if 0 // tests showcasing the new 'default Win32 APIs only + WinUIString/T approach *works*
SetWindowText(mainhWnd, L"BUGGER");
SetWindowTextA(mainhWnd, "TOTAL CRAP");
#endif

//YV- remove one border by changing the style
hWnd = CreateWindow(highGUIclassName, "", (defStyle & ~WS_SIZEBOX) | WS_CHILD, CW_USEDEFAULT, 0, rect.width, rect.height, mainhWnd, 0, hg_hinstance, 0);
hWnd = CreateWindowEx(0, highGUIclassName, TEXT("CHILD"), (defStyle & ~WS_SIZEBOX) | WS_CHILD, CW_USEDEFAULT, CW_USEDEFAULT, rect.width, rect.height, mainhWnd, 0, hg_hinstance, 0);
if (!hWnd)
CV_Error(Error::StsError, "Frame window can not be created");

Expand All @@ -1089,8 +1198,6 @@ static std::shared_ptr<CvWindow> namedWindow_(const std::string& name, int flags

ShowWindow(hWnd, SW_SHOW);

auto window = std::make_shared<CvWindow>(name);

window->hwnd = hWnd;
window->frame = mainhWnd;
window->flags = flags;
Expand Down Expand Up @@ -1541,7 +1648,6 @@ MainWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
break;

case WM_DESTROY:

icvRemoveWindow(window_);
// Do nothing!!!
//PostQuitMessage(0);
Expand Down Expand Up @@ -1786,7 +1892,13 @@ static LRESULT CALLBACK HighGUIProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM

// Finally, set bitmap to clipboard
::SetClipboardData(CF_BITMAP, memBM);
} while (0,0); // (0,0) instead of (0) to avoid MSVC compiler warning C4127: "conditional expression is constant"
#if defined(_MSC_VER)
#pragma warning(disable: 4127 4548)
#endif
} while (0); // while(0,0) instead of while(0) to avoid MSVC compiler warning C4127: "conditional expression is constant"
// but now gives us this instead:
// warning C4548 : expression before comma has no effect; expected expression with side-effect


//////////////////////////////////////////////////////////////////////////
// if handle is allocated (i.e. != 0) then clean-up.
Expand Down Expand Up @@ -2031,7 +2143,7 @@ static void icvUpdateTrackbar(CvTrackbar& trackbar, int pos)
}

snprintf(pos_text + strlen(pos_text), sizeof(pos_text) - strlen(pos_text), "%s: %d\n", suffix, pos);
SetWindowText(trackbar.buddy, pos_text);
SetWindowText(trackbar.buddy, T(pos_text));
}
}

Expand Down Expand Up @@ -2137,7 +2249,7 @@ static void showSaveDialog(CvWindow& window)
if (!icvGetBitmapData(window, sz, channels, data))
return; // nothing to save

char szFileName[MAX_PATH] = "";
TCHAR szFileName[MAX_PATH] = TEXT("");
// try to use window title as file name
GetWindowText(window.frame, szFileName, MAX_PATH);

Expand All @@ -2150,7 +2262,8 @@ static void showSaveDialog(CvWindow& window)
ofn.lStructSize = sizeof(ofn);
#endif
ofn.hwndOwner = window.hwnd;
ofn.lpstrFilter =
// this Win32UIString instance MUST stay alive just past the GetSaveFileName call below; C++ scope cleanup automatically takes care of that: this instance will be destroyed at the end.
auto extlist = Win32UIString(
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
"Portable Network Graphics files (*.png)\0*.png\0"
#endif
Expand Down Expand Up @@ -2182,21 +2295,23 @@ static void showSaveDialog(CvWindow& window)
#endif
"Radiance HDR (*.hdr;*.pic)\0*.hdr;*.pic\0"
"Sun raster files (*.sr;*.ras)\0*.sr;*.ras\0"
"All Files (*.*)\0*.*\0";
"All Files (*.*)\0*.*\0"
);
ofn.lpstrFilter = extlist();
ofn.lpstrFile = szFileName;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_NOREADONLYRETURN | OFN_NOCHANGEDIR;
#if defined(HAVE_PNG) || defined(HAVE_SPNG)
ofn.lpstrDefExt = "png";
ofn.lpstrDefExt = TEXT("png");
#else
ofn.lpstrDefExt = "bmp";
ofn.lpstrDefExt = TEXT("bmp");
#endif

if (GetSaveFileName(&ofn))
{
cv::Mat tmp;
cv::flip(cv::Mat(sz.cy, sz.cx, CV_8UC(channels), data, (sz.cx * channels + 3) & -4), tmp, 0);
cv::imwrite(szFileName, tmp);
cv::imwrite(Win32UIString(szFileName).c_str(), tmp);
}
#else
CV_UNUSED(window);
Expand Down Expand Up @@ -2474,7 +2589,7 @@ std::shared_ptr<CvTrackbar> createTrackbar_(CvWindow& window, const std::string&
window.toolbar.trackbars.push_back(trackbar);

auto slider_name = cv::format("Trackbar%p", trackbar.get());
trackbar->hwnd = CreateWindowEx(0, TRACKBAR_CLASS, slider_name.c_str(),
trackbar->hwnd = CreateWindowEx(0, TRACKBAR_CLASS, T(slider_name),
WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS |
TBS_FIXEDLENGTH | TBS_HORZ | TBS_BOTTOM,
rect.left + HG_BUDDY_WIDTH, rect.top,
Expand All @@ -2483,7 +2598,7 @@ std::shared_ptr<CvTrackbar> createTrackbar_(CvWindow& window, const std::string&
(HMENU)(size_t)bcount, hg_hinstance, 0);

slider_name = cv::format("Buddy%p", trackbar.get());
trackbar->buddy = CreateWindowEx(0, "STATIC", slider_name.c_str(),
trackbar->buddy = CreateWindowEx(0, TEXT("STATIC"), T(slider_name),
WS_CHILD | SS_RIGHT,
rect.left, rect.top,
HG_BUDDY_WIDTH, rect.bottom - rect.top,
Expand Down Expand Up @@ -2865,7 +2980,7 @@ class Win32UIWindow
auto window_ptr = window_.lock();
CV_Assert(window_ptr);
CvWindow& window = *window_ptr;
if (!SetWindowText(window.frame, title.c_str()))
if (!SetWindowText(window.frame, T(title)))
CV_Error_(Error::StsError, ("Failed to set \"%s\" window title to \"%s\"", window.name.c_str(), title.c_str()));
}

Expand Down
Loading