/* @todo: popup when error @todo: read recursive @todo: add option for user to control what files are read @todo: pdf files @todo: plumbing for custom commands - pdf: {tools_path}/vlc {filename} --start-at {hour}:{second}:{minute} @todo: config with preload_files, initial_query, style @todo: help menu and config menu @todo: check for number of cores before creating threads @todo: */ #define BASIC_IMPL #include "../basic/basic.h" #include "../basic/filesystem.h" #include "../basic/thread_queue.h" #include #include #include "imgui.h" #include "imgui_impl_sdl2.h" #include "imgui_impl_opengl3.h" #include #include "glad.h" struct FileLoadResult { String filename; String error; }; Arena Perm; Array XFileLoadResults; #include "config.cpp" #include #include "read_pdf.cpp" #include "read_srt.cpp" WorkQueue MainWorkQueue; #include "loading_thread.cpp" #include "searching_thread.cpp" void UISearchResults(Array filenames) { Scratch scratch; Array matches = LockSearchResults(); defer { UnlockSearchResults(); }; float font_size = ImGui::GetFontSize(); ImFont *font = ImGui::GetFont(); const ImGuiViewport *main_viewport = ImGui::GetMainViewport(); int64_t chars_per_line = (int64_t)(main_viewport->WorkSize.x / 2) / (int64_t)font->GetCharAdvance('_') - strlen(Prompt) * 2; ImGuiListClipper clipper; clipper.Begin((int)matches.len); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { ImGui::PushID(i); defer { ImGui::PopID(); }; auto &it = matches[i]; uintptr_t begin_region = (uintptr_t)XArena.data; uintptr_t end_region = (uintptr_t)XArena.data + XArena.len; uint64_t begin = (uintptr_t)(it.data - chars_per_line / 2); uint64_t end = (uintptr_t)(it.data + it.len + chars_per_line / 2); uint64_t a = Clamp(begin, begin_region, end_region); uint64_t b = Clamp((uintptr_t)it.data, begin_region, end_region); String left = {(char *)a, (int64_t)(b - a)}; uint64_t c = Clamp((uintptr_t)(it.data + it.len), begin_region, end_region); uint64_t d = Clamp(end, begin_region, end_region); String right = {(char *)c, (int64_t)(d - c)}; String middle = it; String string = Format(scratch, "%.*s**%.*s**%.*s", FmtString(left), FmtString(middle), FmtString(right)); XToSource *item = XFindSource(middle); if (ImGui::Button(string.data)) { String exe_folder = GetExeDir(scratch); exe_folder = Format(scratch, "\"%.*s\"", FmtString(exe_folder)); Array replace_vars = {scratch}; replace_vars.add({"exe_folder", GetExeDir(scratch)}); if (item->kind == SourceKind_SRT) { String it = FindVideoForSRT(filenames, item->filepath); if (it.len) { int seconds = item->srt.hour * 60 * 60 + item->srt.minute * 60 + item->srt.second; String copy = Copy(scratch, it); for (int i = 0; i < copy.len; i += 1) if (copy.data[i] == '/') copy.data[i] = '\\'; replace_vars.add({"video", Format(scratch, "\"%.*s\"", FmtString(copy))}); replace_vars.add({"time_in_seconds", Format(scratch, "%d", seconds)}); String args = ReplaceVars(scratch, replace_vars, SRTCommand); Process process = RunEx(args); if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message}); } } else if (item->kind == SourceKind_TXT) { replace_vars.add({"file", Format(scratch, "\"%.*s\"", FmtString(item->filepath))}); replace_vars.add({"line", Format(scratch, "%d", item->txt.row + 1)}); String args = ReplaceVars(scratch, replace_vars, TXTCommand); Process process = RunEx(args); if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message}); } else if (item->kind == SourceKind_PDF) { replace_vars.add({"file", Format(scratch, "\"%.*s\"", FmtString(item->filepath))}); replace_vars.add({"page", Format(scratch, "%d", item->pdf.page)}); String args = ReplaceVars(scratch, replace_vars, PDFCommand); Process process = RunEx(args); if (!process.is_valid) XFileLoadResults.bounded_add({"", process.error_message}); } else { Assert(!"Invalid codepath"); } } ImGui::SameLine(); ImGui::Text(SkipToLastSlash(item->filepath).data); } } } void UILoadedFiles() { Scratch scratch; Array file_load_results = XLockFileLoadResults(); defer { XUnlockFileResults(); }; ImGuiListClipper clipper; clipper.Begin((int)file_load_results.len); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { FileLoadResult file = file_load_results[file_load_results.len - 1 - i]; if (file.error.len) { ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)ImColor::HSV(7.0f, 0.6f, 0.6f)); String string = Format(scratch, "%.*s error: %.*s", FmtString(file.filename), FmtString(file.error)); ImGui::Text(string.data); ImGui::PopStyleColor(); } else { String string = Format(scratch, "%.*s loaded", FmtString(file.filename)); ImGui::Text(string.data); } } } } int main(int, char **) { InitOS(); InitScratch(); InitArena(&Perm); // { // Scratch scratch; // for (FileIter it = IterateFiles(scratch, "D:/pdfs"); IsValid(it); Advance(&it)) { // if (it.is_directory || !EndsWith(it.absolute_path, ".pdf", true)) continue; // PDF pdf = pdfioReadPDF(scratch, it.absolute_path); // Array strings = {scratch}; // For(pdf.pages) { // strings.add(it.string); // } // String s = Merge(scratch, strings, "\n"); // WriteFile(Format(scratch, "%.*s.txt", FmtString(it.absolute_path)), s); // String pdf_content = ReadFile(scratch, it.absolute_path); // } // } XInitLoading(); InitSearch(); LoadConfig(); ThreadStartupInfo infos[16] = {}; InitWorkQueue(&MainWorkQueue, Lengthof(infos), infos); Array filenames = {}; if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) { printf("Error: %s\n", SDL_GetError()); return -1; } const char *glsl_version = "#version 150"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_Window *window = SDL_CreateWindow("Transcript browser", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 960, 600, window_flags); if (window == nullptr) { printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); return -1; } SDL_GLContext gl_context = SDL_GL_CreateContext(window); SDL_GL_MakeCurrent(window, gl_context); if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { return -1; } SDL_GL_SetSwapInterval(1); // Enable vsync // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO &io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Setup Dear ImGui style // ImGui::StyleColorsDark(); ImGui::StyleColorsLight(); // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForOpenGL(window, gl_context); ImGui_ImplOpenGL3_Init(glsl_version); io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); { String read_on_start = ReadOnStart; if (read_on_start != "-") { Scratch scratch; Array strings = Split(scratch, read_on_start, ";"); For(strings) XAddFolder(it, &filenames); } } bool command_menu_open = false; bool show_loaded_files = false; bool set_focus_to_input = true; int64_t frame = -1; // Main loop bool done = false; while (!done) { Scratch frame_arena; frame += 1; if (frame > 4) set_focus_to_input = false; bool set_focus_to_list = false; bool tab_press = false; bool enter_press = false; bool f1_press = false; bool f2_press = false; SDL_Event event; #if 1 while (SDL_PollEvent(&event)) { #else if (SDL_WaitEvent(&event)) { #endif ImGui_ImplSDL2_ProcessEvent(&event); if (event.type == SDL_KEYDOWN) { switch (event.key.keysym.sym) { case SDLK_UP: case SDLK_DOWN: case SDLK_PAGEUP: case SDLK_PAGEDOWN: set_focus_to_list = true; break; case SDLK_BACKSPACE: case SDLK_DELETE: set_focus_to_input = true; break; case SDLK_RETURN: enter_press = true; break; case SDLK_TAB: tab_press = true; break; case SDLK_F1: f1_press = true; break; case SDLK_F2: f2_press = true; break; } } else if (event.type == SDL_TEXTINPUT) { set_focus_to_input = true; } else if (event.type == SDL_QUIT) { done = true; } else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) { done = true; } } ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); #if 0 ImGui::ShowDemoWindow(); #else { ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; const ImGuiViewport *main_viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, 40), ImGuiCond_Always); ImGui::Begin("input", NULL, window_flags); if (show_loaded_files) { if (ImGui::Button("Show files (F1)") || f1_press) { show_loaded_files = !show_loaded_files; } } else { if (ImGui::Button("Hide files (F1)") || f1_press) { show_loaded_files = !show_loaded_files; } } ImGui::SameLine(); if (command_menu_open) { if (ImGui::Button("Hide config (F2)") || f2_press) { command_menu_open = !command_menu_open; } } else { if (ImGui::Button("Show config (F2)") || f2_press) { command_menu_open = !command_menu_open; } } ImGui::SameLine(); if (!command_menu_open && set_focus_to_input) ImGui::SetKeyboardFocusHere(0); if (tab_press && ImGui::IsWindowFocused()) set_focus_to_list = true; if (ImGui::InputText("Input your query", Prompt, sizeof(Prompt))) { StartSearchingForMatches(); } ImGui::End(); if (enter_press) { String prompt = Prompt; if (StartsWith(prompt, "read=")) { XAddFolder(prompt.skip(5), &filenames); memset(Prompt, 0, sizeof(Prompt)); } } } { ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse; const ImGuiViewport *main_viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(ImVec2(0, 35), ImGuiCond_Always); ImGui::SetNextWindowSize(ImVec2(main_viewport->Size.x, main_viewport->Size.y - 35), ImGuiCond_Always); ImGui::Begin("result", NULL, window_flags); if (!ImGui::IsWindowFocused() && set_focus_to_list) ImGui::SetKeyboardFocusHere(0); if (command_menu_open) { ImGui::InputText(".srt", SRTCommand, sizeof(SRTCommand)); ImGui::InputText(".pdf", PDFCommand, sizeof(PDFCommand)); ImGui::InputText(".txt", TXTCommand, sizeof(TXTCommand)); ImGui::InputText("Folders to read during startup", ReadOnStart, sizeof(ReadOnStart)); if (ImGui::Button("Save config")) { SaveConfig(); LoadConfig(); } } else { if (show_loaded_files) { UILoadedFiles(); } else { UISearchResults(filenames); } } ImGui::End(); } #endif ImGui::Render(); glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(window); SDL_Delay(16); } // Cleanup ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_Quit(); return 0; }