668d91fef34a0c35ae10d57f92bcd6e26f4f7a83
[geeqie.git] / src / pan-view / pan-calendar.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "pan-calendar.h"
23
24 #include <cmath>
25
26 #include "misc.h"
27 #include "pan-util.h"
28 #include "pan-view.h"
29 #include "pan-view-filter.h"
30 #include "pixbuf-util.h"
31
32 #define PAN_CAL_POPUP_COLOR 220, 220, 220
33 enum {
34         PAN_CAL_POPUP_ALPHA = 255,
35         PAN_CAL_POPUP_BORDER = 1
36 };
37 #define PAN_CAL_POPUP_BORDER_COLOR 0, 0, 0
38 #define PAN_CAL_POPUP_TEXT_COLOR 0, 0, 0
39
40 enum {
41         PAN_CAL_DAY_WIDTH = 100,
42         PAN_CAL_DAY_HEIGHT = 80
43 };
44
45 #define PAN_CAL_DAY_COLOR 255, 255, 255
46 enum {
47         PAN_CAL_DAY_ALPHA = 220,
48         PAN_CAL_DAY_BORDER = 2
49 };
50 #define PAN_CAL_DAY_BORDER_COLOR 0, 0, 0
51 #define PAN_CAL_DAY_TEXT_COLOR 0, 0, 0
52
53 #define PAN_CAL_MONTH_COLOR 255, 255, 255
54 enum {
55         PAN_CAL_MONTH_ALPHA = 200,
56         PAN_CAL_MONTH_BORDER = 4
57 };
58 #define PAN_CAL_MONTH_BORDER_COLOR 0, 0, 0
59 #define PAN_CAL_MONTH_TEXT_COLOR 0, 0, 0
60
61 enum {
62         PAN_CAL_DOT_SIZE = 3,
63         PAN_CAL_DOT_GAP = 2
64 };
65 #define PAN_CAL_DOT_COLOR 128, 128, 128
66 enum {
67         PAN_CAL_DOT_ALPHA = 128
68 };
69
70 #define PAN_CAL_DAY_OF_WEEK_COLOR 128, 128, 128
71
72 /*
73  *-----------------------------------------------------------------------------
74  * calendar
75  *-----------------------------------------------------------------------------
76  */
77
78 void pan_calendar_update(PanWindow *pw, PanItem *pi_day)
79 {
80         PanItem *pbox;
81         PanItem *pi;
82         GList *list;
83         GList *work;
84         gint x1, y1, x2, y2, x3, y3;
85         gint x, y, w, h;
86         gint grid;
87         gint column;
88
89         while ((pi = pan_item_find_by_key(pw, PAN_ITEM_NONE, "day_bubble"))) pan_item_remove(pw, pi);
90
91         if (!pi_day || pi_day->type != PAN_ITEM_BOX ||
92             !pi_day->key || strcmp(pi_day->key, "day") != 0) return;
93
94         list = pan_layout_intersect(pw, pi_day->x, pi_day->y, pi_day->width, pi_day->height);
95
96         work = list;
97         while (work)
98                 {
99                 PanItem *dot;
100                 GList *node;
101
102                 dot = static_cast<PanItem *>(work->data);
103                 node = work;
104                 work = work->next;
105
106                 if (dot->type != PAN_ITEM_BOX || !dot->fd ||
107                     !dot->key || strcmp(dot->key, "dot") != 0)
108                         {
109                         list = g_list_delete_link(list, node);
110                         }
111                 }
112
113         grid = static_cast<gint>(sqrt(g_list_length(list)) + 0.5);
114
115         x = pi_day->x + pi_day->width + 4;
116         y = pi_day->y;
117
118         pbox = pan_item_box_new(pw, nullptr, x, y, PAN_BOX_BORDER, PAN_BOX_BORDER,
119                                 PAN_CAL_POPUP_BORDER,
120                                 PAN_CAL_POPUP_COLOR, PAN_CAL_POPUP_ALPHA,
121                                 PAN_CAL_POPUP_BORDER_COLOR, PAN_CAL_POPUP_ALPHA);
122         pan_item_set_key(pbox, "day_bubble");
123
124         if (pi_day->fd)
125                 {
126                 PanItem *plabel;
127                 gchar *buf;
128
129                 buf = pan_date_value_string(pi_day->fd->date, PAN_DATE_LENGTH_WEEK);
130                 plabel = pan_item_text_new(pw, x, y, buf, static_cast<PanTextAttrType>(PAN_TEXT_ATTR_BOLD | PAN_TEXT_ATTR_HEADING),
131                                            PAN_BORDER_3,
132                                            PAN_CAL_POPUP_TEXT_COLOR, 255);
133                 pan_item_set_key(plabel, "day_bubble");
134                 g_free(buf);
135
136                 pan_item_size_by_item(pbox, plabel, 0);
137
138                 y += plabel->height;
139                 }
140
141         if (list)
142                 {
143                 column = 0;
144
145                 x += PAN_BOX_BORDER;
146                 y += PAN_BOX_BORDER;
147
148                 work = list;
149                 while (work)
150                         {
151                         PanItem *dot;
152
153                         dot = static_cast<PanItem *>(work->data);
154                         work = work->next;
155
156                         if (dot->fd)
157                                 {
158                                 PanItem *pimg;
159
160                                 pimg = pan_item_thumb_new(pw, file_data_ref(dot->fd), x, y);
161                                 pan_item_set_key(pimg, "day_bubble");
162
163                                 pan_item_size_by_item(pbox, pimg, PAN_BOX_BORDER);
164
165                                 column++;
166                                 if (column < grid)
167                                         {
168                                         x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
169                                         }
170                                 else
171                                         {
172                                         column = 0;
173                                         x = pbox->x + PAN_BOX_BORDER;
174                                         y += PAN_THUMB_SIZE + PAN_THUMB_GAP;
175                                         }
176                                 }
177                         }
178                 }
179
180         x1 = pi_day->x + pi_day->width - 8;
181         y1 = pi_day->y + 8;
182         x2 = pbox->x + 1;
183         y2 = pbox->y + MIN(42, pbox->height);
184         x3 = pbox->x + 1;
185         y3 = MAX(pbox->y, y2 - 30);
186         util_clip_triangle(x1, y1, x2, y2, x3, y3,
187                            &x, &y, &w, &h);
188
189         pi = pan_item_tri_new(pw, nullptr, x, y, w, h,
190                               x1, y1, x2, y2, x3, y3,
191                               PAN_CAL_POPUP_COLOR, PAN_CAL_POPUP_ALPHA);
192         pan_item_tri_border(pi, PAN_BORDER_1 | PAN_BORDER_3, PAN_CAL_POPUP_BORDER_COLOR, PAN_CAL_POPUP_ALPHA);
193         pan_item_set_key(pi, "day_bubble");
194         pan_item_added(pw, pi);
195
196         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
197         pan_item_added(pw, pbox);
198
199         pan_layout_resize(pw);
200 }
201
202 void pan_calendar_compute(PanWindow *pw, FileData *dir_fd, gint *width, gint *height)
203 {
204         GList *list;
205         GList *work;
206         gint x, y;
207         time_t tc;
208         gint count;
209         gint day_max;
210         gint grid;
211         gint year = 0;
212         gint month = 0;
213         gint end_year = 0;
214         gint end_month = 0;
215         gint day_of_week;
216
217         list = pan_list_tree(dir_fd, SORT_NONE, TRUE, TRUE, pw->ignore_symlinks);
218         pan_filter_fd_list(&list, pw->filter_ui->filter_elements, pw->filter_ui->filter_classes);
219
220         if (pw->cache_list && pw->exif_date_enable)
221                 {
222                 pw->cache_list = pan_cache_sort(pw->cache_list, SORT_NAME, TRUE, TRUE);
223                 list = filelist_sort(list, SORT_NAME, TRUE, TRUE);
224                 pan_cache_sync_date(pw, list);
225                 }
226
227         pw->cache_list = pan_cache_sort(pw->cache_list, SORT_TIME, TRUE, TRUE);
228         list = filelist_sort(list, SORT_TIME, TRUE, TRUE);
229
230         day_max = 0;
231         count = 0;
232         tc = 0;
233         work = list;
234         while (work)
235                 {
236                 FileData *fd;
237
238                 fd = static_cast<FileData *>(work->data);
239                 work = work->next;
240
241                 if (!pan_date_compare(fd->date, tc, PAN_DATE_LENGTH_DAY))
242                         {
243                         count = 0;
244                         tc = fd->date;
245                         }
246                 else
247                         {
248                         count++;
249                         if (day_max < count) day_max = count;
250                         }
251                 }
252
253         DEBUG_1("biggest day contains %d images", day_max);
254
255         grid = static_cast<gint>(sqrt(static_cast<gdouble>(day_max)) + 0.5) * (PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2 + PAN_THUMB_GAP);
256
257         if (list)
258                 {
259                 auto fd = static_cast<FileData *>(list->data);
260
261                 year = pan_date_value(fd->date, PAN_DATE_LENGTH_YEAR);
262                 month = pan_date_value(fd->date, PAN_DATE_LENGTH_MONTH);
263                 }
264
265         work = g_list_last(list);
266         if (work)
267                 {
268                 auto fd = static_cast<FileData *>(work->data);
269                 end_year = pan_date_value(fd->date, PAN_DATE_LENGTH_YEAR);
270                 end_month = pan_date_value(fd->date, PAN_DATE_LENGTH_MONTH);
271                 }
272
273         *width = PAN_BOX_BORDER * 2;
274         *height = PAN_BOX_BORDER * 2;
275
276         x = PAN_BOX_BORDER;
277         y = PAN_BOX_BORDER;
278
279         work = list;
280         while (work && (year < end_year || (year == end_year && month <= end_month)))
281                 {
282                 PanItem *pi_month;
283                 PanItem *pi_text;
284                 PanItem *pi_day_number;
285                 gint day;
286                 gint days;
287                 gint col;
288                 gint row;
289                 time_t dt;
290                 gchar *buf;
291
292                 /* figure last second of this month */
293                 dt = pan_date_to_time((month == 12) ? year + 1 : year, (month == 12) ? 1 : month + 1, 1);
294                 dt -= 60 * 60 * 24;
295
296                 /* anything to show this month? */
297                 if (!pan_date_compare((static_cast<FileData *>(work->data))->date, dt, PAN_DATE_LENGTH_MONTH))
298                         {
299                         month ++;
300                         if (month > 12)
301                                 {
302                                 year++;
303                                 month = 1;
304                                 }
305                         continue;
306                         }
307
308                 days = pan_date_value(dt, PAN_DATE_LENGTH_DAY);
309                 dt = pan_date_to_time(year, month, 1);
310                 col = pan_date_value(dt, PAN_DATE_LENGTH_WEEK);
311                 col = col - (date_get_first_day_of_week() - 1);
312                 if (col < 0) col = col + 7;
313                 row = 1;
314
315                 x = PAN_BOX_BORDER;
316
317                 pi_month = pan_item_box_new(pw, nullptr, x, y, PAN_CAL_DAY_WIDTH * 7, PAN_CAL_DAY_HEIGHT / 4,
318                                             PAN_CAL_MONTH_BORDER,
319                                             PAN_CAL_MONTH_COLOR, PAN_CAL_MONTH_ALPHA,
320                                             PAN_CAL_MONTH_BORDER_COLOR, PAN_CAL_MONTH_ALPHA);
321                 buf = pan_date_value_string(dt, PAN_DATE_LENGTH_MONTH);
322                 pi_text = pan_item_text_new(pw, x, y, buf,
323                                             static_cast<PanTextAttrType>(PAN_TEXT_ATTR_BOLD | PAN_TEXT_ATTR_HEADING),
324                                             PAN_BORDER_3,
325                                             PAN_CAL_MONTH_TEXT_COLOR, 255);
326                 g_free(buf);
327                 pi_text->x = pi_month->x + (pi_month->width - pi_text->width) / 2;
328
329                 pi_month->height = pi_text->y + pi_text->height - pi_month->y;
330
331                 x = PAN_BOX_BORDER + col * PAN_CAL_DAY_WIDTH;
332                 y = pi_month->y + pi_month->height + PAN_BOX_BORDER;
333
334                 for (day = 1; day <= days; day++)
335                         {
336                         FileData *fd;
337                         PanItem *pi_day;
338                         gint dx, dy;
339                         gint n = 0;
340                         gchar fake_path[20];
341
342                         dt = pan_date_to_time(year, month, day);
343
344                         /*
345                          * Create a FileData entry that represents the given day.
346                          * It does not correspond to any real file
347                          */
348
349                         g_snprintf(fake_path, sizeof(fake_path), "//%04d-%02d-%02d", year, month, day);
350                         fd = file_data_new_no_grouping(fake_path);
351                         fd->date = dt;
352                         pi_day = pan_item_box_new(pw, fd, x, y, PAN_CAL_DAY_WIDTH, PAN_CAL_DAY_HEIGHT,
353                                                   PAN_CAL_DAY_BORDER,
354                                                   PAN_CAL_DAY_COLOR, PAN_CAL_DAY_ALPHA,
355                                                   PAN_CAL_DAY_BORDER_COLOR, PAN_CAL_DAY_ALPHA);
356                         pan_item_set_key(pi_day, "day");
357
358                         dx = x + PAN_CAL_DOT_GAP * 2;
359                         dy = y + PAN_CAL_DOT_GAP * 2;
360
361                         fd = static_cast<FileData *>((work) ? work->data : nullptr);
362                         while (fd && pan_date_compare(fd->date, dt, PAN_DATE_LENGTH_DAY))
363                                 {
364                                 PanItem *pi;
365
366                                 pi = pan_item_box_new(pw, fd, dx, dy, PAN_CAL_DOT_SIZE, PAN_CAL_DOT_SIZE,
367                                                       0,
368                                                       PAN_CAL_DOT_COLOR, PAN_CAL_DOT_ALPHA,
369                                                       0, 0, 0, 0);
370                                 pan_item_set_key(pi, "dot");
371
372                                 dx += PAN_CAL_DOT_SIZE + PAN_CAL_DOT_GAP;
373                                 if (dx + PAN_CAL_DOT_SIZE > pi_day->x + pi_day->width - PAN_CAL_DOT_GAP * 2)
374                                         {
375                                         dx = x + PAN_CAL_DOT_GAP * 2;
376                                         dy += PAN_CAL_DOT_SIZE + PAN_CAL_DOT_GAP;
377                                         }
378                                 if (dy + PAN_CAL_DOT_SIZE > pi_day->y + pi_day->height - PAN_CAL_DOT_GAP * 2)
379                                         {
380                                         /* must keep all dots within respective day even if it gets ugly */
381                                         dy = y + PAN_CAL_DOT_GAP * 2;
382                                         }
383
384                                 n++;
385
386                                 work = work->next;
387                                 fd = static_cast<FileData *>((work) ? work->data : nullptr);
388                                 }
389
390                         if (n > 0)
391                                 {
392                                 PanItem *pi;
393
394                                 pi_day->color_r = MAX(pi_day->color_r - 61 - n * 3, 80);
395                                 pi_day->color_g = pi_day->color_r;
396
397                                 buf = g_strdup_printf("( %d )", n);
398                                 pi = pan_item_text_new(pw, x, y, buf, PAN_TEXT_ATTR_NONE,
399                                                        PAN_BORDER_3,
400                                                        PAN_CAL_DAY_TEXT_COLOR, 255);
401                                 g_free(buf);
402
403                                 pi->x = pi_day->x + (pi_day->width - pi->width) / 2;
404                                 pi->y = pi_day->y + (pi_day->height - pi->height) / 2;
405                                 }
406
407                         buf = g_strdup_printf("%d", day);
408                         pi_day_number = pan_item_text_new(pw, x + 4, y + 4, buf, static_cast<PanTextAttrType>(PAN_TEXT_ATTR_BOLD | PAN_TEXT_ATTR_HEADING),
409                                           PAN_BORDER_3,
410                                           PAN_CAL_DAY_TEXT_COLOR, 255);
411                         g_free(buf);
412
413                         day_of_week = date_get_first_day_of_week() + col;
414                         if (day_of_week > 7) day_of_week = day_of_week - 7;
415
416                         buf = date_get_abbreviated_day_name(day_of_week);
417                         pan_item_text_new(pw, x + 4 + pi_day_number->width + 4, y + 4, buf, PAN_TEXT_ATTR_NONE,
418                                           PAN_BORDER_3,
419                                           PAN_CAL_DAY_OF_WEEK_COLOR, 255);
420                         g_free(buf);
421
422                         pan_item_size_coordinates(pi_day, PAN_BOX_BORDER, width, height);
423
424                         col++;
425                         if (col > 6)
426                                 {
427                                 col = 0;
428                                 row++;
429                                 x = PAN_BOX_BORDER;
430                                 y += PAN_CAL_DAY_HEIGHT;
431                                 }
432                         else
433                                 {
434                                 x += PAN_CAL_DAY_WIDTH;
435                                 }
436                         }
437
438                 if (col > 0) y += PAN_CAL_DAY_HEIGHT;
439                 y += PAN_BOX_BORDER * 2;
440
441                 month ++;
442                 if (month > 12)
443                         {
444                         year++;
445                         month = 1;
446                         }
447                 }
448
449         *width += grid;
450         *height = MAX(*height, grid + PAN_BOX_BORDER * 2 * 2);
451
452         g_list_free(list);
453 }
454 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */