Difference between revisions of "CPlusPlus for Gideros Studio Help"

From GiderosMobile
 
(30 intermediate revisions by 2 users not shown)
Line 2: Line 2:
 
Here you will find various resources to help learn C++ for people who wish to help with Gideros Studio development.
 
Here you will find various resources to help learn C++ for people who wish to help with Gideros Studio development.
  
 +
== WIN32 ==
  
 
=== KNOWNFOLDERID ===
 
=== KNOWNFOLDERID ===
Refs:
+
platform-win32.cpp
 +
 
 +
'''refs''':
 
* KNOWNFOLDERID: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
 
* KNOWNFOLDERID: https://learn.microsoft.com/en-us/windows/win32/shell/knownfolderid
 
* shlobj_core.h header: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/
 
* shlobj_core.h header: https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/
Line 14: Line 17:
  
 
=== Convert wstring <-> string ===
 
=== Convert wstring <-> string ===
 +
platform-win32.cpp
 +
 +
'''refs''':
 
* SO (cc-by): https://stackoverflow.com/a/18374698/870125
 
* SO (cc-by): https://stackoverflow.com/a/18374698/870125
  
 
C:\dev\gideros_hgy29\libgid\src\win32\platform-win32.cpp
 
C:\dev\gideros_hgy29\libgid\src\win32\platform-win32.cpp
<source lang="c++">
+
<syntaxhighlight lang="c++">
 
#include <locale> // new 20221014 XXX
 
#include <locale> // new 20221014 XXX
 
#include <codecvt> // new 20221014 XXX
 
#include <codecvt> // new 20221014 XXX
Line 38: Line 44:
 
     return converterX.to_bytes(wstr);
 
     return converterX.to_bytes(wstr);
 
}
 
}
</source>
+
</syntaxhighlight>
  
  
 
=== win32 minimum, maximum screen size ===
 
=== win32 minimum, maximum screen size ===
https://www.youtube.com/watch?v=-kg4TG7GoYI
+
platform-win32.cpp
 +
 
 +
'''refs''':
 +
* https://www.youtube.com/watch?v=-kg4TG7GoYI
 
* https://gamedev.net/forums/topic/569148-c-windows-api-minimum-resize-dimensions/4638297/
 
* https://gamedev.net/forums/topic/569148-c-windows-api-minimum-resize-dimensions/4638297/
 
* SO (cc-by): https://stackoverflow.com/q/19035481/870125
 
* SO (cc-by): https://stackoverflow.com/q/19035481/870125
Line 50: Line 59:
  
 
C:\dev\gideros_hgy29\libgid\src\win32\platform-win32.cpp
 
C:\dev\gideros_hgy29\libgid\src\win32\platform-win32.cpp
<source lang="c++">
+
<syntaxhighlight lang="c++">
</source>
+
</syntaxhighlight>
  
  
Line 64: Line 73:
 
slash direction for file-paths.
 
slash direction for file-paths.
  
<source lang="c++">
+
<syntaxhighlight lang="c++">
 
  * \note If you want a trailing slash, add `SEP_STR` as the last path argument,
 
  * \note If you want a trailing slash, add `SEP_STR` as the last path argument,
 
  * duplicate slashes will be cleaned up.
 
  * duplicate slashes will be cleaned up.
Line 75: Line 84:
 
#else
 
#else
 
#  define SEP '/'
 
#  define SEP '/'
</source>
+
</syntaxhighlight>
  
<source lang="c++">
+
<syntaxhighlight lang="c++">
 
#define SEP_CHR    '#'
 
#define SEP_CHR    '#'
 
#define SEP_STR    "#"
 
