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