Skip to content

Commit 9e75167

Browse files
committed
Add capture month to collection and collection filters
This commit adds a new field to the collection structure to capture the month of photo capture. It also updates the collection filters to allow filtering based on the capture month. This will enable users to better organize and analyze their photos based on the time of capture.
1 parent fbd6a7a commit 9e75167

5 files changed

Lines changed: 325 additions & 3 deletions

File tree

src/common/collection.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,8 @@ const char *dt_collection_name_untranslated(const dt_collection_properties_t pro
618618
return N_("tag");
619619
case DT_COLLECTION_PROP_DAY:
620620
return N_("capture date");
621+
case DT_COLLECTION_PROP_MONTH:
622+
return N_("capture month");
621623
case DT_COLLECTION_PROP_TIME:
622624
return N_("capture time");
623625
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
@@ -2101,6 +2103,73 @@ static gchar *get_query_string(const dt_collection_properties_t property, const
21012103

21022104
break;
21032105
}
2106+
case DT_COLLECTION_PROP_MONTH:
2107+
{
2108+
if(g_str_has_prefix(escaped_text, "0x"))
2109+
{
2110+
// bitmask format from the filtering panel toggle buttons
2111+
const int mask = (int)strtoll(&escaped_text[2], NULL, 16);
2112+
if(mask == 0 || mask == 0xFFF)
2113+
{
2114+
query = g_strdup("1=1");
2115+
}
2116+
else
2117+
{
2118+
// build IN(...) list from bitmask
2119+
GString *months = g_string_new(NULL);
2120+
for(int i = 0; i < 12; i++)
2121+
{
2122+
if(mask & (1 << i))
2123+
{
2124+
if(months->len > 0)
2125+
g_string_append(months, ", ");
2126+
g_string_append_printf(months, "%d", i + 1);
2127+
}
2128+
}
2129+
// clang-format off
2130+
query = g_strdup_printf(
2131+
"(CAST(strftime('%%m', datetime_taken / 86400000000.0 + julianday('0001-01-01'))"
2132+
" AS INTEGER) IN (%s))",
2133+
months->str);
2134+
// clang-format on
2135+
g_string_free(months, TRUE);
2136+
}
2137+
}
2138+
else
2139+
{
2140+
// plain text format from the collections panel (e.g. "12" or "3")
2141+
// parse as a comma-separated list of month numbers or a single month
2142+
GString *months = g_string_new(NULL);
2143+
gchar **parts = g_strsplit(escaped_text, ",", -1);
2144+
for(int i = 0; parts[i]; i++)
2145+
{
2146+
g_strstrip(parts[i]);
2147+
const int m = atoi(parts[i]);
2148+
if(m >= 1 && m <= 12)
2149+
{
2150+
if(months->len > 0)
2151+
g_string_append(months, ", ");
2152+
g_string_append_printf(months, "%d", m);
2153+
}
2154+
}
2155+
g_strfreev(parts);
2156+
2157+
if(months->len > 0)
2158+
{
2159+
// clang-format off
2160+
query = g_strdup_printf(
2161+
"(CAST(strftime('%%m', datetime_taken / 86400000000.0 + julianday('0001-01-01'))"
2162+
" AS INTEGER) IN (%s))",
2163+
months->str);
2164+
// clang-format on
2165+
}
2166+
else
2167+
query = g_strdup("1=1");
2168+
2169+
g_string_free(months, TRUE);
2170+
}
2171+
break;
2172+
}
21042173
case DT_COLLECTION_PROP_DAY:
21052174
case DT_COLLECTION_PROP_TIME:
21062175
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
@@ -2114,6 +2183,7 @@ static gchar *get_query_string(const dt_collection_properties_t property, const
21142183
switch(local_property)
21152184
{
21162185
case DT_COLLECTION_PROP_DAY: colname = "datetime_taken" ; break ;
2186+
// WHERE strftime('%m', date_time_column) = '08'
21172187
case DT_COLLECTION_PROP_TIME: colname = "datetime_taken" ; break ;
21182188
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP: colname = "import_timestamp" ; break ;
21192189
case DT_COLLECTION_PROP_CHANGE_TIMESTAMP: colname = "change_timestamp" ; break ;

src/common/collection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ typedef enum dt_collection_properties_t
8585
DT_COLLECTION_PROP_ISO,
8686

8787
DT_COLLECTION_PROP_DAY,
88+
DT_COLLECTION_PROP_MONTH,
8889
DT_COLLECTION_PROP_TIME,
8990
DT_COLLECTION_PROP_IMPORT_TIMESTAMP,
9091
DT_COLLECTION_PROP_CHANGE_TIMESTAMP,

src/libs/collect.c

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,64 @@ void *legacy_params(struct dt_lib_module_t *self,
289289

290290
return (void *)new;
291291
}
292+
else if(old_version == 3)
293+
{
294+
/* from v3 to v4 we have added 1 new timestamp filter */
295+
dt_lib_collect_params_t *old = (dt_lib_collect_params_t *)old_params;
296+
297+
if(old->rules > MAX_RULES)
298+
/* preset is corrupted, return NULL and drop the preset */
299+
return NULL;
300+
301+
dt_lib_collect_params_t *new = malloc(old_params_size);
302+
303+
const int table[DT_COLLECTION_PROP_LAST] =
304+
{
305+
DT_COLLECTION_PROP_FILMROLL,
306+
DT_COLLECTION_PROP_FOLDERS,
307+
DT_COLLECTION_PROP_FILENAME,
308+
DT_COLLECTION_PROP_CAMERA,
309+
DT_COLLECTION_PROP_LENS,
310+
DT_COLLECTION_PROP_APERTURE,
311+
DT_COLLECTION_PROP_EXPOSURE,
312+
DT_COLLECTION_PROP_FOCAL_LENGTH,
313+
DT_COLLECTION_PROP_ISO,
314+
DT_COLLECTION_PROP_DAY,
315+
DT_COLLECTION_PROP_MONTH,
316+
DT_COLLECTION_PROP_TIME,
317+
DT_COLLECTION_PROP_GEOTAGGING,
318+
DT_COLLECTION_PROP_ASPECT_RATIO,
319+
DT_COLLECTION_PROP_TAG,
320+
DT_COLLECTION_PROP_COLORLABEL,
321+
322+
// spaces for the metadata, see metadata.h
323+
DT_COLLECTION_PROP_COLORLABEL + 1,
324+
DT_COLLECTION_PROP_COLORLABEL + 2,
325+
DT_COLLECTION_PROP_COLORLABEL + 3,
326+
DT_COLLECTION_PROP_COLORLABEL + 4,
327+
DT_COLLECTION_PROP_COLORLABEL + 5,
328+
329+
DT_COLLECTION_PROP_GROUPING,
330+
DT_COLLECTION_PROP_LOCAL_COPY,
331+
DT_COLLECTION_PROP_HISTORY,
332+
DT_COLLECTION_PROP_MODULE,
333+
DT_COLLECTION_PROP_ORDER
334+
};
335+
336+
new->rules = old->rules;
337+
338+
for(int r = 0; r < old->rules; r++)
339+
{
340+
new->rule[r].item = table[old->rule[r].item];
341+
new->rule[r].mode = old->rule[r].mode;
342+
memcpy(new->rule[r].string, old->rule[r].string, PARAM_STRING_SIZE);
343+
}
344+
345+
*new_size = old_params_size;
346+
*new_version = 4;
347+
348+
return (void *)new;
349+
}
292350

293351
return NULL;
294352
}
@@ -627,7 +685,9 @@ static gboolean view_onButtonPressed(GtkWidget *treeview,
627685
GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
628686
if(get_path && dt_modifier_is(event->state, GDK_SHIFT_MASK)
629687
&& gtk_tree_selection_count_selected_rows(selection) > 0
630-
&& (d->view_rule == DT_COLLECTION_PROP_DAY || _is_time_property(d->view_rule)
688+
&& (d->view_rule == DT_COLLECTION_PROP_DAY
689+
|| d->view_rule == DT_COLLECTION_PROP_MONTH
690+
|| _is_time_property(d->view_rule)
631691
|| d->view_rule == DT_COLLECTION_PROP_APERTURE
632692
|| d->view_rule == DT_COLLECTION_PROP_FOCAL_LENGTH
633693
|| d->view_rule == DT_COLLECTION_PROP_ISO
@@ -1364,6 +1424,7 @@ static void _tree_view(dt_lib_collect_rule_t *dr)
13641424
format_separator = "%s|";
13651425
break;
13661426
case DT_COLLECTION_PROP_DAY:
1427+
case DT_COLLECTION_PROP_MONTH:
13671428
case DT_COLLECTION_PROP_TIME:
13681429
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
13691430
case DT_COLLECTION_PROP_CHANGE_TIMESTAMP:
@@ -1502,6 +1563,20 @@ static void _tree_view(dt_lib_collect_rule_t *dr)
15021563
// clang-format on
15031564
break;
15041565

1566+
case DT_COLLECTION_PROP_MONTH:
1567+
// clang-format off
1568+
query = g_strdup_printf
1569+
("SELECT CAST(strftime('%%m', datetime_taken / 86400000000.0"
1570+
" + julianday('0001-01-01')) AS INTEGER) AS month_num,"
1571+
" 1, COUNT(*) AS count"
1572+
" FROM main.images AS mi"
1573+
" WHERE datetime_taken IS NOT NULL AND datetime_taken <> 0"
1574+
" AND %s"
1575+
" GROUP BY month_num"
1576+
" ORDER BY month_num", where_ext);
1577+
// clang-format on
1578+
break;
1579+
15051580
case DT_COLLECTION_PROP_TIME:
15061581
case DT_COLLECTION_PROP_IMPORT_TIMESTAMP:
15071582
case DT_COLLECTION_PROP_CHANGE_TIMESTAMP:
@@ -1563,6 +1638,18 @@ static void _tree_view(dt_lib_collect_rule_t *dr)
15631638
sdt[10] = '\0';
15641639
name = g_strdup(sdt);
15651640
}
1641+
else if(property == DT_COLLECTION_PROP_MONTH)
1642+
{
1643+
const int month_num = sqlite3_column_int(stmt, 0);
1644+
const char *month_names[] = { N_("January"), N_("February"), N_("March"),
1645+
N_("April"), N_("May"), N_("June"),
1646+
N_("July"), N_("August"), N_("September"),
1647+
N_("October"), N_("November"), N_("December") };
1648+
if(month_num >= 1 && month_num <= 12)
1649+
name = g_strdup_printf("%02d - %s", month_num, _(month_names[month_num - 1]));
1650+
else
1651+
name = g_strdup(_("unknown"));
1652+
}
15661653
else
15671654
{
15681655
const char* sqlite_name = (const char *)sqlite3_column_text(stmt, 0);
@@ -2192,6 +2279,22 @@ static void _list_view(dt_lib_collect_rule_t *dr)
21922279
}
21932280
break;
21942281

2282+
case DT_COLLECTION_PROP_MONTH: // capture month
2283+
// clang-format off
2284+
g_snprintf(query, sizeof(query),
2285+
"SELECT CAST(strftime('%%m', datetime_taken / 86400000000.0"
2286+
" + julianday('0001-01-01')) AS INTEGER) AS month_num,"
2287+
" 1, COUNT(*) AS count"
2288+
" FROM main.images AS mi"
2289+
" WHERE datetime_taken IS NOT NULL AND datetime_taken <> 0"
2290+
" AND %s"
2291+
" GROUP BY month_num"
2292+
" ORDER BY month_num %s",
2293+
where_ext,
2294+
sort_descending ? "DESC" : "ASC");
2295+
// clang-format on
2296+
break;
2297+
21952298
default:
21962299
if(property >= DT_COLLECTION_PROP_METADATA_OFFSET)
21972300
{
@@ -2304,6 +2407,18 @@ static void _list_view(dt_lib_collect_rule_t *dr)
23042407
break;
23052408
}
23062409
}
2410+
else if(property == DT_COLLECTION_PROP_MONTH)
2411+
{
2412+
const int month_num = sqlite3_column_int(stmt, 0);
2413+
const char *month_names[] = { N_("January"), N_("February"), N_("March"),
2414+
N_("April"), N_("May"), N_("June"),
2415+
N_("July"), N_("August"), N_("September"),
2416+
N_("October"), N_("November"), N_("December") };
2417+
if(month_num >= 1 && month_num <= 12)
2418+
folder = _(month_names[month_num - 1]);
2419+
else
2420+
folder = _("unknown");
2421+
}
23072422

23082423
// check if name is empty string
23092424

@@ -3343,6 +3458,7 @@ static void _populate_collect_combo(GtkWidget *w)
33433458

33443459
dt_bauhaus_combobox_add_section(w, _("times"));
33453460
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_DAY);
3461+
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_MONTH);
33463462
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_TIME);
33473463
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_IMPORT_TIMESTAMP);
33483464
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_CHANGE_TIMESTAMP);

