#include "file_browser.h" #include #include #include #include #include #ifdef __ANDROID__ #include jclass MainActivity; jobject mainActivity; jmethodID GetUserDir_Method; jmethodID OpenFilePicker_Method; jmethodID GetPickedFile_Method; jmethodID ClearSelected_Method; jmethodID IsLoading_Method; JNIEnv *env; bool IsLoading() { return env->CallStaticBooleanMethod(MainActivity, IsLoading_Method, 0); } const char *GetUserDir() { jstring str = (jstring)env->CallStaticObjectMethod(MainActivity, GetUserDir_Method, 0);; jboolean tmp = true; return env->GetStringUTFChars(str, &tmp); } void OpenFilePicker(const char *pwd) { if (pwd == nullptr) { jstring string = env->NewStringUTF(pwd); env->CallStaticVoidMethod(MainActivity, OpenFilePicker_Method, string); } else { env->CallStaticVoidMethod(MainActivity, OpenFilePicker_Method, nullptr); } } const char *GetPickedFile() { jstring str = (jstring)env->CallStaticObjectMethod(MainActivity, GetPickedFile_Method, 0); jboolean tmp = true; return env->GetStringUTFChars(str, &tmp); } void ClearSelected() { env->CallStaticVoidMethod(MainActivity, ClearSelected_Method, 0); } #endif #ifdef __EMSCRIPTEN__ extern "C" { extern void open_filepicker(); extern void set_filter(const char *filter); extern const char *get_first_file(); extern bool file_picker_cancelled(); extern bool file_picker_confirmed(); extern bool file_picker_closed(); extern bool file_picker_visible(); extern bool file_picker_loading(); extern void clear_file_selection(); } #endif FileBrowser::FileBrowser(bool save, ImGuiFileBrowserFlags extra_fallback_flags) { #ifdef PORTALS main_context = g_main_context_default(); main_loop = g_main_loop_new(main_context, true); portal = xdp_portal_new(); type = g_variant_type_new("a(sa(us))"); inner_filter_type = g_variant_type_new("a(us)"); #endif this->save = save; this->flags = (save ? ImGuiFileBrowserFlags_CreateNewDir|ImGuiFileBrowserFlags_EnterNewFilename : 0) | extra_fallback_flags; fallback = ImGui::FileBrowser(this->flags); } void FileBrowser::SetTypeFilters(string name, vector filters) { filter_name = name; this->filters = filters; #ifdef __EMSCRIPTEN__ std::string filterStr; for (auto filter : filters) { filterStr += ","; filterStr += filter; } filterStr = filterStr.substr(1); set_filter(filterStr.c_str()); #endif #ifdef PORTALS if (variant != NULL) { g_variant_unref(variant); } GVariantBuilder builder; GVariantBuilder inner_filter_builder; g_variant_builder_init(&builder, type); g_variant_builder_init(&inner_filter_builder, inner_filter_type); GVariant *name_variant = g_variant_new_string((const gchar*)name.c_str()); for (auto filter : filters) { GVariant *id = g_variant_new_uint32(0); GVariant *filter_variant = g_variant_new_string((const gchar*)(string("*") + filter).c_str()); GVariant **filter_children = new GVariant*[2] {id, filter_variant}; GVariant *filter_tuple = g_variant_new_tuple(filter_children, 2); g_variant_builder_add_value(&inner_filter_builder, filter_tuple); } GVariant *inner_filters = g_variant_builder_end(&inner_filter_builder); GVariant **outer_filter_children = new GVariant*[2] {name_variant, inner_filters}; GVariant *outer_filter_tuple = g_variant_new_tuple(outer_filter_children, 2); g_variant_builder_add_value(&builder, outer_filter_tuple); variant = g_variant_builder_end(&builder); g_variant_ref(variant); #endif fallback.SetTypeFilters(filters); } void FileBrowser::SetPwd(path path) { pwd = path; #if !(defined(PORTALS) || defined(__ANDROID__)) fallback.SetPwd(path); #endif } bool FileBrowser::HasSelected() { #ifdef PORTALS return selected.has_value(); #elif defined(__ANDROID__) return strlen(GetPickedFile()) > 0; #elif defined(__EMSCRIPTEN__) return file_picker_confirmed(); #else return fallback.HasSelected(); #endif } path FileBrowser::GetSelected() { #ifdef PORTALS return selected.value_or(path()); #elif defined(__ANDROID__) const char *file = GetPickedFile(); if (strlen(file) > 0) { return std::string(file); } else { return {}; } #elif defined(__EMSCRIPTEN__) if (HasSelected()) { const char *c_file = get_first_file(); std::string name = c_file; free((void*)c_file); return name; } else { return {}; } #else return fallback.GetSelected(); #endif } void FileBrowser::SetWindowSize(int w, int h) { window_size = ImVec2(w, h); fallback.SetWindowSize(w, h); } void FileBrowser::SetWindowPos(int x, int y) { window_pos = ImVec2(x, y); fallback.SetWindowPos(x, y); } void FileBrowser::Open() { #ifdef PORTALS open = true; if (save) { xdp_portal_save_file(portal, NULL, title.c_str(), NULL, pwd.c_str(), NULL, variant, NULL, NULL, XDP_SAVE_FILE_FLAG_NONE, NULL, &FileBrowser::FileBrowserSaveCallback, this); } else { xdp_portal_open_file(portal, NULL, title.c_str(), variant, NULL, NULL, XDP_OPEN_FILE_FLAG_NONE, NULL, &FileBrowser::FileBrowserOpenCallback, this); } #elif defined(__ANDROID__) ClearSelected(); open = true; ::OpenFilePicker(nullptr); #elif defined(__EMSCRIPTEN__) open_filepicker(); #else fallback.Open(); #endif } #ifdef PORTALS path url_decode(string str) { static const string file_proto = "file://"; if (str.starts_with(file_proto)) { str = str.substr(file_proto.length()); } string ret; int len = str.length(); for (int i = 0; i < len; i++) { if (str[i] != '%') { // Don't decode '+' as a space as libportal doesn't encode space as '+', and encodes '+' in a file path verbatim ret += str[i]; } else { unsigned int ch_int; sscanf(str.substr(i + 1, 2).c_str(), "%x", &ch_int); ret += static_cast(ch_int); i = i + 2; } } return ret; } void FileBrowser::FileBrowserOpenCallback(GObject *src, GAsyncResult *res, gpointer data) { (void)src; FileBrowser *self = (FileBrowser*)data; GVariant *variant = xdp_portal_open_file_finish(self->portal, res, NULL); if (variant == NULL) { printf("Cancelled.\n"); return; } GVariant *uris = g_variant_lookup_value(variant, "uris", G_VARIANT_TYPE_STRING_ARRAY); GVariant *first_uri = g_variant_get_child_value(uris, 0); gsize length; const gchar* c_str_without_terminater = g_variant_get_string(first_uri, &length); vector c_str_vec; c_str_vec.reserve(length + 1); for (gsize i = 0; i < length; i++) { c_str_vec.push_back(c_str_without_terminater[i]); } c_str_vec.push_back('\0'); const char *c_str = c_str_vec.data(); printf("Selected file %s\n", c_str); path path = url_decode(c_str); self->open = false; self->selected.emplace(path); } void FileBrowser::FileBrowserSaveCallback(GObject *src, GAsyncResult *res, gpointer data) { (void)src; FileBrowser *self = (FileBrowser*)data; GVariant *variant = xdp_portal_save_file_finish(self->portal, res, NULL); if (variant == NULL) { printf("Cancelled.\n"); return; } GVariant *uris = g_variant_lookup_value(variant, "uris", G_VARIANT_TYPE_STRING_ARRAY); GVariant *first_uri = g_variant_get_child_value(uris, 0); gsize length; const gchar* c_str_without_terminater = g_variant_get_string(first_uri, &length); vector c_str_vec; c_str_vec.reserve(length + 1); for (gsize i = 0; i < length; i++) { c_str_vec.push_back(c_str_without_terminater[i]); } c_str_vec.push_back('\0'); const char *c_str = c_str_vec.data(); printf("Selected file %s\n", c_str); path path = url_decode(c_str); self->open = false; self->selected.emplace(path); } #endif void FileBrowser::Display() { #ifdef PORTALS g_main_context_iteration(main_context, false); #elif defined(__ANDROID__) if (HasSelected()) { open = false; } if (IsLoading()) { ImVec2 pos(0, 0); ImVec2 size = ImGui::GetMainViewport()->Size; ImGui::SetNextWindowPos(pos); ImGui::SetNextWindowSize(size); ImGui::Begin("Loading...", NULL, ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoTitleBar); ImVec2 textSize = ImGui::CalcTextSize("Loading..."); ImVec2 textPos = size; textPos.x -= textSize.x; textPos.y -= textSize.y; textPos.x /= 2; textPos.y /= 2; ImGui::SetCursorPos(textPos); ImGui::TextUnformatted("Loading..."); ImGui::End(); } #elif defined(__EMSCRIPTEN__) if (file_picker_visible() || file_picker_loading()) { if((flags & ImGuiFileBrowserFlags_NoModal)) { if (window_pos.has_value()) ImGui::SetNextWindowPos( window_pos.value()); ImGui::SetNextWindowSize( window_size); } else { if (window_pos.has_value()) ImGui::SetNextWindowPos( window_pos.value()); ImGui::SetNextWindowSize( window_size); } if(flags & ImGuiFileBrowserFlags_NoModal) { if(!ImGui::BeginPopup(title.c_str(), (flags & ImGuiFileBrowserFlags_NoMove ? ImGuiWindowFlags_NoMove : 0) | (flags & ImGuiFileBrowserFlags_NoResize ? ImGuiWindowFlags_NoResize : 0))) { return; } } else if(!ImGui::BeginPopupModal(title.c_str(), nullptr, (flags & ImGuiFileBrowserFlags_NoMove ? ImGuiWindowFlags_NoMove : 0) | (flags & ImGuiFileBrowserFlags_NoResize ? ImGuiWindowFlags_NoResize : 0) | (flags & ImGuiFileBrowserFlags_NoTitleBar ? ImGuiWindowFlags_NoTitleBar : 0))) { return; } if (file_picker_loading()) { ImGui::Text("Loading file(s)..."); } else { ImGui::Text("Please select a file..."); } ImGui::EndPopup(); } #else fallback.Display(); #endif } void FileBrowser::ClearSelected() { selected = optional(); #ifdef __EMSCRIPTEN__ clear_file_selection(); #endif #ifdef __ANDROID__ ::ClearSelected(); #endif #ifndef PORTALS fallback.ClearSelected(); #endif } void FileBrowser::SetTitle(string title) { this->title = title; #ifndef PORTALS fallback.SetTitle(title); #endif } bool FileBrowser::IsOpened() { #ifdef PORTALS return open; #elif defined(__ANDROID__) return open; #elif defined(__EMSCRIPTEN__) return !file_picker_closed() || file_picker_confirmed(); #else return fallback.IsOpened(); #endif } FileBrowser::~FileBrowser() { #ifdef PORTALS if (variant != NULL) { g_variant_unref(variant); } if (type != NULL) { g_variant_type_free(type); } if (inner_filter_type != NULL) { g_variant_type_free(inner_filter_type); } g_main_loop_quit(main_loop); #endif }