#define SEP_STR    "#"
Line 92: Line 101:
  
 
   if (found_ofs + len_num + len_move > len_max) {
 
   if (found_ofs + len_num + len_move > len_max) {
</source>
+
</syntaxhighlight>
  
  
Line 103: Line 112:
 
* '''https://www.codeproject.com/Articles/16678/Vista-Goodies-in-C-Using-the-New-Vista-File-Dialog'''
 
* '''https://www.codeproject.com/Articles/16678/Vista-Goodies-in-C-Using-the-New-Vista-File-Dialog'''
 
* '''https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog'''
 
* '''https://learn.microsoft.com/en-us/windows/win32/shell/common-file-dialog'''
<source lang="c++">
+
<syntaxhighlight lang="c++">
         }else if (strcmp(what, "openFileDialog") == 0)
+
#include <wchar.h> // new 20221026 XXX
 +
...
 +
         }else if ((strcmp(what, "openDirectoryDialog") == 0)
 +
                || (strcmp(what, "openFileDialog") == 0)
 +
                || (strcmp(what, "saveFileDialog") == 0))
 
         {
 
         {
             DWORD dwFlags; // XXX
+
        /* TODO parse extension :-( */
 
+
             // for win32 args ("title|path|extensions//help") is only 1 arg
             if (args.size()>0)
+
            // unlike Qt which seems to cut the | into different args
            {
+
             if(args.size() == 1){ // do we need to deal with "help" as arg or leave it to Qt?
//                printf("args: %s, %s\n",args[0],args[1]); // TEST
+
                // we cut the args
 +
                std::wstring s=ws(args[0].s.c_str()); // "Title|c:\\tmp|Text (*.txt);; Image (*.png)"
 +
                size_t index = s.find(L"|");
 +
                size_t index2 = s.find(L"|", index+1);
 +
                std::wstring title = s.substr(0, index);
 +
                std::wstring place = s.substr(index+1, index2-index-1);
 +
                std::wstring extension = s.substr(index2+1);
 +
//                printf("t p e:\n %ls\n %ls\n %ls\n", title.c_str(),place.c_str(),extension.c_str()); // TEST OK
  
 +
                DWORD dwFlags; // new 20221028 XXX
 
                 HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
 
                 HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
 
                 if (SUCCEEDED(hr))
 
                 if (SUCCEEDED(hr))
 
                 {
 
                 {
                     IFileOpenDialog *pFile;
+
                     if (strcmp(what, "openDirectoryDialog") == 0){
                    COMDLG_FILTERSPEC fileTypes[] =
+
                        /* TO DO */
                    {
+
                        /*--------------------------------------------------*/
                        { L"Text Documents", L"*.txt" },
+
                    }else if (strcmp(what, "openFileDialog") == 0){
                        { L"All Files", L"*.*" },
+
                        IFileOpenDialog *pFile;
                    }; // NEED TO PASS FROM ARGS!
+
                        COMDLG_FILTERSPEC fileTypes[] = /* TODO parse from wstring extension :-( */
 +
                        {
 +
                            { L"Text Documents", L"*.txt" },
 +
                            { L"All Files", L"*.*" },
 +
                        };
  
                    // Create the FileOpenDialog object.
+
                        // Create the FileOpenDialog object.
                    hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,  
+
                        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,  
                            IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
+
                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
 
 
                    if (SUCCEEDED(hr))
 
                    {
 
                        // get/set options
 
                        pFile->GetOptions(&dwFlags);
 
                        pFile->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
 
                        pFile->SetFileTypes(ARRAYSIZE(fileTypes), fileTypes);
 
                        pFile->SetFileTypeIndex(1); // index starts at 1 XXX
 
//                        pFile->SetDefaultExtension(L"obj;fbx"); // XXX
 
//                        pFile->SetFolder(shellItem);
 
 
 
                        // Show the Open dialog box.
 
                        hr = pFile->Show(NULL);
 
 
 
                        // Get the file name from the dialog box.
 
 
                         if (SUCCEEDED(hr))
 
                         if (SUCCEEDED(hr))
 
                         {
 
                         {
                             IShellItem *pItem;
+
                            // get/set options
                             hr = pFile->GetResult(&pItem);
+
                            pFile->GetOptions(&dwFlags);
 +
                            pFile->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
 +
                            pFile->SetFileTypes(ARRAYSIZE(fileTypes), fileTypes); // SEE ABOVE fileTypes
 +
                            pFile->SetFileTypeIndex(1); // index starts at 1
 +
//                            pFile->SetDefaultExtension(L"obj;fbx"); // XXX
 +
                            hr = pFile->SetTitle(title.c_str()); // need more check?
 +
 
 +
                            // set starting folder
 +
                             IShellItem *pItem = NULL;
 +
                             hr = SHCreateItemFromParsingName(place.c_str(), NULL, IID_IShellItem, (LPVOID *)&pItem);
 
                             if (SUCCEEDED(hr))
 
                             if (SUCCEEDED(hr))
 
                             {
 
                             {
                                 PWSTR pszFilePath;
+
                                 pFile->SetFolder(pItem);
                                 hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
+
                                 pItem->Release();
 +
                                pItem = NULL;
 +
                            }
  
                                // Display the file name to the user.
+
                            // Show the Open dialog box.
 +
                            hr = pFile->Show(NULL);
 +
                            // Get the file name from the dialog box.
 +
                            if (SUCCEEDED(hr))
 +
                            {
 +
                                IShellItem *pItem;
 +
                                hr = pFile->GetResult(&pItem);
 
                                 if (SUCCEEDED(hr))
 
                                 if (SUCCEEDED(hr))
 
                                 {
 
                                 {
//                                  MessageBoxW(NULL, pszFilePath, L"File Path", MB_OK);
+
                                    PWSTR pszFilePath;
                                     r.type=gapplication_Variant::STRING;
+
                                    hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
                                    r.s=us(pszFilePath);
+
                                     if (SUCCEEDED(hr))
                                    rets.push_back(r);
+
                                    {
 +
                                        r.type=gapplication_Variant::STRING;
 +
                                        r.s=us(pszFilePath);
 +
                                        rets.push_back(r);
  
                                    CoTaskMemFree(pszFilePath);
+
                                        CoTaskMemFree(pszFilePath);
 +
                                    }
 +
                                    pItem->Release();
 
                                 }
 
                                 }
                                pItem->Release();
 
 
                             }
 
                             }
 +
                            pFile->Release();
 
                         }
 
                         }
                         pFile->Release();
+
                         CoUninitialize();
 +
                        /*--------------------------------------------------*/
 +
                    }else if (strcmp(what, "saveFileDialog") == 0){
 +
                        /* TO DO */
 +
                        /*--------------------------------------------------*/
 +
                    }
 +
                }
 +
            }
 +
            /*------------------------------------------------------------------*/
 +
        }else if (strcmp(what, "temporaryDirectory") == 0)
 +
</syntaxhighlight>
 +
 
 +
=== openFileDialog Latest ===
 +
platform-win32.cpp ('''updated and working piece of code''')
 +
 
 +
'''Added''':
 +
* append file extension to saved files (1st extension from filters)
 +
 
 +
<syntaxhighlight lang="c++">
 +
        }else if ((strcmp(what, "openDirectoryDialog") == 0)
 +
                || (strcmp(what, "openFileDialog") == 0)
 +
                || (strcmp(what, "saveFileDialog") == 0))
 +
            {
 +
            if(args.size() <= 2){
 +
                /* INFO SHOWN IN GIDEROS STUDIO DEBUGGER, IMPLEMENTED IN QT, NOT NEEDED HERE? */
 +
            }
 +
            else
 +
            {
 +
                std::wstring title = ws(args[0].s.c_str());
 +
                std::wstring place = ws(args[1].s.c_str());
 +
                std::vector<std::pair<std::wstring,std::wstring>> filters;
 +
                if (args.size()>=3) {
 +
                std::wstring ext = ws(args[2].s.c_str());
 +
                while (!ext.empty()) {
 +
                    std::wstring next;
 +
                size_t semicolon=ext.find(L";;");
 +
                if (semicolon!=std::wstring::npos) {
 +
                next=ext.substr(semicolon+2);
 +
                ext=ext.substr(0,semicolon);
 +
                }
 +
                size_t p1=ext.find_first_of(L'(');
 +
                size_t p2=ext.find_last_of(L')');
 +
                if ((p1!=std::wstring::npos)&&(p2!=std::wstring::npos)&&(p2>p1))
 +
                {
 +
                //Valid filter, extract label and extensions
 +
                std::wstring label=ext.substr(0,p1);
 +
                std::wstring exts=ext.substr(p1+1,p2-p1-1);
 +
                //QT uses space for extensions separator, while windows expects semicolon. Convert them.
 +
                std::replace(exts.begin(),exts.end(),L' ',L';');
 +
                filters.push_back(std::pair<std::wstring,std::wstring>(label,exts));
 +
                }
 +
                ext=next;
 +
                }
 +
                }
 +
 
 +
                DWORD dwFlags;
 +
                HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
 +
                if (SUCCEEDED(hr))
 +
                {
 +
                    COMDLG_FILTERSPEC *fileTypes=new COMDLG_FILTERSPEC[filters.size()];
 +
                    for (size_t i=0;i<filters.size();i++) {
 +
                    fileTypes[i].pszName=filters[i].first.c_str();
 +
                    fileTypes[i].pszSpec=filters[i].second.c_str();
 
                     }
 
                     }
                     CoUninitialize();
+
                     IFileDialog *pFile;
 +
if (strcmp(what, "saveFileDialog") == 0)
 +
                        hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
 +
                                IID_IFileSaveDialog, reinterpret_cast<void**>(&pFile));
 +
else
 +
                        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
 +
                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
 +
if (SUCCEEDED(hr))
 +
{
 +
bool isFolder=(strcmp(what, "openDirectoryDialog") == 0);
 +
// get/set options
 +
pFile->GetOptions(&dwFlags);
 +
pFile->SetOptions(dwFlags | FOS_FORCEFILESYSTEM | (isFolder?FOS_PICKFOLDERS:0));
 +
if (!isFolder) {
 +
pFile->SetFileTypes(filters.size(), fileTypes);
 +
pFile->SetFileTypeIndex(1); // index starts at 1
 +
//pFile->SetDefaultExtension(L"obj;fbx"); // append default extension
 +
                            //printf("* fileTypes *, set default extension to %ls\n", fileTypes[0].pszSpec); OK
 +
pFile->SetDefaultExtension(fileTypes[0].pszSpec); // append default 1st extension
 +
}
 +
hr = pFile->SetTitle(title.c_str()); // need more check?
 +
 
 +
// set starting folder
 +
IShellItem *pItem = NULL;
 +
hr = SHCreateItemFromParsingName(place.c_str(), NULL, IID_IShellItem, (LPVOID *)&pItem);
 +
if (SUCCEEDED(hr))
 +
{
 +
pFile->SetFolder(pItem);
 +
pItem->Release();
 +
pItem = NULL;
 +
}
 +
 
 +
// Show the Open dialog box.
 +
hr = pFile->Show(NULL);
 +
// Get the file name from the dialog box.
 +
if (SUCCEEDED(hr))
 +
{
 +
IShellItem *pItem;
 +
hr = pFile->GetResult(&pItem);
 +
if (SUCCEEDED(hr))
 +
{
 +
PWSTR pszFilePath;
 +
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
 +
if (SUCCEEDED(hr))
 +
{
 +
r.type=gapplication_Variant::STRING;
 +
r.s=us(pszFilePath);
 +
rets.push_back(r);
 +
 
 +
CoTaskMemFree(pszFilePath);
 +
}
 +
pItem->Release();
 +
}
 +
}
 +
pFile->Release();
 +
}
 +
                CoUninitialize();
 +
                delete[] fileTypes;
 +
                }
 +
            }
 +
            /*------------------------------------------------------------------*/
 +
        }else if (strcmp(what, "temporaryDirectory") == 0)
 +
</syntaxhighlight>
 +
 
 +
 
 +
=== Locate first occurrence of character in wide string ===
 +
platform-win32.cpp
 +
 
 +
'''refs''':
 +
* https://cplusplus.com/reference/cwchar/wcschr/
 +
* https://cplusplus.com/reference/string/string/find/
 +
<syntaxhighlight lang="c++">
 +
std::wstring gs=ws(args[0].s.c_str()); // "Title|C:/tmp/|Text (*.txt);; Image (*.png)"
 +
size_t index = gs.find(L"|");
 +
size_t index2 = gs.find(L"|", index+1);
 +
printf("args0: %ls\n", gs.c_str()); // output: Title|C:/tmp/|Text (*.txt);; Image (*.png)
 +
printf("indices: %d, %d\n", index,index2); // output 5, 13
 +
std::wstring title = gs.substr(0, index);
 +
std::wstring path = gs.substr(index+1, index2-index-1);
 +
std::wstring exts = gs.substr(index2+1);
 +
printf("t p e:\n %ls\n %ls\n %ls\n", title.c_str(),path.c_str(),exts.c_str()); // Title C:/tmp/ Text (*.txt);; Image (*.png)
 +
</syntaxhighlight>
 +
 
 +
 
 +
=== application:getKeyboardModifiers() ===
 +
'''refs''':
 +
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#key-status
 +
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input#keystroke-message-flags
 +
* SO (cc-by): https://stackoverflow.com/questions/1811206/on-win32-how-to-detect-whether-a-left-shift-or-right-alt-is-pressed-using-perl
 +
<syntaxhighlight lang="c++">
 +
if(GetAsyncKeyState(VK_LSHIFT) & 0x8000)
 +
    ; // left shift is currently down
 +
</syntaxhighlight>
 +
 
 +
*'''\libgid\src\win32\platform-win32.cpp'''
 +
<syntaxhighlight lang="c++">
 +
int getKeyboardModifiers()
 +
{
 +
int m=0;
 +
if (GetKeyState(VK_CONTROL) & 0x8000) m|=GINPUT_CTRL_MODIFIER;
 +
if (GetKeyState(VK_SHIFT) & 0x8000) m|=GINPUT_SHIFT_MODIFIER;
 +
if (GetKeyState(VK_MENU) & 0x8000) m|=GINPUT_ALT_MODIFIER;
 +
return m;
 +
}
 +
</syntaxhighlight>
 +
*'''\win32_example\win32.cpp'''
 +
<syntaxhighlight lang="c++">
 +
  else if ((iMsg==WM_KEYDOWN)||(iMsg==WM_SYSKEYDOWN)) {
 +
int m=0;
 +
if (GetKeyState(VK_CONTROL) & 0x8000) m|=GINPUT_CTRL_MODIFIER;
 +
if (GetKeyState(VK_SHIFT) & 0x8000) m|=GINPUT_SHIFT_MODIFIER;
 +
if (GetKeyState(VK_MENU) & 0x8000) m|=GINPUT_ALT_MODIFIER;
 +
if (!(lParam&0x40000000)) // don't send repeated keys XXX
 +
ginputp_keyDown(wParam,m);
 +
if ((iMsg==WM_KEYDOWN)||(wParam==VK_F10))
 +
return 0;
 +
  }
 +
  else if ((iMsg==WM_KEYUP)||(iMsg==WM_SYSKEYUP)) {
 +
int m=0;
 +
if (GetKeyState(VK_CONTROL) & 0x8000) m|=GINPUT_CTRL_MODIFIER;
 +
if (GetKeyState(VK_SHIFT) & 0x8000) m|=GINPUT_SHIFT_MODIFIER;
 +
if (GetKeyState(VK_MENU) & 0x8000) m|=GINPUT_ALT_MODIFIER;
 +
ginputp_keyUp(wParam,m);
 +
return 0;
 +
  }
 +
</syntaxhighlight>
 +
 
 +
''GetKeyState'' is for messages when you need the state at the time the message was sent.
 +
 
 +
''GetAsyncKeyState'' is for the state right now.
 +
 
 +
The high bit will be set if it's down, and it returns a short, hence the 1000000000000000, or ''0x8000'' mask
 +
 
 +
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeystate
 +
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getasynckeystate
 +
 
 +
 
 +
'''Notes''':</br>
 +
''wparam'' contains the virtual key code, ''lparam'' contains the the key state vector.
 +
 
 +
=== mkDir ===
 +
platform-win32.cpp
 +
 
 +
'''Tested and seems ok :-)'''
 +
 
 +
'''refs''':
 +
* '''https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/mkdir-wmkdir?view=msvc-170'''
 +
<syntaxhighlight lang="c++">
 +
        }else if (strcmp(what, "mkDir") == 0)
 +
        {
 +
            // new 20221114 XXX
 +
            std::wstring dir = ws(args[0].s.c_str());
 +
            if (_wmkdir(dir.c_str()) == 0)
 +
                printf("mkDir OK: %S\n", dir.c_str()); // can be useful for the w32 console
 +
            else
 +
                printf("mkDir failed or Dir may already exist: %S\n", dir.c_str()); // can be useful for the w32 console
 +
            /*------------------------------------------------------------------*/
 +
        }else if (strcmp(what, "documentDirectory") == 0)
 +