src/libs/filtering.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ static _filter_t filters[]
227227
{ DT_COLLECTION_PROP_FILENAME, _filename_widget_init, _filename_update },
228228
{ DT_COLLECTION_PROP_TEXTSEARCH, _search_widget_init, _search_update },
229229
{ DT_COLLECTION_PROP_DAY, _date_widget_init, _date_update },
230+
{ DT_COLLECTION_PROP_MONTH, _month_widget_init, _month_update },
230231
{ DT_COLLECTION_PROP_CHANGE_TIMESTAMP, _date_widget_init, _date_update },
231232
{ DT_COLLECTION_PROP_EXPORT_TIMESTAMP, _date_widget_init, _date_update },
232233
{ DT_COLLECTION_PROP_TIME, _date_widget_init, _date_update },
@@ -900,6 +901,7 @@ static gboolean _rule_show_popup(GtkWidget *widget, dt_lib_filtering_rule_t *rul
900901

901902
_popup_add_item(spop, _("times"), 0, TRUE, NULL, NULL, self, 0.0);
902903
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_DAY);
904+
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_MONTH);
903905
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_TIME);
904906
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_IMPORT_TIMESTAMP);
905907
ADD_COLLECT_ENTRY(spop, DT_COLLECTION_PROP_CHANGE_TIMESTAMP);
@@ -960,6 +962,7 @@ static void _populate_rules_combo(GtkWidget *w)
960962

