Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ changes (where available).
- For non-raw images with gamma corrected data we do the initial
scaling in linear mode for less artifacts.

- Added a new option to filter images by capture month in collections
and collection filters.

## Bug Fixes

- Properly apply the iop-order when applying a style at export
Expand Down
70 changes: 70 additions & 0 deletions src/common/collection.c
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,8 @@ const char *dt_collection_name_untranslated(const dt_collection_properties_t pro
return N_("tag");
case DT_COLLECTION_PROP_DAY:
return N_("capture date");
case DT_COLLECTION_PROP_MONTH:
return N_("capture month");
case DT_COLLECTION_PROP_TIME:
return N_("capture time");
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
Expand Down Expand Up @@ -2101,6 +2103,73 @@ static gchar *get_query_string(const dt_collection_properties_t property, const

break;
}
case DT_COLLECTION_PROP_MONTH:
{
if(g_str_has_prefix(escaped_text, "0x"))
{
// bitmask format from the filtering panel toggle buttons
const int mask = (int)strtoll(&escaped_text[2], NULL, 16);
if(mask == 0 || mask == 0xFFF)
{
query = g_strdup("1=1");
}
else
{
// build IN(...) list from bitmask
GString *months = g_string_new(NULL);
for(int i = 0; i < 12; i++)
{
if(mask & (1 << i))
{
if(months->len > 0)
g_string_append(months, ", ");
g_string_append_printf(months, "%d", i + 1);
}
}
// clang-format off
query = g_strdup_printf(
"(CAST(strftime('%%m', datetime_taken / 86400000000.0 + julianday('0001-01-01'))"
" AS INTEGER) IN (%s))",
months->str);
// clang-format on
g_string_free(months, TRUE);
}
}
else
{
// plain text format from the collections panel (e.g. "12" or "3")
// parse as a comma-separated list of month numbers or a single month
GString *months = g_string_new(NULL);
gchar **parts = g_strsplit(escaped_text, ",", -1);
for(int i = 0; parts[i]; i++)
{
g_strstrip(parts[i]);
const int m = atoi(parts[i]);
if(m >= 1 && m <= 12)
{
if(months->len > 0)
g_string_append(months, ", ");
g_string_append_printf(months, "%d", m);
}
}
g_strfreev(parts);

if(months->len > 0)
{
// clang-format off
query = g_strdup_printf(
"(CAST(strftime('%%m', datetime_taken / 86400000000.0 + julianday('0001-01-01'))"
" AS INTEGER) IN (%s))",
months->str);
// clang-format on
}
else
query = g_strdup("1=1");

g_string_free(months, TRUE);
}
break;
}
case DT_COLLECTION_PROP_DAY:
case DT_COLLECTION_PROP_TIME:
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
Expand All @@ -2114,6 +2183,7 @@ static gchar *get_query_string(const dt_collection_properties_t property, const
switch(local_property)
{
case DT_COLLECTION_PROP_DAY: colname = "datetime_taken" ; break ;
// WHERE strftime('%m', date_time_column) = '08'
case DT_COLLECTION_PROP_TIME: colname = "datetime_taken" ; break ;
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP: colname = "import_timestamp" ; break ;
case DT_COLLECTION_PROP_CHANGE_TIMESTAMP: colname = "change_timestamp" ; break ;
Expand Down
2 changes: 2 additions & 0 deletions src/common/collection.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ typedef enum dt_collection_properties_t

DT_COLLECTION_PROP_DUPLICATES,

DT_COLLECTION_PROP_MONTH,

// all new collection types need to be added before DT_COLLECTION_PROP_LAST,
// which separates actual collection types from special flag values
DT_COLLECTION_PROP_LAST,
Expand Down
57 changes: 55 additions & 2 deletions src/libs/collect.c
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,9 @@ static gboolean view_onButtonPressed(GtkWidget *treeview,
GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
if(get_path && dt_modifier_is(event->state, GDK_SHIFT_MASK)
&& gtk_tree_selection_count_selected_rows(selection) > 0
&& (d->view_rule == DT_COLLECTION_PROP_DAY || _is_time_property(d->view_rule)
&& (d->view_rule == DT_COLLECTION_PROP_DAY
|| d->view_rule == DT_COLLECTION_PROP_MONTH
|| _is_time_property(d->view_rule)
|| d->view_rule == DT_COLLECTION_PROP_APERTURE
|| d->view_rule == DT_COLLECTION_PROP_FOCAL_LENGTH
|| d->view_rule == DT_COLLECTION_PROP_ISO
Expand Down Expand Up @@ -676,7 +678,9 @@ static gboolean view_onButtonPressed(GtkWidget *treeview,
|| (d->singleclick && event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY)
|| (!d->singleclick && event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY
&& (dt_modifier_is(event->state, GDK_SHIFT_MASK)
|| dt_modifier_is(event->state, GDK_CONTROL_MASK))))
|| dt_modifier_is(event->state, GDK_CONTROL_MASK)))
|| (d->view_rule == DT_COLLECTION_PROP_MONTH
&& event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY))
{
row_activated_with_event(GTK_TREE_VIEW(treeview), path, NULL, event, d);

Expand Down Expand Up @@ -1364,6 +1368,7 @@ static void _tree_view(dt_lib_collect_rule_t *dr)
format_separator = "%s|";
break;
case DT_COLLECTION_PROP_DAY:
case DT_COLLECTION_PROP_MONTH:
case DT_COLLECTION_PROP_TIME:
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
case DT_COLLECTION_PROP_CHANGE_TIMESTAMP:
Expand Down Expand Up @@ -1502,6 +1507,20 @@ static void _tree_view(dt_lib_collect_rule_t *dr)
// clang-format on
break;

case DT_COLLECTION_PROP_MONTH:
// clang-format off
query = g_strdup_printf
("SELECT CAST(strftime('%%m', datetime_taken / 86400000000.0"
" + julianday('0001-01-01')) AS INTEGER) AS month_num,"
" 1, COUNT(*) AS count"
" FROM main.images AS mi"
" WHERE datetime_taken IS NOT NULL AND datetime_taken <> 0"
" AND %s"
" GROUP BY month_num"
" ORDER BY month_num", where_ext);
// clang-format on
break;

case DT_COLLECTION_PROP_TIME:
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
case DT_COLLECTION_PROP_CHANGE_TIMESTAMP:
Expand Down Expand Up @@ -1563,6 +1582,14 @@ static void _tree_view(dt_lib_collect_rule_t *dr)
sdt[10] = '\0';
name = g_strdup(sdt);
}
else if(property == DT_COLLECTION_PROP_MONTH)
{
const int month_num = sqlite3_column_int(stmt, 0);
if(month_num >= 1 && month_num <= 12)
name = g_strdup_printf("%02d - %s", month_num, _(dt_month_names[month_num - 1]));
else
name = g_strdup(_("unknown"));
}
else
{
const char* sqlite_name = (const char *)sqlite3_column_text(stmt, 0);
Expand Down Expand Up @@ -2192,6 +2219,22 @@ static void _list_view(dt_lib_collect_rule_t *dr)
}
break;

case DT_COLLECTION_PROP_MONTH: // capture month
// clang-format off
g_snprintf(query, sizeof(query),
"SELECT CAST(strftime('%%m', datetime_taken / 86400000000.0"
" + julianday('0001-01-01')) AS INTEGER) AS month_num,"
" 1, COUNT(*) AS count"
" FROM main.images AS mi"
" WHERE datetime_taken IS NOT NULL AND datetime_taken <> 0"
" AND %s"
" GROUP BY month_num"
" ORDER BY month_num %s",
where_ext,
sort_descending ? "DESC" : "ASC");
// clang-format on
break;

default:
if(property >= DT_COLLECTION_PROP_METADATA_OFFSET)
{
Expand Down Expand Up @@ -2304,6 +2347,14 @@ static void _list_view(dt_lib_collect_rule_t *dr)
break;
}
}
else if(property == DT_COLLECTION_PROP_MONTH)
{
const int month_num = sqlite3_column_int(stmt, 0);
if(month_num >= 1 && month_num <= 12)
folder = _(dt_month_names[month_num - 1]);
else
folder = _("unknown");
}

// check if name is empty string

Expand Down Expand Up @@ -3343,6 +3394,7 @@ static void _populate_collect_combo(GtkWidget *w)

dt_bauhaus_combobox_add_section(w, _("times"));
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_DAY);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_MONTH);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_TIME);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_IMPORT_TIMESTAMP);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_CHANGE_TIMESTAMP);
Expand Down Expand Up @@ -4011,6 +4063,7 @@ void init(struct dt_lib_module_t *self)
luaA_enum_value(L, dt_collection_properties_t, DT_COLLECTION_PROP_LOCAL_COPY);
luaA_enum_value(L, dt_collection_properties_t, DT_COLLECTION_PROP_MODULE);
luaA_enum_value(L, dt_collection_properties_t, DT_COLLECTION_PROP_ORDER);
luaA_enum_value(L, dt_collection_properties_t, DT_COLLECTION_PROP_MONTH);
}
#endif
#undef MAX_RULES
Expand Down
35 changes: 34 additions & 1 deletion src/libs/collect.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,49 @@

#pragma once

#include <glib/gi18n.h>

typedef enum dt_lib_collect_mode_t
{
DT_LIB_COLLECT_MODE_AND = 0,
DT_LIB_COLLECT_MODE_OR,
DT_LIB_COLLECT_MODE_AND_NOT
} dt_lib_collect_mode_t;

static const char *dt_month_names[] __attribute__((unused)) =
{
N_("January"),
N_("February"),
N_("March"),
N_("April"),
N_("May"),
N_("June"),
N_("July"),
N_("August"),
N_("September"),
N_("October"),
N_("November"),
N_("December"),
};

static const char *dt_month_short_names[] __attribute__((unused)) =
{
N_("Jan"),
N_("Feb"),
N_("Mar"),
N_("Apr"),
N_("May"),
N_("Jun"),
N_("Jul"),
N_("Aug"),
N_("Sep"),
N_("Oct"),
N_("Nov"),
N_("Dec"),
};

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on

7 changes: 6 additions & 1 deletion src/libs/filtering.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ static _filter_t filters[]
{ DT_COLLECTION_PROP_FILENAME, _filename_widget_init, _filename_update },
{ DT_COLLECTION_PROP_TEXTSEARCH, _search_widget_init, _search_update },
{ DT_COLLECTION_PROP_DAY, _date_widget_init, _date_update },
{ DT_COLLECTION_PROP_MONTH, _month_widget_init, _month_update },
{ DT_COLLECTION_PROP_CHANGE_TIMESTAMP, _date_widget_init, _date_update },
{ DT_COLLECTION_PROP_EXPORT_TIMESTAMP, _date_widget_init, _date_update },
{ DT_COLLECTION_PROP_TIME, _date_widget_init, _date_update },
Expand Down Expand Up @@ -900,6 +901,7 @@ static gboolean _rule_show_popup(GtkWidget *widget, dt_lib_filtering_rule_t *rul

_popup_add_item(spop, _("times"), 0, TRUE, NULL, NULL, self, 0.0);
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_DAY);
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_MONTH);
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_TIME);
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_IMPORT_TIMESTAMP);
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_CHANGE_TIMESTAMP);
Expand Down Expand Up @@ -960,6 +962,7 @@ static void _populate_rules_combo(GtkWidget *w)

