Files
imgui_transcript_browser/src/transcript_browser/main.cpp
2024-07-12 12:47:29 +02:00

396 lines
15 KiB
C++

/*
@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 <mutex>
#include <stdio.h>
#include "imgui.h"
#include "imgui_impl_sdl2.h"
#include "imgui_impl_opengl3.h"
#include <SDL.h>
#include "glad.h"
struct FileLoadResult {
String filename;
String error;
};
Arena Perm;
Array<FileLoadResult> XFileLoadResults;
#include "config.cpp"
#include <pdfio.h>
#include "read_pdf.cpp"
#include "read_srt.cpp"
WorkQueue MainWorkQueue;
#include "loading_thread.cpp"
#include "searching_thread.cpp"
void UISearchResults(Array<String> filenames) {
Scratch scratch;
Array<String> 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<ReplaceVar> 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<FileLoadResult> 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<String> 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<String> 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<String> 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;
}