Skip to content

Commit

Permalink
keyaccel: introduce custom scriptable key shortcuts
Browse files Browse the repository at this point in the history
  • Loading branch information
hanatos committed Oct 13, 2023
1 parent 66fb949 commit 5f4e251
Show file tree
Hide file tree
Showing 19 changed files with 248 additions and 68 deletions.
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-bottom+
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# increase crop at the bottom by 5%
paraminc:crop:01:crop:3:4:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-bottom-
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# decrease crop at the bottom by 5%
paramdec:crop:01:crop:3:4:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-left+
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# increase crop at the left by 5%
paramdec:crop:01:crop:0:1:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-left-
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# decrease crop at the left by 5%
paraminc:crop:01:crop:0:1:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-right+
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# increase crop at the right by 5%
paraminc:crop:01:crop:1:2:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-right-
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# decrease crop at the right by 5%
paramdec:crop:01:crop:1:2:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-top+
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# increase crop at the top by 5%
paramdec:crop:01:crop:2:3:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/crop-top-
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# decrease crop at the top by 5%
paraminc:crop:01:crop:2:3:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/exposure+
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# increment exposure by half stop
paraminc:colour:01:exposure:0:1:0.5
2 changes: 2 additions & 0 deletions bin/data/keyaccel/exposure-
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# decrement exposure by half stop
paramdec:colour:01:exposure:0:1:0.5
2 changes: 2 additions & 0 deletions bin/data/keyaccel/rotate+
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# rotate by 1 degree ccw
paraminc:crop:01:rotate:0:1:0.05
2 changes: 2 additions & 0 deletions bin/data/keyaccel/rotate-
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# rotate by 1 degree cw
paramdec:crop:01:rotate:0:1:0.05
13 changes: 13 additions & 0 deletions doc/howto/presets/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,16 @@ included in the preset.

the default hotkey in darkroom mode is `ctrl-p` and will bring up the menu
to select a preset from a list.

## hotkey bindings

for quick access to everyday operations, you can tie custom preset scripts
directly to a hotkey. if you browse through the hotkeys in the settings
expander in darkroom mode, you'll find entries like `exposure+`. these realised
as small scripts in the `data/keyaccel/` or `~/.config/vkdt/keyaccel/` directories
which follow the same syntax as `.cfg` or `.pst` files. if the first line is
a comment (starting with `#`), it will be displayed as additional info in
the gui.

