1 /*
2  * biji-notebook.c
3  *
4  * Copyright (C) Pierre-Yves LUYTEN 2013 <py@luyten.fr>
5  *
6  * bijiben is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * bijiben is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 /*
21  * in current implementation, one cannot add a notebook
22  * to a notebook
23  * but tracker would be fine with this
24  * given we prevent self-containment.
25  */
26 
27 /*
28  * biji_create_notebook_icon
29  * is adapted from Photos (photos_utils_create_notebook_icon),
30  * which is ported from Documents
31  */
32 
33 #include <math.h>
34 
35 #include "biji-notebook.h"
36 #include "biji-tracker.h"
37 
38 
39 static void biji_notebook_update_collected (GList *result, gpointer user_data);
40 
41 
42 struct _BijiNotebook
43 {
44   BijiItem         parent_instance;
45 
46   gchar           *urn;
47   gchar           *name;
48   gint64           mtime;
49 
50   cairo_surface_t *icon;
51   cairo_surface_t *emblem;
52 
53   GList           *collected_items;
54 };
55 
56 static void biji_notebook_finalize (GObject *object);
57 
58 G_DEFINE_TYPE (BijiNotebook, biji_notebook, BIJI_TYPE_ITEM)
59 
60 /* Properties */
61 enum {
62   PROP_0,
63   PROP_URN,
64   PROP_NAME,
65   PROP_MTIME,
66   BIJI_NOTEBOOK_PROPERTIES
67 };
68 
69 
70 /* Signals */
71 enum {
72   NOTEBOOK_ICON_UPDATED,
73   BIJI_NOTEBOOKS_SIGNALS
74 };
75 
76 static GParamSpec *properties[BIJI_NOTEBOOK_PROPERTIES] = { NULL, };
77 
78 static guint biji_notebooks_signals [BIJI_NOTEBOOKS_SIGNALS] = { 0 };
79 
80 
81 static const gchar *
biji_notebook_get_title(BijiItem * coll)82 biji_notebook_get_title (BijiItem *coll)
83 {
84   BijiNotebook *self;
85 
86   g_return_val_if_fail (BIJI_IS_NOTEBOOK (coll), NULL);
87   self = BIJI_NOTEBOOK (coll);
88 
89   return self->name;
90 }
91 
92 
93 static const gchar *
biji_notebook_get_uuid(BijiItem * coll)94 biji_notebook_get_uuid (BijiItem *coll)
95 {
96   BijiNotebook *self;
97 
98   g_return_val_if_fail (BIJI_IS_NOTEBOOK (coll), NULL);
99   self = BIJI_NOTEBOOK (coll);
100 
101   return self->urn;
102 }
103 
104 
105 static cairo_surface_t *
biji_create_notebook_icon(gint base_size,gint scale,GList * surfaces)106 biji_create_notebook_icon (gint base_size, gint scale, GList *surfaces)
107 {
108   cairo_surface_t *surface, *pix;
109   cairo_t *cr;
110   GList *l;
111   GtkStyleContext *context;
112   GtkWidgetPath *path;
113   gint cur_x;
114   gint cur_y;
115   gint idx;
116   gint padding;
117   gint pix_height;
118   gint pix_width;
119   gdouble pix_scale_x;
120   gdouble pix_scale_y;
121   gint scale_size;
122   gint tile_size;
123 
124   /* TODO: use notes thumbs */
125 
126   padding = MAX (floor (base_size / 10), 4);
127   tile_size = (base_size - (3 * padding)) / 2;
128 
129   context = gtk_style_context_new ();
130   gtk_style_context_add_class (context, "biji-notebook-icon");
131 
132   path = gtk_widget_path_new ();
133   gtk_widget_path_append_type (path, GTK_TYPE_ICON_VIEW);
134   gtk_style_context_set_path (context, path);
135   gtk_widget_path_unref (path);
136 
137   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, base_size * scale, base_size * scale);
138   cairo_surface_set_device_scale (surface, scale, scale);
139   cr = cairo_create (surface);
140 
141   gtk_render_background (context, cr, 0, 0, base_size, base_size);
142   gtk_render_frame (context, cr, 0, 0, base_size, base_size);
143 
144   l = surfaces;
145   idx = 0;
146   cur_x = padding;
147   cur_y = padding;
148 
149 
150   while (l != NULL && idx < 4)
151     {
152       pix = l->data;
153       cairo_surface_get_device_scale (pix, &pix_scale_x, &pix_scale_y);
154       pix_width = cairo_image_surface_get_width (pix) / (gint) pix_scale_x;
155       pix_height = cairo_image_surface_get_height (pix) / (gint) pix_scale_y;
156 
157       scale_size = MIN (pix_width, pix_height);
158 
159       cairo_save (cr);
160 
161       cairo_translate (cr, cur_x, cur_y);
162 
163       cairo_rectangle (cr, 0, 0,
164                        tile_size, tile_size);
165       cairo_clip (cr);
166 
167       cairo_scale (cr, (gdouble) tile_size / (gdouble) scale_size, (gdouble) tile_size / (gdouble) scale_size);
168       cairo_set_source_surface (cr, pix, 0, 0);
169 
170       cairo_paint (cr);
171       cairo_restore (cr);
172 
173       if ((idx % 2) == 0)
174         {
175           cur_x += tile_size + padding;
176         }
177       else
178         {
179           cur_x = padding;
180           cur_y += tile_size + padding;
181         }
182 
183       idx++;
184       l = l->next;
185     }
186 
187   cairo_destroy (cr);
188   g_object_unref (context);
189 
190   return surface;
191 }
192 
193 static GList *
get_collected_pix(BijiNotebook * self,gint scale)194 get_collected_pix (BijiNotebook *self,
195                    gint scale)
196 {
197   GList *result = NULL, *l;
198 
199   for (l = self->collected_items ; l != NULL; l = l->next)
200   {
201     if (BIJI_IS_ITEM (l->data))
202       result = g_list_prepend (
203                         result,
204                         biji_item_get_pristine (BIJI_ITEM (l->data), scale));
205   }
206 
207   return result;
208 }
209 
210 static cairo_surface_t *
biji_notebook_get_icon(BijiItem * coll,gint scale)211 biji_notebook_get_icon (BijiItem *coll,
212                         gint scale)
213 {
214   BijiNotebook *self = BIJI_NOTEBOOK (coll);
215   GList *pix;
216 
217   if (!self->icon)
218   {
219     pix = get_collected_pix (self, scale);
220     self->icon = biji_create_notebook_icon (BIJI_ICON_WIDTH, scale, pix);
221     g_list_free (pix);
222   }
223 
224   return self->icon;
225 }
226 
227 
228 static cairo_surface_t *
biji_notebook_get_emblem(BijiItem * coll,gint scale)229 biji_notebook_get_emblem (BijiItem *coll,
230                           gint scale)
231 {
232   BijiNotebook *self = BIJI_NOTEBOOK (coll);
233   GList *pix;
234 
235   if (!self->emblem)
236   {
237     pix = get_collected_pix (self, scale);
238     self->emblem = biji_create_notebook_icon (BIJI_EMBLEM_WIDTH, scale, pix);
239     g_list_free (pix);
240   }
241 
242   return self->emblem;
243 }
244 
245 
246 
247 static gint64
biji_notebook_get_mtime(BijiItem * coll)248 biji_notebook_get_mtime (BijiItem *coll)
249 {
250   BijiNotebook *self;
251 
252   g_return_val_if_fail (BIJI_IS_NOTEBOOK (coll), 0);
253   self = BIJI_NOTEBOOK (coll);
254 
255   return self->mtime;
256 }
257 
258 
259 /* As of today, notebooks are only local
260  * We'll need to override this */
261 
262 static const gchar *
biji_notebook_get_place(BijiItem * coll)263 biji_notebook_get_place (BijiItem *coll)
264 {
265   return _("Local");
266 }
267 
268 
269 static gboolean
biji_notebook_trash(BijiItem * item)270 biji_notebook_trash (BijiItem *item)
271 {
272   BijiNotebook *self;
273   g_return_val_if_fail (BIJI_IS_NOTEBOOK (item), FALSE);
274 
275   self = BIJI_NOTEBOOK (item);
276   biji_remove_notebook_from_tracker (biji_item_get_manager (item), self->urn);
277 
278   return TRUE;
279 }
280 
281 static gboolean
biji_notebook_delete(BijiItem * item)282 biji_notebook_delete (BijiItem *item)
283 {
284   g_return_val_if_fail (BIJI_IS_NOTEBOOK (item), FALSE);
285 
286   g_warning ("Notebooks delete is not yet implemented");
287   return FALSE;
288 }
289 
290 static gboolean
biji_notebook_restore(BijiItem * item,gchar ** old_uuid)291 biji_notebook_restore (BijiItem  *item,
292                        gchar    **old_uuid)
293 {
294   g_warning ("Notebooks restore is not yet implemented");
295   return FALSE;
296 }
297 
298 
299 static gboolean
biji_notebook_has_notebook(BijiItem * item,gchar * notebook)300 biji_notebook_has_notebook (BijiItem *item, gchar *notebook)
301 {
302   //todo
303   return FALSE;
304 }
305 
306 
307 static gboolean
biji_notebook_add_notebook(BijiItem * item,BijiItem * coll,gchar * title)308 biji_notebook_add_notebook (BijiItem *item, BijiItem *coll, gchar *title)
309 {
310   g_warning ("biji notebook add notebook is not implemented.");
311   return FALSE;
312 }
313 
314 
315 static gboolean
biji_notebook_remove_notebook(BijiItem * item,BijiItem * notebook)316 biji_notebook_remove_notebook (BijiItem *item, BijiItem *notebook)
317 {
318   g_warning ("biji notebook remove notebook is not implemented.");
319   return FALSE;
320 }
321 
322 
323 static void
biji_notebook_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)324 biji_notebook_set_property (GObject      *object,
325                               guint         property_id,
326                               const GValue *value,
327                               GParamSpec   *pspec)
328 {
329   BijiNotebook *self = BIJI_NOTEBOOK (object);
330 
331 
332   switch (property_id)
333     {
334       case PROP_URN:
335         self->urn = g_strdup (g_value_get_string (value));
336         break;
337       case PROP_NAME:
338         self->name = g_strdup (g_value_get_string (value));
339         break;
340       case PROP_MTIME:
341         self->mtime = g_value_get_int64 (value);
342         break;
343       default:
344         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
345         break;
346     }
347 }
348 
349 
350 static void
biji_notebook_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)351 biji_notebook_get_property (GObject    *object,
352                               guint       property_id,
353                               GValue     *value,
354                               GParamSpec *pspec)
355 {
356   BijiNotebook *self = BIJI_NOTEBOOK (object);
357 
358   switch (property_id)
359     {
360       case PROP_URN:
361         g_value_set_string (value, self->urn);
362         break;
363       case PROP_NAME:
364         g_value_set_string (value, self->name);
365         break;
366       case PROP_MTIME:
367         g_value_set_int64 (value, self->mtime);
368         break;
369       default:
370         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
371         break;
372     }
373 }
374 
375 
376 static void
on_collected_item_change(BijiNotebook * self)377 on_collected_item_change (BijiNotebook *self)
378 {
379   BijiManager *manager;
380   GList *l;
381 
382   manager = biji_item_get_manager (BIJI_ITEM (self));
383 
384   /* Diconnected any handler */
385   for (l = self->collected_items; l != NULL; l = l->next)
386   {
387     g_signal_handlers_disconnect_by_func (l->data, on_collected_item_change, self);
388   }
389 
390   /* Then re-process the whole stuff */
391   biji_get_items_with_notebook_async (manager,
392                                       self->name,
393                                       biji_notebook_update_collected,
394                                       self);
395 }
396 
397 /* For convenience, items are retrieved async.
398  * Thus use a signal once icon & emblem updated.*/
399 static void
biji_notebook_update_collected(GList * result,gpointer user_data)400 biji_notebook_update_collected (GList *result,
401                                   gpointer user_data)
402 {
403   BijiNotebook *self = user_data;
404   GList *l;
405 
406   g_clear_pointer (&self->collected_items, g_list_free);
407   g_clear_pointer (&self->icon, cairo_surface_destroy);
408   g_clear_pointer (&self->emblem, cairo_surface_destroy);
409 
410   self->collected_items = result;
411 
412   /* Connect */
413   for (l = self->collected_items; l != NULL; l = l->next)
414   {
415     g_signal_connect_swapped (l->data, "color-changed",
416                               G_CALLBACK (on_collected_item_change), self);
417 
418     g_signal_connect_swapped (l->data, "trashed",
419                               G_CALLBACK (on_collected_item_change), self);
420   }
421 
422   g_signal_emit (self, biji_notebooks_signals[NOTEBOOK_ICON_UPDATED], 0);
423 }
424 
425 void
biji_notebook_refresh(BijiNotebook * self)426 biji_notebook_refresh (BijiNotebook *self)
427 {
428   on_collected_item_change (self);
429 }
430 
431 static void
biji_notebook_constructed(GObject * obj)432 biji_notebook_constructed (GObject *obj)
433 {
434   BijiNotebook *self = BIJI_NOTEBOOK (obj);
435   BijiManager *manager;
436 
437 
438   manager = biji_item_get_manager (BIJI_ITEM (obj));
439 
440   biji_get_items_with_notebook_async (manager,
441                                       self->name,
442                                       biji_notebook_update_collected,
443                                       self);
444 }
445 
446 static gboolean
say_no(BijiItem * item)447 say_no (BijiItem *item)
448 {
449   return FALSE;
450 }
451 
452 
453 static void
biji_notebook_class_init(BijiNotebookClass * klass)454 biji_notebook_class_init (BijiNotebookClass *klass)
455 {
456   GObjectClass *g_object_class;
457   BijiItemClass*  item_class;
458 
459   g_object_class = G_OBJECT_CLASS (klass);
460   item_class = BIJI_ITEM_CLASS (klass);
461 
462   g_object_class->constructed = biji_notebook_constructed;
463   g_object_class->finalize = biji_notebook_finalize;
464   g_object_class->set_property = biji_notebook_set_property;
465   g_object_class->get_property = biji_notebook_get_property;
466 
467   properties[PROP_URN] =
468     g_param_spec_string ("urn",
469                          "Collection URN",
470                          "Collection URN as in Tracker",
471                          NULL,
472                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
473 
474   properties[PROP_NAME] =
475     g_param_spec_string ("name",
476                          "Collection label",
477                          "The tag as a string",
478                          NULL,
479                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
480 
481   properties[PROP_MTIME] =
482     g_param_spec_int64  ("mtime",
483                          "Modification time",
484                          "Last modified time",
485                          G_MININT64, G_MAXINT64, 0,
486                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
487 
488   g_object_class_install_properties (g_object_class, BIJI_NOTEBOOK_PROPERTIES, properties);
489 
490   biji_notebooks_signals[NOTEBOOK_ICON_UPDATED] =
491     g_signal_new ("icon-changed",
492                   G_OBJECT_CLASS_TYPE (klass),
493                   G_SIGNAL_RUN_LAST,
494                   0,
495                   NULL,
496                   NULL,
497                   g_cclosure_marshal_VOID__VOID,
498                   G_TYPE_NONE,
499                   0);
500 
501   /* Interface */
502   item_class->get_title = biji_notebook_get_title;
503   item_class->get_uuid = biji_notebook_get_uuid;
504   item_class->get_icon = biji_notebook_get_icon;
505   item_class->get_emblem = biji_notebook_get_emblem;
506   item_class->get_pristine = biji_notebook_get_emblem;
507   item_class->get_mtime = biji_notebook_get_mtime;
508   item_class->get_place = biji_notebook_get_place;
509   item_class->has_color = say_no;
510   item_class->trash = biji_notebook_trash;
511   item_class->delete = biji_notebook_delete;
512   item_class->restore = biji_notebook_restore;
513   item_class->is_collectable = say_no;
514   item_class->has_notebook = biji_notebook_has_notebook;
515   item_class->add_notebook = biji_notebook_add_notebook;
516   item_class->remove_notebook = biji_notebook_remove_notebook;
517 }
518 
519 
520 static void
biji_notebook_finalize(GObject * object)521 biji_notebook_finalize (GObject *object)
522 {
523   BijiNotebook *self;
524   g_return_if_fail (BIJI_IS_NOTEBOOK (object));
525 
526   self = BIJI_NOTEBOOK (object);
527   g_free (self->name);
528   g_free (self->urn);
529 
530   G_OBJECT_CLASS (biji_notebook_parent_class)->finalize (object);
531 }
532 
533 
534 static void
biji_notebook_init(BijiNotebook * self)535 biji_notebook_init (BijiNotebook *self)
536 {
537 }
538 
539 
540 BijiNotebook *
biji_notebook_new(GObject * manager,gchar * urn,gchar * name,gint64 mtime)541 biji_notebook_new (GObject *manager, gchar *urn, gchar *name, gint64 mtime)
542 {
543   return g_object_new (BIJI_TYPE_NOTEBOOK,
544                        "manager", manager,
545                        "name",      name,
546                        "urn",       urn,
547                        "mtime",     mtime,
548                        NULL);
549 }
550