961963
dt_bauhaus_combobox_add_section(w, _("times"));
962964
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_DAY);
965+
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_MONTH);
963966
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_TIME);
964967
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_IMPORT_TIMESTAMP);
965968
ADD_COLLECT_ENTRY(DT_COLLECTION_PROP_CHANGE_TIMESTAMP);
@@ -1215,7 +1218,7 @@ static gboolean _event_rule_close(GtkWidget *widget, GdkEventButton *event, dt_l
12151218
static gboolean _rule_available_for_topbar(const dt_collection_properties_t prop)
12161219
{
12171220
// we don't want to allow date filters for topbar as the design of the bar is not useful as it
1218-
if(prop == DT_COLLECTION_PROP_DAY || prop == DT_COLLECTION_PROP_TIME
1221+
if(prop == DT_COLLECTION_PROP_DAY || prop == DT_COLLECTION_PROP_MONTH || prop == DT_COLLECTION_PROP_TIME
12191222
|| prop == DT_COLLECTION_PROP_CHANGE_TIMESTAMP || prop == DT_COLLECTION_PROP_EXPORT_TIMESTAMP
12201223
|| prop == DT_COLLECTION_PROP_PRINT_TIMESTAMP || prop == DT_COLLECTION_PROP_IMPORT_TIMESTAMP)
12211224
return FALSE;
@@ -1511,6 +1514,8 @@ static void _history_pretty_print(const char *buf, char *out, size_t outsize)
15111514
gchar *pretty = NULL;
15121515
if(item == DT_COLLECTION_PROP_COLORLABEL)
15131516
pretty = _colors_pretty_print(str);
1517+
else if(item == DT_COLLECTION_PROP_MONTH)
1518+
pretty = _month_pretty_print(str);
15141519
else if(!g_strcmp0(str, "%"))
15151520
pretty = g_strdup(_("all"));
15161521
else

0 commit comments

Comments
 (0)