to support incremental changes to the parameters, you can use the `paraminc`
and `paramdec` commands, see the existing scripts for examples.
4 changes: 2 additions & 2 deletions src/db/stringpool.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ dt_stringpool_init(
size_t buf_size = num_entries * (uint64_t)avg_len;
memset(sp, 0, sizeof(*sp));
sp->entry_max = num_entries;
sp->entry = calloc(sizeof(dt_stringpool_entry_t), num_entries);
sp->buf = calloc(buf_size, 1);
sp->entry = (dt_stringpool_entry_t *)calloc(sizeof(dt_stringpool_entry_t), num_entries);
sp->buf = (char *)calloc(buf_size, 1);
sp->buf_max = buf_size;
sp->buf_cnt = 0;
}
Expand Down
1 change: 1 addition & 0 deletions src/gui/flat.mk
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ GUI_H=gui/gui.h\
gui/api.h\
gui/api.hh\
gui/hotkey.hh\
gui/keyaccel.hh\
gui/widget_dopesheet.hh\
gui/widget_draw.hh\
gui/widget_export.hh\
Expand Down
53 changes: 29 additions & 24 deletions src/gui/hotkey.hh
Original file line number Diff line number Diff line change
Expand Up @@ -262,37 +262,34 @@ namespace ImHotKey
if (ImGui::Button("clear", ImVec2(80*s, 40*s)))
memset(keyDown, 0, sizeof(keyDown));
ImGui::SameLine();
if (keyDownCount && keyDownCount <= 4)
if (ImGui::Button("set", ImVec2(80*s, 40*s)))
{
if (ImGui::Button("set", ImVec2(80*s, 40*s)))
int scanCodeCount = 0;
hotkey[editingHotkey].key[0] = 0;
hotkey[editingHotkey].key[1] = 0;
hotkey[editingHotkey].key[2] = 0;
hotkey[editingHotkey].key[3] = 0;
if(keyDownCount && keyDownCount <= 4) // else clear
for(uint32_t i = 1; i < sizeof(keyDown); i++)
{
int scanCodeCount = 0;
hotkey[editingHotkey].key[0] = 0;
hotkey[editingHotkey].key[1] = 0;
hotkey[editingHotkey].key[2] = 0;
hotkey[editingHotkey].key[3] = 0;
for(uint32_t i = 1; i < sizeof(keyDown); i++)
if (keyDown[i])
{
if (keyDown[i])
if(scanCodeCount++)
{
if(scanCodeCount++)
{
uint16_t *k = hotkey[editingHotkey].key + scanCodeCount-1;
k[0] = i + ImGuiKey_NamedKey_BEGIN;
if(k[0] >= ImGuiKey_ModCtrl)
{ // swap control/mod to beginning
uint16_t tmp = hotkey[editingHotkey].key[0];
hotkey[editingHotkey].key[0] = k[0];
k[0] = tmp;
}
uint16_t *k = hotkey[editingHotkey].key + scanCodeCount-1;
k[0] = i + ImGuiKey_NamedKey_BEGIN;
if(k[0] >= ImGuiKey_ModCtrl)
{ // swap control/mod to beginning
uint16_t tmp = hotkey[editingHotkey].key[0];
hotkey[editingHotkey].key[0] = k[0];
k[0] = tmp;
}
else hotkey[editingHotkey].key[0] = i + ImGuiKey_NamedKey_BEGIN;
}
else hotkey[editingHotkey].key[0] = i + ImGuiKey_NamedKey_BEGIN;
}
}
ImGui::SameLine(0.f, 20.f*s);
}
else ImGui::SameLine(0.f, 100.f*s);
ImGui::SameLine(0.f, 20.f*s);

if (ImGui::Button("done", ImVec2(80*s, 40*s))) { ImGui::CloseCurrentPopup(); }
ImGui::EndGroup();
Expand All @@ -303,6 +300,8 @@ namespace ImHotKey
static int GetHotKey(HotKey *hotkey, size_t hotkeyCount)
{
if(dt_gui_imgui_want_text()) return -1;
int max_cnt = 0;
int key = -1;
for(uint32_t i=0;i<hotkeyCount;i++)
{
int cnt = 0;
Expand All @@ -313,9 +312,15 @@ namespace ImHotKey
&& ImGui::IsKeyPressed(ImGuiKey(hotkey[i].key[2]), false)) ||
(cnt == 2 && ImGui::IsKeyDown(ImGuiKey(hotkey[i].key[0])) && ImGui::IsKeyPressed(ImGuiKey(hotkey[i].key[1]), false)) ||
(cnt == 1 && ImGui::IsKeyPressed(ImGuiKey(hotkey[i].key[0]), false)))
return i;
{
if(cnt > max_cnt)
{
max_cnt = cnt;
key = i;
}
}
}
return -1;
return key;
}

static void Serialise(const char *fn, HotKey *hk, int cnt)
Expand Down
106 changes: 106 additions & 0 deletions src/gui/keyaccel.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#pragma once
#include "db/stringpool.h"
#include "core/log.h"
#include <dirent.h>

// this is responsible for managing (user supplied) tiny-presets that can be
// bound to a button via the hotkey system.
// it will read global and custom keyaccel preset files from data/keyaccel/*
// and ~/.config/vkdt/keyaccel/*, respectively.

typedef struct dt_keyaccel_t
{
dt_stringpool_t sp;
}
dt_keyaccel_t;