</syntaxhighlight>
 +
 
 +
 
 +
=== pathfileexists ===
 +
platform-win32.cpp
 +
 
 +
'''Tested and seems ok but not sure about what the return value should be :-/ and we can also check for access mode!'''
 +
 
 +
'''refs''':
 +
* SO (cc-by): https://stackoverflow.com/a/28011583/870125
 +
* https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/access-waccess?view=msvc-170
 +
<syntaxhighlight lang="c++">
 +
        }else if (strcmp(what, "pathfileexists") == 0) // new 20221116 XXX
 +
        {
 +
            if (args.size() > 0) {
 +
                std::wstring path = ws(args[0].s.c_str());
 +
                int retValue;
 +
                if (_waccess(path.c_str(), 0) == 0) { // modes: 0=Existence only, 2=Write-only, 4=Read-only, 6=Read and write
 +
                    // path exists
 +
                    retValue = 1;
 +
                } else {
 +
                    retValue = 0;
 
                 }
 
                 }
 +
                r.type=gapplication_Variant::DOUBLE;
 +
                r.d=retValue;
 +
                rets.push_back(r);
 +
            }
 +
            /*------------------------------------------------------------------*/
 +
        }else if ((strcmp(what, "openDirectoryDialog") == 0)
 +
</syntaxhighlight>
 +
 +
=== pathfileexists Qt ===
 +
platform-qt.cpp
 +
 +
QFile Class seems to be "kind of" the equivalent?
 +
 +
'''refs''':
 +
* https://doc.qt.io/qt-6/qfile.html
 +
 +
bool '''QFile::exists'''(const QString &fileName)
 +
Returns true if the file specified by fileName exists; otherwise returns false.
 +
Note: If fileName is a symlink that points to a non-existing file, false is returned.
 +
 +
I will need to mix this and _waccess.
 +
 +
=== WM_GETMINMAXINFO ===
 +
win32.cpp
 +
 +
'''WIP'''
 +
 +
'''refs''':
 +
* SO (cc-by): https://stackoverflow.com/questions/22261804/how-to-set-minimum-window-size-using-winapi
 +
* https://learn.microsoft.com/en-gb/windows/win32/winmsg/wm-getminmaxinfo?redirectedfrom=MSDN
 +
<syntaxhighlight lang="c++">
 +
</syntaxhighlight>
 +
 +
 +
=== LoadCursorA ===
 +
platform-win32.cpp
 +
 +
'''WIP'''
 +
 +
'''refs''':
 +
* SO (cc-by): https://stackoverflow.com/questions/19257237/reset-cursor-in-wm-setcursor-handler-properly
 +
* https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadcursora
 +
<syntaxhighlight lang="c++">
 +
    else { // SET
 +
        if (strcmp(what, "cursor") == 0)
 +
        {
 +
        /* TODO */
 +
            std::string &shape = args[0].s;
 +
//            QStringList acceptedValue;
 +
//            acceptedValue << "arrow" << "upArrow" << "cross" << "wait" << "IBeam";
 +
//            acceptedValue << "sizeVer" << "sizeHor" << "sizeBDiag" << "sizeFDiag" << "sizeAll";
 +
//            acceptedValue << "blank" << "splitV" << "splitH" << "pointingHand" << "forbidden";
 +
//            acceptedValue << "whatsThis" << "busy" << "openHand" << "closedHand" << "dragCopy";
 +
//            acceptedValue << "dragMove" << "dragLink";
 +
            HCURSOR cursor;
 +
            if (shape == "arrow") {
 +
                printf("** cursor arrow\n");
 +
                cursor = LoadCursorA(NULL, (LPCSTR)IDC_ARROW);
 +
                SetCursor(cursor);
 +
            } else if (shape == "upArrow") {
 +
                printf("** cursor uparrow\n"); // LUA DEBUG OK BUT ARROW SHAPE DOESN'T CHANGE :-(
 +
                cursor = LoadCursorA(NULL, (LPCSTR)IDC_UPARROW);
 +
                SetCursor(cursor);
 
             }
 
             }
 
             /*------------------------------------------------------------------*/
 
             /*------------------------------------------------------------------*/
         }else if (strcmp(what, "saveFileDialog") == 0)
+
         }else if (strcmp(what, "windowPosition") == 0)
</source>
+
</syntaxhighlight>
 +
 
 +
=== modal windows dialog ===
 +
'''Test01'''
 +
 
 +
'''refs''':
 +
*'''https://learn.microsoft.com/en-us/windows/win32/dlgbox/using-dialog-boxes#creating-a-modeless-dialog-box'''
 +
*'''https://learn.microsoft.com/en-us/windows/win32/api/olectl/nf-olectl-olecreatepropertyframe#remarks'''
 +
 
 +
C:\dev\gideros_dev\libgid\src\win32\platform-win32.cpp
 +
<syntaxhighlight lang="c++">
 +
                    COMDLG_FILTERSPEC *fileTypes=new COMDLG_FILTERSPEC[filters.size()];
 +
                    for (size_t i=0;i<filters.size();i++) {
 +
                    fileTypes[i].pszName=filters[i].first.c_str();
 +
                    fileTypes[i].pszSpec=filters[i].second.c_str();
 +
                    }
 +
                    IFileDialog *pFile;
 +
if (strcmp(what, "saveFileDialog") == 0)
 +
//                        hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
 +
//                                IID_IFileSaveDialog, reinterpret_cast<void**>(&pFile));
 +
                        hr = CoCreateInstance(CLSID_FileSaveDialog, hwndcopy, CLSCTX_ALL,
 +
                                IID_IFileSaveDialog, reinterpret_cast<void**>(&pFile));
 +
else
 +
//                        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
 +
