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