Skip to content

Commit

Permalink
Finally fix the selection/interaction paradigm in GUI
Browse files Browse the repository at this point in the history
- global actions are available from the global menu,
- global actions are applied on app-wide selection, else on view-wide active images. Selection is remembered between app restarts and saved in database,
- only lighttable can register selections (as in: user input), either from thumbnail interaction or from key shortcuts/global menu
- interactions with thumbnails in filmstrip don't make app-mide selections, they only update active images for the views using them,
- darkroom clears the selection,
- global actions affecting images both in lighttable and darkroom are properly reloading the history in the darkroom development at completion

Meaning:
1. global actions doing write operations will only affect the currently-opened image when in darkroom, else the selection in lighttable. Any other behaviour is either (or both) unpredictable or unintuitive.
2. selection happens on mouse **click**, global menu **click**, global menu **shortcuts**, and key actions on thumbnail (to be re-implemented),
3. mouse-hover events refresh read-only displays, like EXIF and stuff.
4. copy-pasting histories, which is a cross-image operation, happens only in lighttable to make it predictable through possibly-hidden dangling selection in filmstrip.

Fix #284
Fix #350
Fix darktable-org#6025
Fix darktable-org#16850

That shitshow went on for much too long, thank you very much.
  • Loading branch information
aurelienpierre committed Feb 7, 2025
1 parent 27b7ce3 commit a3b86f5
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 158 deletions.
22 changes: 22 additions & 0 deletions src/common/colorlabels.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,27 @@ const char *dt_colorlabels_name[] = {
NULL // termination
};

char * dt_colorlabels_get_name(const int label)
{
switch(label)
{
case 0:
return _("red");
case 1:
return _("yellow");
case 2:
return _("green");
case 3:
return _("blue");
case 4:
return _("purple");
case 5:
return _("empty");
default:
return _("unknown/invalid");
}
}

typedef struct dt_undo_colorlabels_t
{
int imgid;
Expand Down Expand Up @@ -241,6 +262,7 @@ void dt_colorlabels_toggle_label_on_list(GList *list, const int color, const gbo
dt_undo_end_group(darktable.undo);
}
dt_collection_hint_message(darktable.collection);
dt_toast_log(_("Color label set to %s"), dt_colorlabels_get_name(color));
}

int dt_colorlabels_check_label(const int imgid, const int color)
Expand Down
11 changes: 3 additions & 8 deletions src/common/history.c
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ gboolean dt_history_paste_on_list(const GList *list, gboolean undo)
return FALSE;

if(undo) dt_undo_start_group(darktable.undo, DT_UNDO_LT_HISTORY);
for(GList *l = (GList *)list; l; l = g_list_next(l))
for(GList *l = g_list_first((GList *)list); l; l = g_list_next(l))
{
const int dest = GPOINTER_TO_INT(l->data);
dt_history_copy_and_paste_on_image(darktable.view_manager->copy_paste.copied_imageid,
Expand All @@ -1020,20 +1020,17 @@ gboolean dt_history_paste_parts_on_list(const GList *list, gboolean undo)
// at the time the dialog is started, some signals are sent and this in turn call
// back dt_view_get_images_to_act_on() which free list and create a new one.

GList *l_copy = g_list_copy((GList *)list);

// we launch the dialog
const int res = dt_gui_hist_dialog_new(&(darktable.view_manager->copy_paste),
darktable.view_manager->copy_paste.copied_imageid, FALSE);

if(res != GTK_RESPONSE_OK)
{
g_list_free(l_copy);
return FALSE;
}

if(undo) dt_undo_start_group(darktable.undo, DT_UNDO_LT_HISTORY);
for (const GList *l = l_copy; l; l = g_list_next(l))
for (const GList *l = g_list_first((GList *)list); l; l = g_list_next(l))
{
const int dest = GPOINTER_TO_INT(l->data);
dt_history_copy_and_paste_on_image(darktable.view_manager->copy_paste.copied_imageid,
Expand All @@ -1044,8 +1041,6 @@ gboolean dt_history_paste_parts_on_list(const GList *list, gboolean undo)
}
if(undo) dt_undo_end_group(darktable.undo);

g_list_free(l_copy);

return TRUE;
}

Expand All @@ -1056,7 +1051,7 @@ gboolean dt_history_delete_on_list(const GList *list, gboolean undo)

if(undo) dt_undo_start_group(darktable.undo, DT_UNDO_LT_HISTORY);

for(GList *l = (GList *)list; l; l = g_list_next(l))
for(GList *l = g_list_first((GList *)list); l; l = g_list_next(l))
{
const int imgid = GPOINTER_TO_INT(l->data);
dt_undo_lt_history_t *hist = dt_history_snapshot_item_init();
Expand Down
2 changes: 2 additions & 0 deletions src/common/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,8 @@ void dt_image_flip(const int32_t imgid, const int32_t cw)
dt_history_snapshot_undo_pop, dt_history_snapshot_undo_lt_history_data_free);

dt_mipmap_cache_remove(darktable.mipmap_cache, imgid);

DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_DEVELOP_MIPMAP_UPDATED, imgid);
}