//                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
 +
                        hr = CoCreateInstance(CLSID_FileOpenDialog, hwndcopy, CLSCTX_ALL,
 +
                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
 +
if (SUCCEEDED(hr))
 +
{
 +
</syntaxhighlight>
 +
 
 +
== CBUMP ==
 +
 
 +
=== the TODOs ===
 +
bump.cpp
 +
 
 +
<syntaxhighlight lang="c++">
 +
static bool rect_getSegmentIntersectionIndices(double x, double y, double w,
 +
double h, double x1, double y1, double x2, double y2, double &ti1,
 +
double &ti2, double &nx1, double &ny1, double &nx2, double &ny2) {
 +
//ti1, ti2 = ti1 or 0, ti2 or 1 TODO
 +
double dx = x2 - x1;
 +
double dy = y2 - y1;
 +
...
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="c++">
 +
static bool rect_detectCollision(double x1, double y1, double w1, double h1,
 +
double x2, double y2, double w2, double h2, double goalX, double goalY,
 +
Collision &col) {
 +
//goalX = goalX or x1 TODO
 +
//goalY = goalY or y1
 +
 
 +
double dx = goalX - x1, dy = goalY - y1;
 +
...
 +
</syntaxhighlight>
 +
It is time to do the TODOs ;-)
 +
 
 +
== ANDROID ==
 +
=== GMEDIA ===
 +
documenting my research here :)
 +
* C:\dev\gideros_dev20240621\plugins\gmedia\source\media.cpp
 +
* https://github.com/gideros/gideros/blob/f0d48afb92942cdd0232d4d43572b506f644d49f/plugins/gmedia/source/media.cpp#L231
 +
* https://doc.qt.io/qt-6/qml-qtmultimedia-imagecapture.html#captureToFile-method
 +
* https://doc.qt.io/qt-6/cameraoverview.html
 +
* https://developer.android.com/codelabs/camerax-getting-started#0
 +
 
 +
'''In android studio''':
 +
<blockquote>
 +
WRITE_EXTERNAL_STORAGE is deprecated (and is not granted) when targeting Android 13+. If you need to write to shared storage, use the MediaStore.createWriteRequest intent.
 +
 
 +
Inspection info:Scoped storage is enforced on Android 10+ (or Android 11+ if using requestLegacyExternalStorage). In particular, WRITE_EXTERNAL_STORAGE will no longer provide write access to all files; it will provide the equivalent of READ_EXTERNAL_STORAGE instead.  As of Android 13, if you need to query or interact with MediaStore or media files on the shared storage, you should be using instead one or more new storage permissions:
 +
* android.permission.READ_MEDIA_IMAGES
 +
* android.permission.READ_MEDIA_VIDEO
 +
* android.permission.READ_MEDIA_AUDIO
 +
and then add maxSdkVersion="33" to the older permission. See the developer guide for how to do this: https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions
 +
 
 +
The MANAGE_EXTERNAL_STORAGE permission can be used to manage all files, but it is rarely necessary and most apps on Google Play are not allowed to use it. Most apps should instead migrate to use scoped storage. To modify or delete files, apps should request write access from the user as described at https://goo.gle/android-mediastore-createwriterequest.
 +
 
 +
To learn more, read these resources: Play policy: https://goo.gle/policy-storage-help Allowable use cases: https://goo.gle/policy-storage-usecases
 +
 
 +
Issue id: ScopedStorage  More info: https://goo.gle/android-storage-usecases  Vendor: Android Open Source Project Contact: https://groups.google.com/g/lint-dev Feedback: https://issuetracker.google.com/issues/new?component=192708
 +
 
 +
----
 +
 
 +
WRITE_EXTERNAL_STORAGE
 +
Added in API level 4
 +
 
 +
public static final String  WRITE_EXTERNAL_STORAGE
 +
 +
Allows an application to write to external storage.
 +
Note: If your app targets Build.VERSION_CODES.R  or higher, this permission has no effect.
 +
If your app is on a device that runs API level 19 or higher, you don't need to declare this permission to read and write files in your application-specific directories returned by Context.getExternalFilesDir(String)  and Context.getExternalCacheDir().
 +
Learn more about how to modify media files  that your app doesn't own, and how to modify non-media files  that your app doesn't own.
 +
If your app is a file manager and needs broad access to external storage files, then the system must place your app on an allowlist so that you can successfully request the MANAGE_EXTERNAL_STORAGE permission. Learn more about the appropriate use cases for
 +
developer.android.com/reference/android//guide/topics/manifest/uses-sdk-element.html#min">minSdkVersion and targetSdkVersion values are set to 3 or lower, the system implicitly grants your app this permission. If you don't need this permission, be sure your targetSdkVersion is 4 or higher.
 +
Protection level: dangerous
 +
Constant Value: "android.permission.WRITE_EXTERNAL_STORAGE"
 +
 
 +
  < Android API 35, extension level 13 Platform > (android.jar)
 +
`WRITE_EXTERNAL_STORAGE` on developer.android.com
 +
</blockquote>
 +
 
 +
 
 +
 
 +
=== CANNOT ACCESS android/data/packagename/files/ on Android 29+ ===
 +
=== CANNOT ACCESS EXTERNAL FILES (need new granular permissions) on Android 29+ ===
 +
There is this new Launch an activity for result '''ActivityResultLauncher''':
 +
*https://developer.android.com/training/basics/intents/result#launch
 +
which replaces old onActivityResult:
 +
* https://github.com/gideros/gideros/blob/f0d48afb92942cdd0232d4d43572b506f644d49f/android/GiderosAndroidPlayer/app/src/main/java/com/giderosmobile/android/player/GiderosApplication.java#L722
 +
 
 +
Quite a few changes need to be done:
 +
* Build.gradle (project):
 +
<syntaxhighlight lang="c++">
 +
// Top-level build file where you can add configuration options common to all sub-projects/modules.
 +
buildscript {
 +
    repositories {
 +
        mavenCentral()
 +
        google()
 +
        //TAG-TOP-GRADLE-BUILDREPOS//
 +
    }
 +
    dependencies {
 +
        classpath 'com.android.tools.build:gradle:8.7.3'
 +
        //TAG-CLASSPATH-DEPENDENCIES//
 +
    }
 +
}
 +
 
 +
allprojects {
 +
    repositories {
 +
        mavenCentral()
 +
        //TAG-TOP-GRADLE-ALLREPOS//
 +
    }
 +
}
 +
 
 +
//TAG-TOP-GRADLE-MAIN//
 +
</syntaxhighlight>
 +
 
 +
* build.gradle (app):
 +
<syntaxhighlight lang="c++">
 +
dependencies {
 +
    implementation files('libs/gideros.aar')
 +
    //TAG-DEPENDENCIES//
 +
    implementation 'com.android.support:multidex:1.0.3'
 +
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
 +
    implementation 'androidx.activity:activity:1.9.3' // XXX To simplify photo picker integration, include version 1.7.0 or higher of the androidx.activity library
 +
//    implementation 'androidx.fragment:fragment:1.8.5' // XXX
 +
 
 +
    configurations.implementation { // XXX
 +
        exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
 +
    }
 +
}
 +
</syntaxhighlight>
 +
 
 +
* gradle.manifest
 +
<syntaxhighlight lang="c++">
 +
android.useAndroidX=true
 +
android.enableJetifier=false
 +
android.buildFeatures.buildConfig = true
 +
android.nonTransitiveRClass=false
 +
android.nonFinalResIds=false
 +
</syntaxhighlight>
 +
 
 +
* gradle-wrapper.properties:
 +
<syntaxhighlight lang="c++">
 +
#Wed Apr 10 15:27:10 PDT 2013
 +
distributionBase=GRADLE_USER_HOME
 +
distributionPath=wrapper/dists
 +
zipStoreBase=GRADLE_USER_HOME
 +
zipStorePath=wrapper/dists
 +
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
 +
</syntaxhighlight>
 +
 
 +
* Android Manifest:
 +
<syntaxhighlight lang="c++">
 +
<!--    <uses-permission android:name="android.permission.VIBRATE" />-->
 +
    <!-- TAG:MANIFEST-EXTRA -->
 +
 
 +
    <queries> <!-- XXX -->
 +
        <!-- TAG:QUERIES -->
 +
    </queries> <!-- XXX -->
 +
 
 +
    <!-- Devices running Android 12L (API level 32) or lower  -->
 +
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
 +
    <!-- Devices running Android 13 (API level 33) or higher -->
 +
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
 +
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
 +
    <!-- To handle the reselection within the app on devices running Android 14
 +
        or higher if your app targets Android 14 (API level 34) or higher.  -->
 +
    <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
 +
 
 +
    <!-- write  -->
 +
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 +
 
 +
    <!-- camera  -->
 +
    <uses-permission android:name="android.permission.CAMERA" />
 +
</syntaxhighlight>
 +
 
 +
* in GMedia.java
 +
<syntaxhighlight lang="c++">
 +
    // ******************************************
 +
    // Register ActivityResult handler
 +
    // GetContent creates an ActivityResultLauncher<String> to let you pass
 +
    // in the mime type you want to let the user select
 +
//    static ActivityResultLauncher<String> mGetContent = registerForActivityResult(
 +
//            new ActivityResultContracts.GetContent(),
 +
//            uri -> {
 +
//                // Handle the returned Uri
 +
//            });
 +
//
 +
//    private static ActivityResultLauncher<String> registerForActivityResult(
 +
//            ActivityResultContracts.GetContent getContent, ActivityResultCallback<Uri> activityResultCallback) {
 +
//        Log.d("XXX", "XXX ActivityResultLauncher");
 +
//        return null;
 +
//    }
 +
 
 +
    // Register ActivityResult handler
 +
    // GetContent creates an ActivityResultLauncher<String> to let you pass
 +
    // in the mime type you want to let the user select
 +
    static ActivityResultLauncher<Intent> mGetContent = registerForActivityResult(
 +
            new ActivityResultContracts.StartActivityForResult(),
 +
            (result) -> {
 +
                Log.d("XXX", "XXX ActivityResultContracts: " + result.getPath());
 +
                // code to process data from activity called
 +
            });
 +
 
 +
    private static ActivityResultLauncher<Intent> registerForActivityResult(
 +
            ActivityResultContracts.StartActivityForResult getContent, ActivityResultCallback<Uri> activityResultCallback) {
 +
        Log.d("XXX", "XXX ActivityResultLauncher");
 +
        return null;
 +
    }
 +
    // ******************************************
 +
</syntaxhighlight>
 +
 
 +
Then we call '''ActivityResultLauncher'''
 +
<syntaxhighlight lang="c++">
 +
    public static void getPictureFunction() {
 +
        Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
 +
//        sActivity.get().startActivityForResult(i, REQUEST_SELECT_PICTURE);
 +
//        // Pass in the mime type you want to let the user select
 +
//        // as the input
 +
//        mGetContent.launch("image/*");
 +
        GMedia.mGetContent.launch(i); // Intent
 +
    }
 +
</syntaxhighlight>
 +
 
 +
This needs a lot of changes I am not able to do in Gideros (adding the new '''ActivityResultLauncher''' in GiderosAndroidPlayer/.../'''GiderosApplication.java''') and some other stuff :-(
 +
 
 +
'''I added notes in the wiki where the plugin doesn't work anymore on new android!'''
 +
 
 +
 
  
  

Latest revision as of 03:51, 21 December 2024

Here you will find various resources to help learn C++ for people who wish to help with Gideros Studio development.

WIN32

KNOWNFOLDERID

platform-win32.cpp

refs:

C:\dev\gideros_hgy29\libgid\src\win32\platform-win32.cpp


Convert wstring <-> string

platform-win32.cpp

refs:

C:\dev\gideros_hgy29\libgid\src\win32\platform-win32.cpp

#include <locale> // new 20221014 XXX
#include <codecvt> // new 20221014 XXX

...

std::wstring s2ws(const std::string& str)
{
    using convert_typeX = std::codecvt_utf8<wchar_t>;
    std::wstring_convert<convert_typeX, wchar_t> converterX;

    return converterX.from_bytes(str);
}

std::string ws2s(const std::wstring& wstr)
{
    using convert_typeX = std::codecvt_utf8<wchar_t>;
    std::wstring_convert<convert_typeX, wchar_t> converterX;

    return converterX.to_bytes(wstr);
}


win32 minimum, maximum screen size

platform-win32.cpp

refs:

C:\dev\gideros_hgy29\libgid\src\win32\platform-win32.cpp


win32 LFS problem with separator

Recent changes to path handling (most likely [0]) caused AssetCatalogTest.create_catalog_after_loading_file to fail on WIN32.

The test relied on the resulting path to be joined with "/" as a path separator. The resulting path used both forward and back-slashes. While these do work for some API's on WIN32, mixing both in a file path isn't expected behavior in most cases, so update the tests to use native slash direction for file-paths.

 * \note If you want a trailing slash, add `SEP_STR` as the last path argument,
 * duplicate slashes will be cleaned up.
 */
size_t BLI_path_join(char *__restrict dst, size_t dst_len, const char *path, ...)
#  define SEP '\\'
#  define ALTSEP '/'
#  define SEP_STR "\\"
#  define ALTSEP_STR "/"
#else
#  define SEP '/'
#define SEP_CHR     '#'
#define SEP_STR     "#"

#define EPS 0.001

#define UN_SC_KM    1000.0f
#define UN_SC_HM    100.0f
      str_tmp, TEMP_STR_SIZE, "*%.9g" SEP_STR, unit->scalar / scale_pref);

  if (len_num > len_max) {
    len_num = len_max;
  }

  if (found_ofs + len_num + len_move > len_max) {


openFileDialog

platform-win32.cpp

refs:

#include <wchar.h> // new 20221026 XXX
...
        }else if ((strcmp(what, "openDirectoryDialog") == 0)
                || (strcmp(what, "openFileDialog") == 0)
                || (strcmp(what, "saveFileDialog") == 0))
        {
        	/* TODO parse extension :-( */
            // for win32 args ("title|path|extensions//help") is only 1 arg
            // unlike Qt which seems to cut the | into different args
            if(args.size() == 1){ // do we need to deal with "help" as arg or leave it to Qt?
                // we cut the args
                std::wstring s=ws(args[0].s.c_str()); // "Title|c:\\tmp|Text (*.txt);; Image (*.png)"
                size_t index = s.find(L"|");
                size_t index2 = s.find(L"|", index+1);
                std::wstring title = s.substr(0, index);
                std::wstring place = s.substr(index+1, index2-index-1);
                std::wstring extension = s.substr(index2+1);
//                printf("t p e:\n %ls\n %ls\n %ls\n", title.c_str(),place.c_str(),extension.c_str()); // TEST OK

                DWORD dwFlags; // new 20221028 XXX
                HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
                if (SUCCEEDED(hr))
                {
                    if (strcmp(what, "openDirectoryDialog") == 0){
                        /* TO DO */
                        /*--------------------------------------------------*/
                    }else if (strcmp(what, "openFileDialog") == 0){
                        IFileOpenDialog *pFile;
                        COMDLG_FILTERSPEC fileTypes[] = /* TODO parse from wstring extension :-( */
                        {
                            { L"Text Documents", L"*.txt" },
                            { L"All Files", L"*.*" },
                        };

                        // Create the FileOpenDialog object.
                        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, 
                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
                        if (SUCCEEDED(hr))
                        {
                            // get/set options
                            pFile->GetOptions(&dwFlags);
                            pFile->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
                            pFile->SetFileTypes(ARRAYSIZE(fileTypes), fileTypes); // SEE ABOVE fileTypes
                            pFile->SetFileTypeIndex(1); // index starts at 1
//                            pFile->SetDefaultExtension(L"obj;fbx"); // XXX
                            hr = pFile->SetTitle(title.c_str()); // need more check?

                            // set starting folder
                            IShellItem *pItem = NULL;
                            hr = SHCreateItemFromParsingName(place.c_str(), NULL, IID_IShellItem, (LPVOID *)&pItem);
                            if (SUCCEEDED(hr))
                            {
                                pFile->SetFolder(pItem);
                                pItem->Release();
                                pItem = NULL;
                            }

                            // Show the Open dialog box.
                            hr = pFile->Show(NULL);
                            // Get the file name from the dialog box.
                            if (SUCCEEDED(hr))
                            {
                                IShellItem *pItem;
                                hr = pFile->GetResult(&pItem);
                                if (SUCCEEDED(hr))
                                {
                                    PWSTR pszFilePath;
                                    hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
                                    if (SUCCEEDED(hr))
                                    {
                                        r.type=gapplication_Variant::STRING;
                                        r.s=us(pszFilePath);
                                        rets.push_back(r);

                                        CoTaskMemFree(pszFilePath);
                                    }
                                    pItem->Release();
                                }
                            }
                            pFile->Release();
                        }
                        CoUninitialize();
                        /*--------------------------------------------------*/
                    }else if (strcmp(what, "saveFileDialog") == 0){
                        /* TO DO */
                        /*--------------------------------------------------*/
                    }
                }
            }
            /*------------------------------------------------------------------*/
        }else if (strcmp(what, "temporaryDirectory") == 0)

openFileDialog Latest

platform-win32.cpp (updated and working piece of code)

Added:

  • append file extension to saved files (1st extension from filters)
        }else if ((strcmp(what, "openDirectoryDialog") == 0)
                || (strcmp(what, "openFileDialog") == 0)
                || (strcmp(what, "saveFileDialog") == 0))
            {
            if(args.size() <= 2){
                /* INFO SHOWN IN GIDEROS STUDIO DEBUGGER, IMPLEMENTED IN QT, NOT NEEDED HERE? */
            }
            else
            {
                std::wstring title = ws(args[0].s.c_str());
                std::wstring place = ws(args[1].s.c_str());
                std::vector<std::pair<std::wstring,std::wstring>> filters;
                if (args.size()>=3) {
                	std::wstring ext = ws(args[2].s.c_str());
                	while (!ext.empty()) {
                    	std::wstring next;
                		size_t semicolon=ext.find(L";;");
                		if (semicolon!=std::wstring::npos) {
                			next=ext.substr(semicolon+2);
                			ext=ext.substr(0,semicolon);
                		}
                		size_t p1=ext.find_first_of(L'(');
                		size_t p2=ext.find_last_of(L')');
                		if ((p1!=std::wstring::npos)&&(p2!=std::wstring::npos)&&(p2>p1))
                		{
                			//Valid filter, extract label and extensions
                			std::wstring label=ext.substr(0,p1);
                			std::wstring exts=ext.substr(p1+1,p2-p1-1);
                			//QT uses space for extensions separator, while windows expects semicolon. Convert them.
                			std::replace(exts.begin(),exts.end(),L' ',L';');
                			filters.push_back(std::pair<std::wstring,std::wstring>(label,exts));
                		}
                		ext=next;
                	}
                }

                DWORD dwFlags;
                HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
                if (SUCCEEDED(hr))
                {
                    COMDLG_FILTERSPEC *fileTypes=new COMDLG_FILTERSPEC[filters.size()];
                    for (size_t i=0;i<filters.size();i++) {
                    	fileTypes[i].pszName=filters[i].first.c_str();
                    	fileTypes[i].pszSpec=filters[i].second.c_str();
                    }
                    IFileDialog *pFile;
					if (strcmp(what, "saveFileDialog") == 0)
                        hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
                                IID_IFileSaveDialog, reinterpret_cast<void**>(&pFile));
					else
                        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
					if (SUCCEEDED(hr))
					{
						bool isFolder=(strcmp(what, "openDirectoryDialog") == 0);
						// get/set options
						pFile->GetOptions(&dwFlags);
						pFile->SetOptions(dwFlags | FOS_FORCEFILESYSTEM | (isFolder?FOS_PICKFOLDERS:0));
						if (!isFolder) {
							pFile->SetFileTypes(filters.size(), fileTypes);
							pFile->SetFileTypeIndex(1); // index starts at 1
							//pFile->SetDefaultExtension(L"obj;fbx"); // append default extension
                            //printf("* fileTypes *, set default extension to %ls\n", fileTypes[0].pszSpec); OK
							pFile->SetDefaultExtension(fileTypes[0].pszSpec); // append default 1st extension
						}
						hr = pFile->SetTitle(title.c_str()); // need more check?

						// set starting folder
						IShellItem *pItem = NULL;
						hr = SHCreateItemFromParsingName(place.c_str(), NULL, IID_IShellItem, (LPVOID *)&pItem);
						if (SUCCEEDED(hr))
						{
							pFile->SetFolder(pItem);
							pItem->Release();
							pItem = NULL;
						}

						// Show the Open dialog box.
						hr = pFile->Show(NULL);
						// Get the file name from the dialog box.
						if (SUCCEEDED(hr))
						{
							IShellItem *pItem;
							hr = pFile->GetResult(&pItem);
							if (SUCCEEDED(hr))
							{
								PWSTR pszFilePath;
								hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
								if (SUCCEEDED(hr))
								{
									r.type=gapplication_Variant::STRING;
									r.s=us(pszFilePath);
									rets.push_back(r);

									CoTaskMemFree(pszFilePath);
								}
								pItem->Release();
							}
						}
						pFile->Release();
					}
	                CoUninitialize();
	                delete[] fileTypes;
                }
            }
            /*------------------------------------------------------------------*/
        }else if (strcmp(what, "temporaryDirectory") == 0)