inline int // return the new count after putting our stuff in there
dt_keyaccel_init(
dt_keyaccel_t *ka, // key accel struct to initialise/store strings on
ImHotKey::HotKey *list, // list of partially inited hotkeys
int list_cnt, // currently in list
const int list_size) // allocation size of the array
{
dt_stringpool_init(&ka->sp, 2*list_size, 30); // + space for description/comment
struct dirent **ent = 0;
char dirname[PATH_MAX];
int id = 0;
for(int dir=0;dir<2;dir++)
{ // first search home dir, then system wide:
if(dir) snprintf(dirname, sizeof(dirname), "%s/data/keyaccel", dt_pipe.basedir);
else snprintf(dirname, sizeof(dirname), "%s/keyaccel", vkdt.db.basedir);
int ent_cnt = scandir(dirname, &ent, 0, alphasort);
if(ent_cnt == -1) continue; // no such directory, we're out
for(int i=0;i<ent_cnt;i++)
{
id++;
if(list_cnt >= list_size) break;
if(ent[i]->d_name[0] == '.') continue; // skip parent directories and hidden files

char comment[256] = {0};
char filename[PATH_MAX];
int r = snprintf(filename, sizeof(filename), "%s/%s", dirname, ent[i]->d_name);
if(r >= sizeof(filename)-1) continue; // truncated
FILE *f = fopen(filename, "rb");
if(f)
{ // try to read comment line
fscanf(f, "# %255[^\n]", comment);
fclose(f);
}
else continue; // we can't even open the file!

const char *dedup0 = 0, *dedup1 = 0;
uint32_t id0 = dt_stringpool_get(&ka->sp, ent[i]->d_name, strlen(ent[i]->d_name), id, &dedup0);
if(id0 != id) continue; // this preset name already inserted (probably local overriding global)
dt_stringpool_get(&ka->sp, comment, strlen(comment), 0, &dedup1);
// dt_log(s_log_gui, "[keyaccel] adding hotkey %d %s %s\n", list_cnt, dedup0, dedup1);
list[list_cnt].functionName = dedup0;
list[list_cnt].functionLib = dedup1;
for(int k=0;k<4;k++) list[list_cnt].key[k] = 0; // these will be set when reading the darkroom.hotkeys config file
list_cnt++;
}
for(int i=0;i<ent_cnt;i++) free(ent[i]);
free(ent);
ent = 0;
}
return list_cnt;
}

inline void
dt_keyaccel_cleanup(
dt_keyaccel_t *ka)
{
dt_stringpool_cleanup(&ka->sp);
}

inline void
dt_keyaccel_exec(const char *key)
{ // first search home dir, then global dir. ingest preset line by line, with history
char filename[PATH_MAX];
snprintf(filename, sizeof(filename), "%s/keyaccel/%s", vkdt.db.basedir, key);
FILE *f = fopen(filename, "rb");
if(!f)
{
snprintf(filename, sizeof(filename), "%s/data/keyaccel/%s", dt_pipe.basedir, key);
f = fopen(filename, "rb");
}
if(!f) return; // out of luck today
uint32_t lno = 0;
char line[300000];
while(!feof(f))
{
fscanf(f, "%299999[^\n]", line);
if(fgetc(f) == EOF) break; // read \n
lno++;
// > 0 are warnings, < 0 are fatal, 0 is success
if(dt_graph_read_config_line(&vkdt.graph_dev, line) < 0) goto error;
dt_graph_history_line(&vkdt.graph_dev, line);
}
if(0)
{
error:
dt_log(s_log_err, "[keyaccel] failed to execute %s : %d", key, lno);
}
vkdt.graph_dev.runflags = static_cast<dt_graph_run_t>(s_graph_run_all);
fclose(f);
}
74 changes: 43 additions & 31 deletions src/gui/render_darkroom.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ extern "C"
}
#include "gui/render_view.hh"
#include "gui/hotkey.hh"
#include "gui/keyaccel.hh"
#include "gui/api.hh"
#include "gui/widget_dopesheet.hh"
#include "gui/widget_draw.hh"
Expand All @@ -16,32 +17,7 @@ extern "C"

