[0.1.16] Ctrl-C copies the selected capture's image, Ctrl-Shift-C its full path
The selected capture is the one outlined in the grid and shown in the Capture window. Its image is copied as the Capture window shows it, decoded on a thread of its own and put on the clipboard as screen captures are (Windows only so far); its path is copied as the system writes paths. Both are actions in [hotkeys] (copy_image, copy_path), which do not act while typing in the search box. Settings files at the current version get their default bindings at the next start; the comment saying to raise APP_USER_SETTINGS_VERSION for a new action had it backwards.
This commit is contained in:
parent
22cad98500
commit
03f23f7122
@ -58,6 +58,11 @@ see [Screen capture](#screen-capture).
|
|||||||
move the file to the Recycle Bin or to delete it permanently, overwriting it with random bytes
|
move the file to the Recycle Bin or to delete it permanently, overwriting it with random bytes
|
||||||
first; "Don't ask again" keeps the choice until the app restarts. Only the `.mcap` goes: the
|
first; "Don't ask again" keeps the choice until the app restarts. Only the `.mcap` goes: the
|
||||||
`.snagx` it was converted from is untouched.
|
`.snagx` it was converted from is untouched.
|
||||||
|
- **Copy:** click a thumbnail to select it (it is outlined, and shown in the Capture window), then
|
||||||
|
**Ctrl-C** copies its image, as the Capture window shows it (annotations and all; a video's
|
||||||
|
frame), and **Ctrl-Shift-C** its full path. Neither acts while you type in the search box. They
|
||||||
|
are `copy_image` and `copy_path` in `[hotkeys]` in `settings.config`. Copying images is Windows
|
||||||
|
only so far.
|
||||||
- **Files added or deleted outside the app** (in Explorer, by another program) appear in the grid
|
- **Files added or deleted outside the app** (in Explorer, by another program) appear in the grid
|
||||||
or leave it on their own, folders added, renamed or deleted too: the library is watched.
|
or leave it on their own, folders added, renamed or deleted too: the library is watched.
|
||||||
**Refresh** looks at the whole folder again, should the watcher have missed something.
|
**Refresh** looks at the whole folder again, should the watcher have missed something.
|
||||||
|
|||||||
@ -18,6 +18,10 @@ Ctrl-Equal increase_font_size
|
|||||||
Ctrl-Alt-Space reset_window
|
Ctrl-Alt-Space reset_window
|
||||||
Ctrl-Shift-Alt-R reset_layout
|
Ctrl-Shift-Alt-R reset_layout
|
||||||
|
|
||||||
|
# Library: the selected capture
|
||||||
|
Ctrl-C copy_image
|
||||||
|
Ctrl-Shift-C copy_path
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
Ctrl-Shift-D toggle_debug_panel
|
Ctrl-Shift-D toggle_debug_panel
|
||||||
Ctrl-Shift-L toggle_logger_panel
|
Ctrl-Shift-L toggle_logger_panel
|
||||||
|
|||||||
@ -18,6 +18,10 @@ Cmd-Equal increase_font_size
|
|||||||
Cmd-Opt-Shift-Space reset_window
|
Cmd-Opt-Shift-Space reset_window
|
||||||
Cmd-Shift-Opt-R reset_layout
|
Cmd-Shift-Opt-R reset_layout
|
||||||
|
|
||||||
|
# Library: the selected capture
|
||||||
|
Cmd-C copy_image
|
||||||
|
Cmd-Shift-C copy_path
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
Cmd-Shift-D toggle_debug_panel
|
Cmd-Shift-D toggle_debug_panel
|
||||||
Cmd-Shift-L toggle_logger_panel
|
Cmd-Shift-L toggle_logger_panel
|
||||||
|
|||||||
@ -47,7 +47,7 @@ APP_NAME :: "mcap";
|
|||||||
APP_FOLDER_NAME :: "mcap";
|
APP_FOLDER_NAME :: "mcap";
|
||||||
GIT_BRANCH :: "check";
|
GIT_BRANCH :: "check";
|
||||||
GIT_HASH :: "-";
|
GIT_HASH :: "-";
|
||||||
VERSION :: "0.1.15";
|
VERSION :: "0.1.16";
|
||||||
BUILD_DATE :: "-";
|
BUILD_DATE :: "-";
|
||||||
DEBUG :: true;
|
DEBUG :: true;
|
||||||
COMPILE_TESTS :: true;
|
COMPILE_TESTS :: true;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ APP_NAME :: "mcap";
|
|||||||
APP_FOLDER_NAME :: "mcap";
|
APP_FOLDER_NAME :: "mcap";
|
||||||
APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
|
APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
|
||||||
|
|
||||||
VERSION :: "0.1.15";
|
VERSION :: "0.1.16";
|
||||||
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
|
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
|
||||||
// builds never write over the ones you run by hand.
|
// builds never write over the ones you run by hand.
|
||||||
build_directory := "bin";
|
build_directory := "bin";
|
||||||
|
|||||||
@ -156,6 +156,14 @@ viewer: struct {
|
|||||||
open_name: string;
|
open_name: string;
|
||||||
open_result: string; // the file written, or ""; heap, the open thread's until it ends
|
open_result: string; // the file written, or ""; heap, the open thread's until it ends
|
||||||
open_error: string;
|
open_error: string;
|
||||||
|
|
||||||
|
// The selected capture's image, decoded off the main thread for the clipboard (viewer_copy_image).
|
||||||
|
copy_thread: Thread;
|
||||||
|
copy_started: bool;
|
||||||
|
copy_busy: bool; // set by the main thread, cleared by the copy thread when it ends
|
||||||
|
copy_source: string; // the .mcap; heap, main thread's until the thread starts
|
||||||
|
copy_name: string;
|
||||||
|
copy_done: Decoded; // the copy thread's until it ends
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- startup and shutdown
|
// ---------------------------------------------------------------- startup and shutdown
|
||||||
@ -229,6 +237,7 @@ viewer_shutdown :: () {
|
|||||||
for 1..viewer.workers_started signal(*viewer.wake);
|
for 1..viewer.workers_started signal(*viewer.wake);
|
||||||
for 0..viewer.workers_started - 1 thread_is_done(*viewer.workers[it], -1);
|
for 0..viewer.workers_started - 1 thread_is_done(*viewer.workers[it], -1);
|
||||||
if viewer.open_started thread_is_done(*viewer.open_thread, -1);
|
if viewer.open_started thread_is_done(*viewer.open_thread, -1);
|
||||||
|
if viewer.copy_started thread_is_done(*viewer.copy_thread, -1);
|
||||||
background_shutdown();
|
background_shutdown();
|
||||||
catalog_cache_save_at_shutdown(); // the workers have stopped: nothing changes the entries now
|
catalog_cache_save_at_shutdown(); // the workers have stopped: nothing changes the entries now
|
||||||
viewer_watch_stop();
|
viewer_watch_stop();
|
||||||
@ -451,6 +460,7 @@ viewer_frame_gpu_work :: () {
|
|||||||
viewer_apply_catalog_updates();
|
viewer_apply_catalog_updates();
|
||||||
catalog_cache_frame(); // saves the catalog cache once it is complete and has changed (app/catalog.jai)
|
catalog_cache_frame(); // saves the catalog cache once it is complete and has changed (app/catalog.jai)
|
||||||
viewer_open_frame();
|
viewer_open_frame();
|
||||||
|
viewer_copy_frame();
|
||||||
segment_overlay_frame(); // the Capture window's segments: what arrived, and its texture (app/segment_ui.jai)
|
segment_overlay_frame(); // the Capture window's segments: what arrived, and its texture (app/segment_ui.jai)
|
||||||
background_frame(); // takes back search results, and starts a search when visible_dirty (app/background.jai)
|
background_frame(); // takes back search results, and starts a search when visible_dirty (app/background.jai)
|
||||||
}
|
}
|
||||||
@ -1215,6 +1225,77 @@ viewer_open_frame :: () {
|
|||||||
else toast_show(.Error, "Could not write % out: %", viewer.open_name, viewer.open_error);
|
else toast_show(.Error, "Could not write % out: %", viewer.open_name, viewer.open_error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------- copying the selected capture
|
||||||
|
|
||||||
|
// The selected capture: the one outlined in the grid and shown in the Capture window. -1 when none.
|
||||||
|
viewer_selected :: () -> s32 {
|
||||||
|
index := viewer.full_index;
|
||||||
|
if index < 0 || index >= viewer.captures.count || viewer.captures[index].removed return -1;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl-Shift-C (copy_path in [hotkeys]): the selected capture's path, as the system writes paths.
|
||||||
|
viewer_copy_path :: () {
|
||||||
|
index := viewer_selected();
|
||||||
|
if index < 0 { toast_show(.Error, "Select a capture to copy its path"); return; }
|
||||||
|
capture := *viewer.captures[index];
|
||||||
|
path := capture.path;
|
||||||
|
#if OS == .WINDOWS path = replace(path, "/", "\\",, temp);
|
||||||
|
clipboard_set_text(path);
|
||||||
|
toast_show(.Success, "Copied the path of %", capture.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl-C (copy_image in [hotkeys]): the selected capture's image, as the Capture window shows it
|
||||||
|
// (annotations and all; a video's frame), decoded on a thread of its own and put on the clipboard
|
||||||
|
// by viewer_copy_frame.
|
||||||
|
viewer_copy_image :: () {
|
||||||
|
index := viewer_selected();
|
||||||
|
if index < 0 { toast_show(.Error, "Select a capture to copy its image"); return; }
|
||||||
|
capture := *viewer.captures[index];
|
||||||
|
#if OS != .WINDOWS {
|
||||||
|
toast_show(.Error, "Copying images is not available on this system yet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if viewer.copy_started { toast_show(.Error, "Still copying %", viewer.copy_name); return; }
|
||||||
|
push_allocator(context.default_allocator);
|
||||||
|
free(viewer.copy_source); free(viewer.copy_name);
|
||||||
|
viewer.copy_source = copy_string(capture.path);
|
||||||
|
viewer.copy_name = copy_string(capture.name);
|
||||||
|
viewer.copy_done = .{};
|
||||||
|
viewer.copy_busy = true;
|
||||||
|
// Reading an image takes a few MB of temporary storage, as on the viewer's workers.
|
||||||
|
viewer.copy_started = thread_init(*viewer.copy_thread, viewer_copy_proc, temporary_storage_size = 1024 * 1024, name = "copy image");
|
||||||
|
if !viewer.copy_started { viewer.copy_busy = false; toast_show(.Error, "Could not copy %", capture.name); return; }
|
||||||
|
thread_start(*viewer.copy_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewer_copy_proc :: (thread: *Thread) -> s64 {
|
||||||
|
context.allocator = context.default_allocator;
|
||||||
|
defer atomic_swap(*viewer.copy_busy, false);
|
||||||
|
viewer.copy_done = viewer_decode_full(viewer.copy_source);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each frame: puts what viewer_copy_proc decoded on the clipboard, once it is done.
|
||||||
|
viewer_copy_frame :: () {
|
||||||
|
if !viewer.copy_started || viewer.copy_busy return;
|
||||||
|
thread_is_done(*viewer.copy_thread, -1);
|
||||||
|
thread_deinit(*viewer.copy_thread);
|
||||||
|
viewer.copy_started = false;
|
||||||
|
done := *viewer.copy_done;
|
||||||
|
defer viewer_free_pixels(done);
|
||||||
|
if done.failed { toast_show(.Error, "Could not read the image of %; see the Event Log", viewer.copy_name); return; }
|
||||||
|
pixels := []u8.{cast(s64) done.width * done.height * 4, done.pixels};
|
||||||
|
if !platform_copy_image_to_clipboard(pixels, done.width, done.height) {
|
||||||
|
log_error("[viewer] Could not put the image of % on the clipboard.", viewer.copy_source);
|
||||||
|
toast_show(.Error, "Could not put % on the clipboard", viewer.copy_name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scaled := ifx done.width != done.source_width || done.height != done.source_height then tprint(", scaled down from % x %", done.source_width, done.source_height) else "";
|
||||||
|
log("[viewer] Copied the image of % (% x %%).", viewer.copy_source, done.width, done.height, scaled);
|
||||||
|
toast_show(.Success, "Copied % (% x %%)", viewer.copy_name, done.width, done.height, scaled);
|
||||||
|
}
|
||||||
|
|
||||||
viewer_grid :: () { using ImGui;
|
viewer_grid :: () { using ImGui;
|
||||||
style := GetStyle();
|
style := GetStyle();
|
||||||
cell := viewer.thumb_size;
|
cell := viewer.thumb_size;
|
||||||
|
|||||||
@ -148,8 +148,9 @@ to_string_all_bindings :: (hotkey: Hotkey) -> string {
|
|||||||
// The bindings are read from settings.config, or from DEFAULT_HOTKEYS_DATA when the file has
|
// The bindings are read from settings.config, or from DEFAULT_HOTKEYS_DATA when the file has
|
||||||
// no [hotkeys] section; see hotkeys_install. once and ignore_when_text_field_active belong to the
|
// no [hotkeys] section; see hotkeys_install. once and ignore_when_text_field_active belong to the
|
||||||
// action and are set here. To add an action: declare its Hotkey here, add it to
|
// action and are set here. To add an action: declare its Hotkey here, add it to
|
||||||
// hotkey_actions_build and app_check_key_combinations, bind it in both assets/data/default_hotkeys
|
// hotkey_actions_build and app_check_key_combinations, and bind it in both
|
||||||
// files, and raise APP_USER_SETTINGS_VERSION so existing settings files get its default.
|
// assets/data/default_hotkeys files. Settings files at APP_USER_SETTINGS_VERSION get its default
|
||||||
|
// binding at the next start (hotkeys_missing_lines); raising the version would give them "none".
|
||||||
program_exit_hotkey: Hotkey;
|
program_exit_hotkey: Hotkey;
|
||||||
minimize_to_tray_hotkey: Hotkey;
|
minimize_to_tray_hotkey: Hotkey;
|
||||||
toggle_debug_panel_hotkey: Hotkey;
|
toggle_debug_panel_hotkey: Hotkey;
|
||||||
@ -162,6 +163,10 @@ increase_font_size_hotkey: Hotkey;
|
|||||||
reset_window_hotkey: Hotkey;
|
reset_window_hotkey: Hotkey;
|
||||||
reset_layout_hotkey: Hotkey;
|
reset_layout_hotkey: Hotkey;
|
||||||
|
|
||||||
|
// The selected capture (app/viewer.jai). Not while typing, so Ctrl-C in the search box copies its text.
|
||||||
|
copy_image_hotkey: Hotkey = .{ignore_when_text_field_active = true};
|
||||||
|
copy_path_hotkey: Hotkey = .{ignore_when_text_field_active = true};
|
||||||
|
|
||||||
Hotkey_Action :: struct {
|
Hotkey_Action :: struct {
|
||||||
name: string; // as written in [hotkeys]
|
name: string; // as written in [hotkeys]
|
||||||
hotkey: *Hotkey;
|
hotkey: *Hotkey;
|
||||||
@ -182,6 +187,8 @@ hotkey_actions_build :: (out: *[..] Hotkey_Action) {
|
|||||||
add(out, "increase_font_size", *increase_font_size_hotkey);
|
add(out, "increase_font_size", *increase_font_size_hotkey);
|
||||||
add(out, "reset_window", *reset_window_hotkey);
|
add(out, "reset_window", *reset_window_hotkey);
|
||||||
add(out, "reset_layout", *reset_layout_hotkey);
|
add(out, "reset_layout", *reset_layout_hotkey);
|
||||||
|
add(out, "copy_image", *copy_image_hotkey);
|
||||||
|
add(out, "copy_path", *copy_path_hotkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
hotkey_actions: [..] Hotkey_Action;
|
hotkey_actions: [..] Hotkey_Action;
|
||||||
@ -627,6 +634,9 @@ app_check_key_combinations :: () {
|
|||||||
else toast_show(.Error, "Could not reset the window");
|
else toast_show(.Error, "Could not reset the window");
|
||||||
}
|
}
|
||||||
if check_key_combination(*reset_layout_hotkey) imgui_reset_layout_requested = true; // applied between frames
|
if check_key_combination(*reset_layout_hotkey) imgui_reset_layout_requested = true; // applied between frames
|
||||||
|
// The viewer is the shutdown thread's once shutdown has started.
|
||||||
|
if check_key_combination(*copy_image_hotkey) && !app_exit_shutdown_started() viewer_copy_image();
|
||||||
|
if check_key_combination(*copy_path_hotkey) && !app_exit_shutdown_started() viewer_copy_path();
|
||||||
|
|
||||||
if should_exit_this_frame {
|
if should_exit_this_frame {
|
||||||
log("Calling exit hotkey... exiting program!");
|
log("Calling exit hotkey... exiting program!");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user