Locate first occurrence of character in wide string

platform-win32.cpp

refs:

std::wstring gs=ws(args[0].s.c_str()); // "Title|C:/tmp/|Text (*.txt);; Image (*.png)"
size_t index = gs.find(L"|");
size_t index2 = gs.find(L"|", index+1);
printf("args0: %ls\n", gs.c_str()); // output: Title|C:/tmp/|Text (*.txt);; Image (*.png)
printf("indices: %d, %d\n", index,index2); // output 5, 13
std::wstring title = gs.substr(0, index);
std::wstring path = gs.substr(index+1, index2-index-1);
std::wstring exts = gs.substr(index2+1);
printf("t p e:\n %ls\n %ls\n %ls\n", title.c_str(),path.c_str(),exts.c_str()); // Title C:/tmp/ Text (*.txt);; Image (*.png)


application:getKeyboardModifiers()

refs:

if(GetAsyncKeyState(VK_LSHIFT) & 0x8000)
    ; // left shift is currently down
  • \libgid\src\win32\platform-win32.cpp
int getKeyboardModifiers()
{
	int m=0;
	if (GetKeyState(VK_CONTROL) & 0x8000) m|=GINPUT_CTRL_MODIFIER;
	if (GetKeyState(VK_SHIFT) & 0x8000) m|=GINPUT_SHIFT_MODIFIER;
	if (GetKeyState(VK_MENU) & 0x8000) m|=GINPUT_ALT_MODIFIER;
	return m;
}
  • \win32_example\win32.cpp
  else if ((iMsg==WM_KEYDOWN)||(iMsg==WM_SYSKEYDOWN)) {
		int m=0;
		if (GetKeyState(VK_CONTROL) & 0x8000) m|=GINPUT_CTRL_MODIFIER;
		if (GetKeyState(VK_SHIFT) & 0x8000) m|=GINPUT_SHIFT_MODIFIER;
		if (GetKeyState(VK_MENU) & 0x8000) m|=GINPUT_ALT_MODIFIER;
		if (!(lParam&0x40000000)) // don't send repeated keys XXX
			ginputp_keyDown(wParam,m);
		if ((iMsg==WM_KEYDOWN)||(wParam==VK_F10))
			return 0;
  }
  else if ((iMsg==WM_KEYUP)||(iMsg==WM_SYSKEYUP)) {
		int m=0;
		if (GetKeyState(VK_CONTROL) & 0x8000) m|=GINPUT_CTRL_MODIFIER;
		if (GetKeyState(VK_SHIFT) & 0x8000) m|=GINPUT_SHIFT_MODIFIER;
		if (GetKeyState(VK_MENU) & 0x8000) m|=GINPUT_ALT_MODIFIER;
		ginputp_keyUp(wParam,m);
		return 0;
  }

