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