dt_bauhaus_combobox_add_section(w, _("times"));
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_DAY);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_MONTH);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_TIME);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_IMPORT_TIMESTAMP);
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_CHANGE_TIMESTAMP);
Expand Down Expand Up @@ -1215,7 +1218,7 @@ static gboolean _event_rule_close(GtkWidget *widget, GdkEventButton *event, dt_l
static gboolean _rule_available_for_topbar(const dt_collection_properties_t prop)
{
// we don't want to allow date filters for topbar as the design of the bar is not useful as it
if(prop == DT_COLLECTION_PROP_DAY || prop == DT_COLLECTION_PROP_TIME
if(prop == DT_COLLECTION_PROP_DAY || prop == DT_COLLECTION_PROP_MONTH || prop == DT_COLLECTION_PROP_TIME
|| prop == DT_COLLECTION_PROP_CHANGE_TIMESTAMP || prop == DT_COLLECTION_PROP_EXPORT_TIMESTAMP
|| prop == DT_COLLECTION_PROP_PRINT_TIMESTAMP || prop == DT_COLLECTION_PROP_IMPORT_TIMESTAMP)
return FALSE;
Expand Down Expand Up @@ -1511,6 +1514,8 @@ static void _history_pretty_print(const char *buf, char *out, size_t outsize)
gchar *pretty = NULL;
if(item == DT_COLLECTION_PROP_COLORLABEL)
pretty = _colors_pretty_print(str);
else if(item == DT_COLLECTION_PROP_MONTH)
pretty = _month_pretty_print(str);
else if(!g_strcmp0(str, "%"))
pretty = g_strdup(_("all"));
else
Expand Down
Loading
Loading