namespace { // anonymous namespace

static ImHotKey::HotKey hk_darkroom[] = {
{"create preset", "create new preset from image", {ImGuiKey_LeftCtrl, ImGuiKey_O}},
{"apply preset", "choose preset to apply", {ImGuiKey_LeftCtrl, ImGuiKey_P}},
{"show history", "toggle visibility of left panel", {ImGuiKey_LeftCtrl, ImGuiKey_H}},
{"redo", "go up in history stack one item", {ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_Z}}, // test before undo
{"undo", "go down in history stack one item", {ImGuiKey_LeftCtrl, ImGuiKey_Z}},
{"assign tag", "assign a tag to the current image", {ImGuiKey_LeftCtrl, ImGuiKey_T}},
{"insert keyframe", "insert a keyframe for the active widget", {ImGuiKey_LeftCtrl, ImGuiKey_K}},
{"node editor", "show node editor for the current image", {ImGuiKey_LeftCtrl, ImGuiKey_N}},
{"next image", "switch to next image in folder", {ImGuiKey_Space}},
{"prev image", "switch to previous image in folder", {ImGuiKey_Backspace}},
{"fullscreen", "show/hide side panels for fullscreen", {ImGuiKey_Tab}},
{"dopesheet", "show/hide keyframe overview", {ImGuiKey_LeftCtrl, ImGuiKey_D}},
{"rate 0", "assign zero stars", {ImGuiKey_0}},
{"rate 1", "assign one star", {ImGuiKey_1}},
{"rate 2", "assign two stars", {ImGuiKey_2}},
{"rate 3", "assign three stars", {ImGuiKey_3}},
{"rate 4", "assign four stars", {ImGuiKey_4}},
{"rate 5", "assign five stars", {ImGuiKey_5}},
{"label red", "toggle red label", {ImGuiKey_F1}},
{"label green", "toggle green label", {ImGuiKey_F2}},
{"label blue", "toggle blue label", {ImGuiKey_F3}},
{"label yellow", "toggle yellow label", {ImGuiKey_F4}},
{"label purple", "toggle purple label", {ImGuiKey_F5}},
{"reload shaders", "debug: reload shader code while running", {}},
};
static dt_keyaccel_t keyaccel;

enum hotkey_names_t
{ // for sane access in code
Expand Down Expand Up @@ -69,6 +45,36 @@ enum hotkey_names_t
s_hotkey_label_4 = 21,
s_hotkey_label_5 = 22,
s_hotkey_reload_shaders = 23,
s_hotkey_count = 24,
};

static const int hk_darkroom_size = 128;
static int hk_darkroom_cnt = s_hotkey_count;
static ImHotKey::HotKey hk_darkroom[hk_darkroom_size] = {
{"create preset", "create new preset from image", {ImGuiKey_LeftCtrl, ImGuiKey_O}},
{"apply preset", "choose preset to apply", {ImGuiKey_LeftCtrl, ImGuiKey_P}},
{"show history", "toggle visibility of left panel", {ImGuiKey_LeftCtrl, ImGuiKey_H}},
{"redo", "go up in history stack one item", {ImGuiKey_LeftCtrl, ImGuiKey_LeftShift, ImGuiKey_Z}}, // test before undo
{"undo", "go down in history stack one item", {ImGuiKey_LeftCtrl, ImGuiKey_Z}},
{"assign tag", "assign a tag to the current image", {ImGuiKey_LeftCtrl, ImGuiKey_T}},
{"insert keyframe", "insert a keyframe for the active widget", {ImGuiKey_LeftCtrl, ImGuiKey_K}},
{"node editor", "show node editor for the current image", {ImGuiKey_LeftCtrl, ImGuiKey_N}},
{"next image", "switch to next image in folder", {ImGuiKey_Space}},
{"prev image", "switch to previous image in folder", {ImGuiKey_Backspace}},
{"fullscreen", "show/hide side panels for fullscreen", {ImGuiKey_Tab}},
{"dopesheet", "show/hide keyframe overview", {ImGuiKey_LeftCtrl, ImGuiKey_D}},
{"rate 0", "assign zero stars", {ImGuiKey_0}},
{"rate 1", "assign one star", {ImGuiKey_1}},
{"rate 2", "assign two stars", {ImGuiKey_2}},
{"rate 3", "assign three stars", {ImGuiKey_3}},
{"rate 4", "assign four stars", {ImGuiKey_4}},
{"rate 5", "assign five stars", {ImGuiKey_5}},
{"label red", "toggle red label", {ImGuiKey_F1}},
{"label green", "toggle green label", {ImGuiKey_F2}},
{"label blue", "toggle blue label", {ImGuiKey_F3}},
{"label yellow", "toggle yellow label", {ImGuiKey_F4}},
{"label purple", "toggle purple label", {ImGuiKey_F5}},
{"reload shaders", "debug: reload shader code while running", {}},
};

// used to communictate between the gui helper functions
Expand Down Expand Up @@ -143,7 +149,9 @@ void render_darkroom_full()

void render_darkroom()
{
gui.hotkey = ImHotKey::GetHotKey(hk_darkroom, sizeof(hk_darkroom)/sizeof(hk_darkroom[0]));
gui.hotkey = -1;
if(!ImGui::IsPopupOpen("edit hotkeys", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel))
gui.hotkey = ImHotKey::GetHotKey(hk_darkroom, hk_darkroom_cnt);
switch(gui.hotkey)
{ // these are "destructive" hotkeys, they change the image and invalidate the dset.
// this has to happen this frame *before* the dset is sent to imgui for display.
Expand Down Expand Up @@ -502,7 +510,7 @@ void render_darkroom()
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.0, 0.5));
if(ImGui::Button("hotkeys", ImVec2(-1, 0)))
ImGui::OpenPopup("edit hotkeys");
ImHotKey::Edit(hk_darkroom, sizeof(hk_darkroom)/sizeof(hk_darkroom[0]), "edit hotkeys");
ImHotKey::Edit(hk_darkroom, hk_darkroom_cnt, "edit hotkeys");
if(ImGui::Button("toggle perf overlay", ImVec2(-1, 0)))
vkdt.wstate.show_perf_overlay ^= 1;

Expand Down Expand Up @@ -610,18 +618,22 @@ void render_darkroom()
case s_hotkey_label_4: dt_gui_label_4(); break;
case s_hotkey_label_5: dt_gui_label_5(); break;
case s_hotkey_reload_shaders: dt_gui_dr_reload_shaders(); break;
default:;
default:
if(gui.hotkey >= s_hotkey_count && gui.hotkey < hk_darkroom_cnt) dt_keyaccel_exec(hk_darkroom[gui.hotkey].functionName);
break;
}
dt_gui_dr_modals(); // draw modal windows for presets etc
}

void render_darkroom_init()
{
ImHotKey::Deserialise("darkroom", hk_darkroom, sizeof(hk_darkroom)/sizeof(hk_darkroom[0]));
hk_darkroom_cnt = dt_keyaccel_init(&keyaccel, hk_darkroom, hk_darkroom_cnt, hk_darkroom_size);
ImHotKey::Deserialise("darkroom", hk_darkroom, hk_darkroom_cnt);
}

void render_darkroom_cleanup()
{
ImHotKey::Serialise("darkroom", hk_darkroom, sizeof(hk_darkroom)/sizeof(hk_darkroom[0]));
ImHotKey::Serialise("darkroom", hk_darkroom, hk_darkroom_cnt);
dt_keyaccel_cleanup(&keyaccel);
widget_end(); // commit params if still ongoing
}
Loading

0 comments on commit 5f4e251

Please sign in to comment.