GetKeyState is for messages when you need the state at the time the message was sent.

GetAsyncKeyState is for the state right now.

The high bit will be set if it's down, and it returns a short, hence the 1000000000000000, or 0x8000 mask


Notes:
wparam contains the virtual key code, lparam contains the the key state vector.

mkDir

platform-win32.cpp

Tested and seems ok :-)

refs:

        }else if (strcmp(what, "mkDir") == 0)
        {
            // new 20221114 XXX
            std::wstring dir = ws(args[0].s.c_str());
            if (_wmkdir(dir.c_str()) == 0)
                printf("mkDir OK: %S\n", dir.c_str()); // can be useful for the w32 console
            else
                printf("mkDir failed or Dir may already exist: %S\n", dir.c_str()); // can be useful for the w32 console
            /*------------------------------------------------------------------*/
        }else if (strcmp(what, "documentDirectory") == 0)


pathfileexists

platform-win32.cpp

Tested and seems ok but not sure about what the return value should be :-/ and we can also check for access mode!

refs:

        }else if (strcmp(what, "pathfileexists") == 0) // new 20221116 XXX
        {
            if (args.size() > 0) {
                std::wstring path = ws(args[0].s.c_str());
                int retValue;
                if (_waccess(path.c_str(), 0) == 0) { // modes: 0=Existence only, 2=Write-only, 4=Read-only, 6=Read and write
                    // path exists
                    retValue = 1;
                } else {
                    retValue = 0;
                }
                r.type=gapplication_Variant::DOUBLE;
                r.d=retValue;
                rets.push_back(r);
            }
            /*------------------------------------------------------------------*/
        }else if ((strcmp(what, "openDirectoryDialog") == 0)

pathfileexists Qt

platform-qt.cpp

QFile Class seems to be "kind of" the equivalent?

refs:

bool QFile::exists(const QString &fileName) Returns true if the file specified by fileName exists; otherwise returns false. Note: If fileName is a symlink that points to a non-existing file, false is returned.

I will need to mix this and _waccess.

WM_GETMINMAXINFO

win32.cpp

WIP

refs:


LoadCursorA

platform-win32.cpp

WIP

refs:

    else { // SET
        if (strcmp(what, "cursor") == 0)
        {
        	/* TODO */
            std::string &shape = args[0].s;
//            QStringList acceptedValue;
//            acceptedValue << "arrow" << "upArrow" << "cross" << "wait" << "IBeam";
//            acceptedValue << "sizeVer" << "sizeHor" << "sizeBDiag" << "sizeFDiag" << "sizeAll";
//            acceptedValue << "blank" << "splitV" << "splitH" << "pointingHand" << "forbidden";
//            acceptedValue << "whatsThis" << "busy" << "openHand" << "closedHand" << "dragCopy";
//            acceptedValue << "dragMove" << "dragLink";
            HCURSOR cursor;
            if (shape == "arrow") {
                printf("** cursor arrow\n");
                cursor = LoadCursorA(NULL, (LPCSTR)IDC_ARROW);
                SetCursor(cursor);
            } else if (shape == "upArrow") {
                printf("** cursor uparrow\n"); // LUA DEBUG OK BUT ARROW SHAPE DOESN'T CHANGE :-(
                cursor = LoadCursorA(NULL, (LPCSTR)IDC_UPARROW);
                SetCursor(cursor);
            }
            /*------------------------------------------------------------------*/
        }else if (strcmp(what, "windowPosition") == 0)

modal windows dialog

Test01

refs:

C:\dev\gideros_dev\libgid\src\win32\platform-win32.cpp

                    COMDLG_FILTERSPEC *fileTypes=new COMDLG_FILTERSPEC[filters.size()];
                    for (size_t i=0;i<filters.size();i++) {
                    	fileTypes[i].pszName=filters[i].first.c_str();
                    	fileTypes[i].pszSpec=filters[i].second.c_str();
                    }
                    IFileDialog *pFile;
					if (strcmp(what, "saveFileDialog") == 0)
//                        hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_ALL,
//                                IID_IFileSaveDialog, reinterpret_cast<void**>(&pFile));
                        hr = CoCreateInstance(CLSID_FileSaveDialog, hwndcopy, CLSCTX_ALL,
                                IID_IFileSaveDialog, reinterpret_cast<void**>(&pFile));
					else
//                        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
//                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
                        hr = CoCreateInstance(CLSID_FileOpenDialog, hwndcopy, CLSCTX_ALL,
                                IID_IFileOpenDialog, reinterpret_cast<void**>(&pFile));
					if (SUCCEEDED(hr))
					{

CBUMP

the TODOs

bump.cpp

static bool rect_getSegmentIntersectionIndices(double x, double y, double w,
		double h, double x1, double y1, double x2, double y2, double &ti1,
		double &ti2, double &nx1, double &ny1, double &nx2, double &ny2) {
	//ti1, ti2 = ti1 or 0, ti2 or 1 TODO
	double dx = x2 - x1;
	double dy = y2 - y1;
...
static bool rect_detectCollision(double x1, double y1, double w1, double h1,
		double x2, double y2, double w2, double h2, double goalX, double goalY,
		Collision &col) {
	//goalX = goalX or x1 TODO
	//goalY = goalY or y1

	double dx = goalX - x1, dy = goalY - y1;
...

It is time to do the TODOs ;-)

ANDROID

GMEDIA

documenting my research here :)

In android studio:

WRITE_EXTERNAL_STORAGE is deprecated (and is not granted) when targeting Android 13+. If you need to write to shared storage, use the MediaStore.createWriteRequest intent.

Inspection info:Scoped storage is enforced on Android 10+ (or Android 11+ if using requestLegacyExternalStorage). In particular, WRITE_EXTERNAL_STORAGE will no longer provide write access to all files; it will provide the equivalent of READ_EXTERNAL_STORAGE instead. As of Android 13, if you need to query or interact with MediaStore or media files on the shared storage, you should be using instead one or more new storage permissions:

  • android.permission.READ_MEDIA_IMAGES
  • android.permission.READ_MEDIA_VIDEO
  • android.permission.READ_MEDIA_AUDIO

and then add maxSdkVersion="33" to the older permission. See the developer guide for how to do this: https://developer.android.com/about/versions/13/behavior-changes-13#granular-media-permissions

The MANAGE_EXTERNAL_STORAGE permission can be used to manage all files, but it is rarely necessary and most apps on Google Play are not allowed to use it. Most apps should instead migrate to use scoped storage. To modify or delete files, apps should request write access from the user as described at https://goo.gle/android-mediastore-createwriterequest.

To learn more, read these resources: Play policy: https://goo.gle/policy-storage-help Allowable use cases: https://goo.gle/policy-storage-usecases

Issue id: ScopedStorage More info: https://goo.gle/android-storage-usecases Vendor: Android Open Source Project Contact: https://groups.google.com/g/lint-dev Feedback: https://issuetracker.google.com/issues/new?component=192708


WRITE_EXTERNAL_STORAGE Added in API level 4

public static final String WRITE_EXTERNAL_STORAGE

Allows an application to write to external storage. Note: If your app targets Build.VERSION_CODES.R or higher, this permission has no effect. If your app is on a device that runs API level 19 or higher, you don't need to declare this permission to read and write files in your application-specific directories returned by Context.getExternalFilesDir(String) and Context.getExternalCacheDir(). Learn more about how to modify media files that your app doesn't own, and how to modify non-media files that your app doesn't own. If your app is a file manager and needs broad access to external storage files, then the system must place your app on an allowlist so that you can successfully request the MANAGE_EXTERNAL_STORAGE permission. Learn more about the appropriate use cases for developer.android.com/reference/android//guide/topics/manifest/uses-sdk-element.html#min">minSdkVersion and targetSdkVersion values are set to 3 or lower, the system implicitly grants your app this permission. If you don't need this permission, be sure your targetSdkVersion is 4 or higher. Protection level: dangerous Constant Value: "android.permission.WRITE_EXTERNAL_STORAGE"

< Android API 35, extension level 13 Platform > (android.jar) `WRITE_EXTERNAL_STORAGE` on developer.android.com


CANNOT ACCESS android/data/packagename/files/ on Android 29+

CANNOT ACCESS EXTERNAL FILES (need new granular permissions) on Android 29+

There is this new Launch an activity for result ActivityResultLauncher:

which replaces old onActivityResult:

Quite a few changes need to be done:

  • Build.gradle (project):
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        mavenCentral()
        google()
        //TAG-TOP-GRADLE-BUILDREPOS//
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.7.3'
        //TAG-CLASSPATH-DEPENDENCIES//
    }
}

allprojects {
    repositories {
        mavenCentral()
        //TAG-TOP-GRADLE-ALLREPOS//
    }
}

//TAG-TOP-GRADLE-MAIN//
  • build.gradle (app):
dependencies {
    implementation files('libs/gideros.aar')
    //TAG-DEPENDENCIES//
    implementation 'com.android.support:multidex:1.0.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.activity:activity:1.9.3' // XXX To simplify photo picker integration, include version 1.7.0 or higher of the androidx.activity library
//    implementation 'androidx.fragment:fragment:1.8.5' // XXX

    configurations.implementation { // XXX
        exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
    }
}
  • gradle.manifest
android.useAndroidX=true
android.enableJetifier=false
android.buildFeatures.buildConfig = true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
  • gradle-wrapper.properties:
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
  • Android Manifest:
<!--    <uses-permission android:name="android.permission.VIBRATE" />-->
    <!-- TAG:MANIFEST-EXTRA -->

    <queries> <!-- XXX -->
        <!-- TAG:QUERIES -->
    </queries> <!-- XXX -->

    <!-- Devices running Android 12L (API level 32) or lower  -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
    <!-- Devices running Android 13 (API level 33) or higher -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
    <!-- To handle the reselection within the app on devices running Android 14
         or higher if your app targets Android 14 (API level 34) or higher.  -->
    <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

    <!-- write  -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <!-- camera  -->
    <uses-permission android:name="android.permission.CAMERA" />
  • in GMedia.java
    // ******************************************
    // Register ActivityResult handler
    // GetContent creates an ActivityResultLauncher<String> to let you pass
    // in the mime type you want to let the user select
//    static ActivityResultLauncher<String> mGetContent = registerForActivityResult(
//            new ActivityResultContracts.GetContent(),
//            uri -> {
//                // Handle the returned Uri
//            });
//
//    private static ActivityResultLauncher<String> registerForActivityResult(
//            ActivityResultContracts.GetContent getContent, ActivityResultCallback<Uri> activityResultCallback) {
//        Log.d("XXX", "XXX ActivityResultLauncher");
//        return null;
//    }

    // Register ActivityResult handler
    // GetContent creates an ActivityResultLauncher<String> to let you pass
    // in the mime type you want to let the user select
    static ActivityResultLauncher<Intent> mGetContent = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            (result) -> {
                Log.d("XXX", "XXX ActivityResultContracts: " + result.getPath());
                // code to process data from activity called
            });

    private static ActivityResultLauncher<Intent> registerForActivityResult(
            ActivityResultContracts.StartActivityForResult getContent, ActivityResultCallback<Uri> activityResultCallback) {
        Log.d("XXX", "XXX ActivityResultLauncher");
        return null;
    }
    // ******************************************

Then we call ActivityResultLauncher

    public static void getPictureFunction() {
        Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
//        sActivity.get().startActivityForResult(i, REQUEST_SELECT_PICTURE);
//        // Pass in the mime type you want to let the user select
//        // as the input
//        mGetContent.launch("image/*");
        GMedia.mGetContent.launch(i); // Intent
    }

This needs a lot of changes I am not able to do in Gideros (adding the new ActivityResultLauncher in GiderosAndroidPlayer/.../GiderosApplication.java) and some other stuff :-(

I added notes in the wiki where the plugin doesn't work anymore on new android!