/* About the image size ratio
Expand Down
24 changes: 24 additions & 0 deletions src/common/ratings.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ typedef struct dt_undo_ratings_t
int after;
} dt_undo_ratings_t;

char *dt_ratings_get_name(const int rating)
{
switch(rating)
{
case 0:
return _("empty");
case 1:
return _("1 star");
case 2:
return _("2 stars");
case 3:
return _("3 stars");
case 4:
return _("4 stars");
case 5:
return _("5 stars");
case 6:
return _("rejected");
default:
return _("unknown/invalid");
}
}

int dt_ratings_get(const int imgid)
{
int stars = 0;
Expand Down Expand Up @@ -169,6 +192,7 @@ void dt_ratings_apply_on_list(GList *img, const int rating, const gboolean undo_
dt_undo_end_group(darktable.undo);
}
dt_collection_hint_message(darktable.collection);
dt_toast_log(_("Rating set to %s"), dt_ratings_get_name(rating));
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/develop/dev_history.c
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,6 @@ gboolean dt_history_copy_and_paste_on_image(const int32_t imgid, const int32_t d
return 1;
}

// be sure the current history is written before pasting some other history data
const dt_view_t *cv = dt_view_manager_get_current_view(darktable.view_manager);
if(cv->view((dt_view_t *)cv) == DT_VIEW_DARKROOM) dt_dev_write_history(darktable.develop);

dt_undo_lt_history_t *hist = dt_history_snapshot_item_init();
hist->imgid = dest_imgid;
dt_history_snapshot_undo_create(hist->imgid, &hist->before, &hist->before_history_end);
Expand Down
30 changes: 18 additions & 12 deletions src/dtgtk/thumbnail.c
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,22 @@ static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpoi
dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;
dt_control_set_mouse_over_id(thumb->imgid);

// select on single or double click, whatever happens next
if(event->button == 1 && event->type == GDK_BUTTON_PRESS)
{
// Duplicate module uses that fucking thumbnail without a table...
if(thumb->table && thumb->table->mode == DT_THUMBTABLE_MODE_FILEMANAGER)
{
if(dt_modifier_is(event->state, 0))
dt_selection_select_single(darktable.selection, thumb->imgid);
else if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
dt_selection_toggle(darktable.selection, thumb->imgid);
else if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
dt_selection_select_range(darktable.selection, thumb->imgid);
}
}

// raise signal on double click
if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
{
DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_VIEWMANAGER_THUMBTABLE_ACTIVATE, thumb->imgid);
Expand All @@ -710,17 +726,6 @@ static gboolean _event_main_press(GtkWidget *widget, GdkEventButton *event, gpoi
}
static gboolean _event_main_release(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
{
dt_thumbnail_t *thumb = (dt_thumbnail_t *)user_data;

if(event->button == 1 && !thumb->moved)
{
if(dt_modifier_is(event->state, 0))
dt_selection_select_single(darktable.selection, thumb->imgid);
else if(dt_modifier_is(event->state, GDK_CONTROL_MASK))
dt_selection_toggle(darktable.selection, thumb->imgid);
else if(dt_modifier_is(event->state, GDK_SHIFT_MASK))
dt_selection_select_range(darktable.selection, thumb->imgid);
}
return FALSE;
}

Expand Down Expand Up @@ -1233,7 +1238,7 @@ GtkWidget *dt_thumbnail_create_widget(dt_thumbnail_t *thumb, float zoom_ratio)
}

dt_thumbnail_t *dt_thumbnail_new(int width, int height, float zoom_ratio, int imgid, int rowid,
dt_thumbnail_overlay_t over)
dt_thumbnail_overlay_t over, dt_thumbtable_t *table)
{
dt_thumbnail_t *thumb = calloc(1, sizeof(dt_thumbnail_t));
thumb->width = width;
Expand All @@ -1243,6 +1248,7 @@ dt_thumbnail_t *dt_thumbnail_new(int width, int height, float zoom_ratio, int im
thumb->over = over;
thumb->zoom = 1.0f;
thumb->expose_again_timeout_id = 0;
thumb->table = table;

// we read and cache all the infos from dt_image_t that we need
const dt_image_t *img = dt_image_cache_get(darktable.image_cache, thumb->imgid, 'r');
Expand Down
6 changes: 5 additions & 1 deletion src/dtgtk/thumbnail.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#define MAX_STARS 5
#define IMG_TO_FIT 0.0f

struct dt_thumbtable_t;

typedef enum dt_thumbnail_border_t
{
DT_THUMBNAIL_BORDER_NONE = 0,
Expand Down Expand Up @@ -113,10 +115,12 @@ typedef struct

gboolean display_focus; // do we display rectangles to show focused part of the image

struct dt_thumbtable_t *table; // convenience reference to the parent

gboolean busy; // should we show the busy message ?
} dt_thumbnail_t;

dt_thumbnail_t *dt_thumbnail_new(int width, int height, float zoom_ratio, int imgid, int rowid, dt_thumbnail_overlay_t over);
dt_thumbnail_t *dt_thumbnail_new(int width, int height, float zoom_ratio, int imgid, int rowid, dt_thumbnail_overlay_t over, struct dt_thumbtable_t *table);
void dt_thumbnail_destroy(dt_thumbnail_t *thumb);
GtkWidget *dt_thumbnail_create_widget(dt_thumbnail_t *thumb, float zoom_ratio);
void dt_thumbnail_resize(dt_thumbnail_t *thumb, int width, int height, gboolean force, float zoom_ratio);
Expand Down
6 changes: 3 additions & 3 deletions src/dtgtk/thumbtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ static int _thumbs_load_needed(dt_thumbtable_t *table)
{
dt_thumbnail_t *thumb = dt_thumbnail_new(
table->thumb_size, table->thumb_size, IMG_TO_FIT, sqlite3_column_int(stmt, 1),
sqlite3_column_int(stmt, 0), table->overlays);
sqlite3_column_int(stmt, 0), table->overlays, table);

thumb->x = posx;
thumb->y = posy;
Expand Down Expand Up @@ -452,7 +452,7 @@ static int _thumbs_load_needed(dt_thumbtable_t *table)
{
dt_thumbnail_t *thumb = dt_thumbnail_new
(table->thumb_size, table->thumb_size, IMG_TO_FIT, sqlite3_column_int(stmt, 1),
sqlite3_column_int(stmt, 0), table->overlays);
sqlite3_column_int(stmt, 0), table->overlays, table);

thumb->x = posx;
thumb->y = posy;
Expand Down Expand Up @@ -1543,7 +1543,7 @@ void dt_thumbtable_full_redraw(dt_thumbtable_t *table, gboolean force)
{
// we create a completely new thumb
dt_thumbnail_t *thumb
= dt_thumbnail_new(table->thumb_size, table->thumb_size, IMG_TO_FIT, nid, nrow, table->overlays);
= dt_thumbnail_new(table->thumb_size, table->thumb_size, IMG_TO_FIT, nid, nrow, table->overlays, table);
thumb->x = posx;
thumb->y = posy;
newlist = g_list_prepend(newlist, thumb);
Expand Down
Loading

0 comments on commit a3b86f5

Please sign in to comment.