1 /* biji-note-obj.c
2  * Copyright (C) Pierre-Yves LUYTEN 2012 <py@luyten.fr>
3  *
4  * bijiben is free software: you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the
6  * Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * bijiben is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12  * See the GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "biji-date-time.h"
19 #include "biji-manager.h"
20 #include "../bjb-utils.h"
21 #include "biji-note-obj.h"
22 #include "biji-timeout.h"
23 #include "biji-tracker.h"
24 
25 #include "editor/biji-webkit-editor.h"
26 
27 typedef struct
28 {
29   /* Metadata */
30   char                  *path;
31   char                  *title;
32   char                  *content;
33   gint64                 mtime;
34   gint64                 create_date;
35   gint64                 last_metadata_change_date;
36   GdkRGBA               *color; // Not yet in Tracker
37 
38   /* Editing use the same widget
39    * for all notes provider. */
40   BijiWebkitEditor      *editor;
41 
42   /* Save */
43   BijiTimeout           *timeout;
44   gboolean               needs_save;
45 
46   /* Icon might be null untill usefull
47    * Emblem is smaller & just shows the color */
48   cairo_surface_t       *icon;
49   cairo_surface_t       *emblem;
50   cairo_surface_t       *pristine;
51 
52   /* Tags
53    * In Tomboy, templates are 'system:notebook:%s' tags.*/
54   GHashTable            *labels;
55   gboolean               is_template;
56 
57   /* Signals */
58   gulong                 note_renamed;
59   gulong                 save;
60 } BijiNoteObjPrivate;
61 
62 /* Properties */
63 enum {
64   PROP_0,
65   PROP_PATH,
66   PROP_TITLE,
67   PROP_MTIME,
68   PROP_CONTENT,
69   BIJI_OBJ_PROPERTIES
70 };
71 
72 static GParamSpec *properties[BIJI_OBJ_PROPERTIES] = { NULL, };
73 
74 /* Signals. Do not interfere with biji-item parent class. */
75 enum {
76   NOTE_RENAMED,
77   NOTE_CHANGED,
78   NOTE_COLOR_CHANGED,
79   BIJI_OBJ_SIGNALS
80 };
81 
82 static guint biji_obj_signals [BIJI_OBJ_SIGNALS] = { 0 };
83 
G_DEFINE_TYPE_WITH_PRIVATE(BijiNoteObj,biji_note_obj,BIJI_TYPE_ITEM)84 G_DEFINE_TYPE_WITH_PRIVATE (BijiNoteObj, biji_note_obj, BIJI_TYPE_ITEM)
85 
86 static void
87 on_save_timeout (BijiNoteObj *self)
88 {
89   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
90 
91   /* g_mutex_lock (priv->mutex); */
92 
93   if (!priv->needs_save)
94     return;
95 
96   g_object_ref (self);
97 
98   /* Each note type serializes its own way
99    * FIXME: common tracker would make sense */
100 
101   BIJI_NOTE_OBJ_GET_CLASS (self)->save_note (self);
102 
103   priv->needs_save = FALSE;
104   g_object_unref (self);
105 }
106 
107 static void
biji_note_obj_init(BijiNoteObj * self)108 biji_note_obj_init (BijiNoteObj *self)
109 {
110   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
111 
112   priv->timeout = biji_timeout_new ();
113   priv->save = g_signal_connect_swapped (priv->timeout, "timeout", G_CALLBACK (on_save_timeout), self);
114   priv->labels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
115 }
116 
117 static void
biji_note_obj_clear_icons(BijiNoteObj * self)118 biji_note_obj_clear_icons (BijiNoteObj *self)
119 {
120   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
121 
122   g_clear_pointer (&priv->icon, cairo_surface_destroy);
123   g_clear_pointer (&priv->emblem, cairo_surface_destroy);
124   g_clear_pointer (&priv->pristine, cairo_surface_destroy);
125 }
126 
127 static void
biji_note_obj_finalize(GObject * object)128 biji_note_obj_finalize (GObject *object)
129 {
130   BijiNoteObj        *self = BIJI_NOTE_OBJ(object);
131   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
132 
133   if (priv->timeout)
134     g_object_unref (priv->timeout);
135 
136   if (priv->needs_save)
137     on_save_timeout (self);
138 
139   g_free (priv->path);
140   g_free (priv->title);
141   g_free (priv->content);
142 
143   g_hash_table_destroy (priv->labels);
144 
145   biji_note_obj_clear_icons (self);
146 
147   gdk_rgba_free (priv->color);
148 
149   G_OBJECT_CLASS (biji_note_obj_parent_class)->finalize (object);
150 }
151 
152 /* we do NOT deserialize here. it might be a brand new note
153  * it's up the manager to ask .note to be read*/
154 static void
biji_note_obj_constructed(GObject * obj)155 biji_note_obj_constructed (GObject *obj)
156 {
157 }
158 
159 static void
biji_note_obj_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)160 biji_note_obj_set_property (GObject      *object,
161                             guint         property_id,
162                             const GValue *value,
163                             GParamSpec   *pspec)
164 {
165   BijiNoteObj        *self = BIJI_NOTE_OBJ (object);
166   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
167 
168   switch (property_id)
169     {
170     case PROP_PATH:
171       biji_note_obj_set_path (self, g_value_get_string (value));
172       break;
173     case PROP_TITLE:
174       biji_note_obj_set_title (self, (char *) g_value_get_string (value));
175       g_object_notify_by_pspec (object, properties[PROP_TITLE]);
176       break;
177     case PROP_MTIME:
178       priv->mtime = g_value_get_int64 (value);
179       break;
180     case PROP_CONTENT:
181       biji_note_obj_set_raw_text (self, g_value_get_string (value));
182       g_object_notify_by_pspec (object, properties[PROP_CONTENT]);
183       break;
184     default:
185       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
186       break;
187     }
188 }
189 
190 static void
biji_note_obj_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)191 biji_note_obj_get_property (GObject    *object,
192                             guint       property_id,
193                             GValue     *value,
194                             GParamSpec *pspec)
195 {
196   BijiNoteObj        *self = BIJI_NOTE_OBJ (object);
197   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
198 
199   switch (property_id)
200     {
201     case PROP_PATH:
202       g_value_set_object (value, priv->path);
203       break;
204     case PROP_TITLE:
205       g_value_set_string (value, priv->title);
206       break;
207     case PROP_MTIME:
208       g_value_set_int64 (value, priv->mtime);
209       break;
210     case PROP_CONTENT:
211       g_value_set_string (value, priv->content);
212       break;
213     default:
214       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
215       break;
216     }
217 }
218 
219 gboolean
biji_note_obj_are_same(BijiNoteObj * note_a,BijiNoteObj * note_b)220 biji_note_obj_are_same (BijiNoteObj *note_a,
221                         BijiNoteObj *note_b)
222 {
223   BijiNoteObjPrivate *a_priv = biji_note_obj_get_instance_private (note_a);
224   BijiNoteObjPrivate *b_priv = biji_note_obj_get_instance_private (note_b);
225 
226   return (g_strcmp0 (a_priv->path,    b_priv->path)    == 0 &&
227           g_strcmp0 (a_priv->content, b_priv->content) == 0);
228 
229 }
230 
231 /* First cancel timeout
232  * this func is most probably stupid it might exists (move file) */
233 static gboolean
trash(BijiItem * item)234 trash (BijiItem *item)
235 {
236   BijiNoteObj        *self      = BIJI_NOTE_OBJ (item);
237   BijiNoteObjPrivate *priv      = biji_note_obj_get_instance_private (self);
238   GFile              *icon      = NULL;
239   char               *icon_path = NULL;
240   gboolean            result    = FALSE;
241 
242   priv->needs_save = FALSE;
243   biji_timeout_cancel (priv->timeout);
244   result = BIJI_NOTE_OBJ_GET_CLASS (self)->archive (self);
245 
246   if (result == TRUE)
247     {
248       /* Delete icon file */
249       icon_path = biji_note_obj_get_icon_file (self);
250       icon = g_file_new_for_path (icon_path);
251       g_file_delete (icon, NULL, NULL);
252     }
253 
254   g_free (icon_path);
255 
256   if (icon != NULL)
257     g_object_unref (icon);
258 
259   return result;
260 }
261 
262 gboolean
biji_note_obj_is_trashed(BijiNoteObj * self)263 biji_note_obj_is_trashed (BijiNoteObj *self)
264 {
265   return BIJI_NOTE_OBJ_GET_CLASS (self)->is_trashed (self);
266 }
267 
268 const char *
biji_note_obj_get_path(BijiNoteObj * self)269 biji_note_obj_get_path (BijiNoteObj *self)
270 {
271   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
272 
273   return priv->path;
274 }
275 
276 static const char *
get_path(BijiItem * item)277 get_path (BijiItem *item)
278 {
279   g_return_val_if_fail (BIJI_IS_NOTE_OBJ (item), NULL);
280 
281   return biji_note_obj_get_path (BIJI_NOTE_OBJ (item));
282 }
283 
284 void
biji_note_obj_set_path(BijiNoteObj * self,const char * path)285 biji_note_obj_set_path (BijiNoteObj *self,
286                         const char  *path)
287 {
288   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
289 
290   if (g_strcmp0 (path, priv->path) == 0)
291     return;
292 
293   g_free (priv->path);
294   priv->path = g_strdup (path);
295   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
296 }
297 
298 const char *
biji_note_obj_get_title(BijiNoteObj * self)299 biji_note_obj_get_title (BijiNoteObj *self)
300 {
301   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
302 
303   return priv->title;
304 }
305 
306 static const char *
get_title(BijiItem * item)307 get_title (BijiItem *item)
308 {
309   g_return_val_if_fail (BIJI_IS_NOTE_OBJ (item), NULL);
310 
311   return biji_note_obj_get_title (BIJI_NOTE_OBJ (item));
312 }
313 
314 /* If already a title, then note is renamed */
315 gboolean
biji_note_obj_set_title(BijiNoteObj * self,const char * proposed_title)316 biji_note_obj_set_title (BijiNoteObj *self,
317                          const char  *proposed_title)
318 {
319   BijiNoteObjPrivate *priv    = biji_note_obj_get_instance_private (self);
320   BijiManager        *manager = biji_item_get_manager (BIJI_ITEM (self));
321   g_autofree char    *title   = NULL;
322 
323   if (g_strcmp0 (proposed_title, priv->title) == 0)
324     return FALSE;
325 
326   title = biji_manager_get_unique_title (manager, proposed_title);
327   biji_note_obj_set_last_metadata_change_date (self, g_get_real_time () / G_USEC_PER_SEC);
328 
329   /* Emit signal even if initial title, just to let know */
330   g_free (priv->title);
331   priv->title = g_strdup (title);
332   g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_TITLE]);
333   biji_note_obj_save_note (self);
334   g_signal_emit (G_OBJECT (self), biji_obj_signals[NOTE_RENAMED], 0);
335 
336   return TRUE;
337 }
338 
339 gboolean
biji_note_obj_set_mtime(BijiNoteObj * self,gint64 time)340 biji_note_obj_set_mtime (BijiNoteObj *self,
341                          gint64       time)
342 {
343   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
344 
345   if (priv->mtime == time)
346     return FALSE;
347 
348   priv->mtime = time;
349   return TRUE;
350 }
351 
352 static gint64
get_mtime(BijiItem * item)353 get_mtime (BijiItem *item)
354 {
355   BijiNoteObj        *self = BIJI_NOTE_OBJ (item);
356   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
357 
358   return priv->mtime;
359 }
360 
361 char *
biji_note_obj_get_last_change_date_string(BijiNoteObj * self)362 biji_note_obj_get_last_change_date_string (BijiNoteObj *self)
363 {
364   BijiItem *item = BIJI_ITEM (self);
365 
366   return bjb_utils_get_human_time (get_mtime (item));
367 }
368 
369 gint64
biji_note_obj_get_last_metadata_change_date(BijiNoteObj * self)370 biji_note_obj_get_last_metadata_change_date (BijiNoteObj *self)
371 {
372   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
373 
374   return priv->last_metadata_change_date;
375 }
376 
377 gboolean
biji_note_obj_set_last_metadata_change_date(BijiNoteObj * self,gint64 time)378 biji_note_obj_set_last_metadata_change_date (BijiNoteObj *self,
379                                              gint64       time)
380 {
381   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
382 
383   if (priv->last_metadata_change_date == time)
384     return FALSE;
385 
386   priv->last_metadata_change_date = time;
387   return TRUE;
388 }
389 
390 static void
biji_note_obj_set_rgba_internal(BijiNoteObj * self,const GdkRGBA * rgba)391 biji_note_obj_set_rgba_internal (BijiNoteObj   *self,
392                                  const GdkRGBA *rgba)
393 {
394   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
395 
396   priv->color = gdk_rgba_copy(rgba);
397   g_signal_emit (G_OBJECT (self), biji_obj_signals[NOTE_COLOR_CHANGED], 0);
398 }
399 
400 void
biji_note_obj_set_rgba(BijiNoteObj * self,const GdkRGBA * rgba)401 biji_note_obj_set_rgba (BijiNoteObj   *self,
402                         const GdkRGBA *rgba)
403 {
404   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
405 
406   if (!priv->color)
407     biji_note_obj_set_rgba_internal (self, rgba);
408 
409   else if (!gdk_rgba_equal (priv->color,rgba))
410     {
411       gdk_rgba_free (priv->color);
412       biji_note_obj_clear_icons (self);
413       biji_note_obj_set_rgba_internal (self, rgba);
414       biji_note_obj_set_last_metadata_change_date (self, g_get_real_time () / G_USEC_PER_SEC);
415       biji_note_obj_save_note (self);
416     }
417 }
418 
419 gboolean
biji_note_obj_get_rgba(BijiNoteObj * self,GdkRGBA * rgba)420 biji_note_obj_get_rgba(BijiNoteObj *self,
421                        GdkRGBA     *rgba)
422 {
423   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
424 
425   g_return_val_if_fail (BIJI_IS_NOTE_OBJ (self), FALSE);
426 
427   if (priv->color && rgba)
428     {
429       *rgba = *(priv->color);
430       return TRUE;
431     }
432 
433   return FALSE;
434 }
435 
436 const char *
biji_note_obj_get_raw_text(BijiNoteObj * self)437 biji_note_obj_get_raw_text (BijiNoteObj *self)
438 {
439   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
440 
441   return priv->content;
442 }
443 
444 void
biji_note_obj_set_raw_text(BijiNoteObj * self,const char * plain_text)445 biji_note_obj_set_raw_text (BijiNoteObj *self,
446                             const char  *plain_text)
447 {
448   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
449 
450   if (g_strcmp0 (plain_text, priv->content) == 0)
451     return;
452 
453   g_free (priv->content);
454   priv->content = g_strdup (plain_text);
455   biji_note_obj_clear_icons (self);
456   g_signal_emit (self, biji_obj_signals[NOTE_CHANGED],0);
457 }
458 
459 GList *
biji_note_obj_get_notebooks(BijiNoteObj * self)460 biji_note_obj_get_notebooks (BijiNoteObj *self)
461 {
462   BijiNoteObjPrivate *priv;
463 
464   g_return_val_if_fail (BIJI_IS_NOTE_OBJ (self), NULL);
465 
466   priv = biji_note_obj_get_instance_private (self);
467 
468   return g_hash_table_get_values (priv->labels);
469 }
470 
471 static gboolean
has_notebook(BijiItem * item,char * label)472 has_notebook (BijiItem *item,
473               char     *label)
474 {
475   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (BIJI_NOTE_OBJ (item));
476 
477   if (g_hash_table_lookup (priv->labels, label))
478     return TRUE;
479 
480   return FALSE;
481 }
482 
483 static void
_biji_notebook_refresh(gboolean query_result,gpointer coll)484 _biji_notebook_refresh (gboolean query_result,
485                         gpointer coll)
486 {
487   g_return_if_fail (BIJI_IS_NOTEBOOK (coll));
488 
489   if (query_result)
490     biji_notebook_refresh (BIJI_NOTEBOOK (coll));
491 }
492 
493 static gboolean
add_notebook(BijiItem * item,BijiItem * notebook,char * title)494 add_notebook (BijiItem *item,
495               BijiItem *notebook,
496               char     *title)
497 {
498   BijiNoteObj        *self;
499   BijiNoteObjPrivate *priv;
500   char               *label = title;
501 
502   g_return_val_if_fail (BIJI_IS_NOTE_OBJ (item), FALSE);
503   self = BIJI_NOTE_OBJ (item);
504   priv = biji_note_obj_get_instance_private (self);
505 
506   if (BIJI_IS_NOTEBOOK (notebook))
507     label = (char*) biji_item_get_title (notebook);
508 
509   if (has_notebook (item, label))
510     return FALSE;
511 
512   g_hash_table_add (priv->labels, g_strdup (label));
513 
514   if (BIJI_IS_NOTEBOOK (notebook))
515     {
516       biji_push_existing_notebook_to_note (self, label, _biji_notebook_refresh, notebook); // Tracker
517       biji_note_obj_set_last_metadata_change_date (self, g_get_real_time () / G_USEC_PER_SEC);
518       biji_note_obj_save_note (self);
519     }
520 
521   return TRUE;
522 }
523 
524 static gboolean
remove_notebook(BijiItem * item,BijiItem * notebook)525 remove_notebook (BijiItem *item,
526                  BijiItem *notebook)
527 {
528   BijiNoteObj        *self;
529   BijiNoteObjPrivate *priv;
530 
531   g_return_val_if_fail (BIJI_IS_NOTE_OBJ (item), FALSE);
532   g_return_val_if_fail (BIJI_IS_NOTEBOOK (notebook), FALSE);
533 
534   self = BIJI_NOTE_OBJ (item);
535   priv = biji_note_obj_get_instance_private (self);
536 
537   if (g_hash_table_remove (priv->labels, biji_item_get_title (notebook)))
538     {
539       biji_remove_notebook_from_note (self, notebook, _biji_notebook_refresh, notebook); // tracker.
540       biji_note_obj_set_last_metadata_change_date (self, g_get_real_time () / G_USEC_PER_SEC);
541       biji_note_obj_save_note (self);
542       return TRUE;
543     }
544 
545   return FALSE;
546 }
547 
548 gboolean
biji_note_obj_is_template(BijiNoteObj * self)549 biji_note_obj_is_template (BijiNoteObj *self)
550 {
551   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
552 
553   return priv->is_template;
554 }
555 
556 void
biji_note_obj_set_is_template(BijiNoteObj * self,gboolean is_template)557 biji_note_obj_set_is_template (BijiNoteObj *self,
558                                gboolean     is_template)
559 {
560   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
561 
562   priv->is_template = is_template;
563 }
564 
565 void
biji_note_obj_save_note(BijiNoteObj * self)566 biji_note_obj_save_note (BijiNoteObj *self)
567 {
568   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (BIJI_NOTE_OBJ (self));
569 
570   priv->needs_save = TRUE;
571   biji_timeout_reset (priv->timeout, 3000);
572 }
573 
574 char *
biji_note_obj_get_icon_file(BijiNoteObj * self)575 biji_note_obj_get_icon_file (BijiNoteObj *self)
576 {
577   const char *uuid;
578   char *basename, *filename;
579 
580   g_return_val_if_fail (BIJI_IS_NOTE_OBJ (self), NULL);
581 
582   uuid = BIJI_NOTE_OBJ_GET_CLASS (self)->get_basename (self);
583   basename = biji_str_mass_replace (uuid, ".note", ".png", ".txt", ".png", NULL);
584 
585   filename = g_build_filename (g_get_user_cache_dir (),
586                                g_get_application_name (),
587                                basename,
588                                NULL);
589 
590   g_free (basename);
591   return filename;
592 }
593 
594 static cairo_surface_t *
get_icon(BijiItem * item,int scale)595 get_icon (BijiItem *item,
596           int       scale)
597 {
598   GdkRGBA                note_color;
599   const char            *text;
600   cairo_t               *cr;
601   PangoLayout           *layout;
602   PangoFontDescription  *desc;
603   cairo_surface_t       *surface = NULL;
604   BijiNoteObj           *note = BIJI_NOTE_OBJ (item);
605   BijiNoteObjPrivate    *priv = biji_note_obj_get_instance_private (note);
606 
607   if (priv->icon)
608     return priv->icon;
609 
610   /* Create & Draw surface */
611   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
612                                         BIJI_ICON_WIDTH * scale,
613                                         BIJI_ICON_HEIGHT * scale);
614   cairo_surface_set_device_scale (surface, scale, scale);
615   cr = cairo_create (surface);
616 
617   /* Background */
618   cairo_rectangle (cr, 0, 0, BIJI_ICON_WIDTH, BIJI_ICON_HEIGHT);
619   if (biji_note_obj_get_rgba (note, &note_color))
620     gdk_cairo_set_source_rgba (cr, &note_color);
621 
622   cairo_fill (cr);
623 
624   /* Text */
625   text = biji_note_obj_get_raw_text (note);
626   if (text != NULL)
627     {
628       cairo_translate (cr, 10, 10);
629       layout = pango_cairo_create_layout (cr);
630 
631       pango_layout_set_width (layout, 180000 );
632       pango_layout_set_wrap (layout, PANGO_WRAP_WORD_CHAR);
633       pango_layout_set_height (layout, 180000 ) ;
634 
635       pango_layout_set_text (layout, text, -1);
636       desc = pango_font_description_from_string (BIJI_ICON_FONT);
637       pango_layout_set_font_description (layout, desc);
638       pango_font_description_free (desc);
639 
640       if(note_color.red < 0.5)
641         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
642       else
643         cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
644 
645       pango_cairo_update_layout (cr, layout);
646       pango_cairo_show_layout (cr, layout);
647 
648       g_object_unref (layout);
649 
650       cairo_translate (cr, -10, -10);
651     }
652 
653   /* Border */
654   cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 1);
655   cairo_set_line_width (cr, 1 * scale);
656   cairo_rectangle (cr, 0, 0, BIJI_ICON_WIDTH, BIJI_ICON_HEIGHT);
657   cairo_stroke (cr);
658 
659   cairo_destroy (cr);
660 
661   priv->icon = surface;
662   return priv->icon;
663 }
664 
665 static cairo_surface_t *
get_pristine(BijiItem * item,int scale)666 get_pristine (BijiItem *item,
667               int       scale)
668 {
669   GdkRGBA                note_color;
670   cairo_t               *cr;
671   cairo_surface_t       *surface = NULL;
672   BijiNoteObj           *note = BIJI_NOTE_OBJ (item);
673   BijiNoteObjPrivate    *priv = biji_note_obj_get_instance_private (note);
674 
675   if (priv->pristine)
676     return priv->pristine;
677 
678   /* Create & Draw surface */
679   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
680                                         BIJI_EMBLEM_WIDTH * scale,
681                                         BIJI_EMBLEM_HEIGHT * scale) ;
682   cairo_surface_set_device_scale (surface, scale, scale);
683   cr = cairo_create (surface);
684 
685   /* Background */
686   cairo_rectangle (cr, 0, 0, BIJI_EMBLEM_WIDTH, BIJI_EMBLEM_HEIGHT);
687   if (biji_note_obj_get_rgba (note, &note_color))
688     gdk_cairo_set_source_rgba (cr, &note_color);
689 
690   cairo_fill (cr);
691   cairo_destroy (cr);
692 
693   priv->pristine = surface;
694   return priv->pristine;
695 }
696 
697 static cairo_surface_t *
get_emblem(BijiItem * item,int scale)698 get_emblem (BijiItem *item,
699             int       scale)
700 {
701   GdkRGBA                note_color;
702   cairo_t               *cr;
703   cairo_surface_t       *surface = NULL;
704   BijiNoteObj           *note = BIJI_NOTE_OBJ (item);
705   BijiNoteObjPrivate    *priv = biji_note_obj_get_instance_private (note);
706 
707   if (priv->emblem)
708     return priv->emblem;
709 
710   /* Create & Draw surface */
711   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
712                                         BIJI_EMBLEM_WIDTH * scale,
713                                         BIJI_EMBLEM_HEIGHT * scale) ;
714   cairo_surface_set_device_scale (surface, scale, scale);
715   cr = cairo_create (surface);
716 
717   /* Background */
718   cairo_rectangle (cr, 0, 0, BIJI_EMBLEM_WIDTH, BIJI_EMBLEM_HEIGHT);
719   if (biji_note_obj_get_rgba (note, &note_color))
720     gdk_cairo_set_source_rgba (cr, &note_color);
721 
722   cairo_fill (cr);
723 
724   /* Border */
725   cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 1);
726   cairo_set_line_width (cr, 1 * scale);
727   cairo_rectangle (cr, 0, 0, BIJI_EMBLEM_WIDTH, BIJI_EMBLEM_HEIGHT);
728   cairo_stroke (cr);
729 
730   cairo_destroy (cr);
731 
732   priv->emblem = surface;
733   return priv->emblem;
734 }
735 
736 /* Single Note */
737 
738 void
biji_note_obj_set_all_dates_now(BijiNoteObj * self)739 biji_note_obj_set_all_dates_now (BijiNoteObj *self)
740 {
741   gint64 time = g_get_real_time () / G_USEC_PER_SEC;
742 
743   biji_note_obj_set_create_date (self, time);
744   biji_note_obj_set_last_metadata_change_date (self, time);
745   biji_note_obj_set_mtime (self, time);
746 }
747 
748 gint64
biji_note_obj_get_create_date(BijiNoteObj * self)749 biji_note_obj_get_create_date (BijiNoteObj *self)
750 {
751   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
752 
753   return priv->create_date;
754 }
755 
756 gboolean
biji_note_obj_set_create_date(BijiNoteObj * self,gint64 time)757 biji_note_obj_set_create_date (BijiNoteObj *self,
758                                gint64       time)
759 {
760   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
761 
762   if (priv->create_date == time)
763     return FALSE;
764 
765   priv->create_date = time;
766   return TRUE;
767 }
768 
769 /* Webkit */
770 
771 char *
html_from_plain_text(const char * content)772 html_from_plain_text (const char *content)
773 {
774   g_autofree char *escaped = NULL;
775 
776   if (content == NULL)
777     escaped = g_strdup ("");
778   else
779     escaped = biji_str_mass_replace (content,
780                                      "&", "&amp;",
781                                      "<", "&lt;",
782                                      ">", "&gt;",
783                                      "\n", "<br/>",
784                                      NULL);
785 
786   return g_strconcat ("<html xmlns=\"http://www.w3.org/1999/xhtml\">",
787                       "<head>",
788                       "<link rel=\"stylesheet\" href=\"Default.css\" type=\"text/css\"/>",
789                       "<script language=\"javascript\" src=\"bijiben.js\"></script>"
790                       "</head>",
791                       "<body id=\"editable\">",
792                       escaped,
793                       "</body></html>",
794                       NULL);
795 }
796 
797 static gboolean
is_webkit1(const char * content)798 is_webkit1 (const char *content)
799 {
800   return g_strstr_len (content, -1, "<script type=\"text/javascript\">    window.onload = function () {      document.getElementById('editable').focus();    };</script>") != NULL;
801 }
802 
803 static char *
convert_webkit1_to_webkit2(const char * content)804 convert_webkit1_to_webkit2 (const char *content)
805 {
806   g_autofree char *stripped = NULL;
807 
808   stripped = biji_str_mass_replace (content,
809                                     "<html xmlns=\"http://www.w3.org/1999/xhtml\"><body contenteditable=\"true\" id=\"editable\">", "",
810                                     "<script type=\"text/javascript\">    window.onload = function () {      document.getElementById('editable').focus();    };</script>", "\n",
811                                     "<div><br/></div>", "\n",
812                                     "<div>", "",
813                                     "</div>", "\n",
814                                     "<br/>", "\n",
815                                     "</body></html>", "",
816                                     NULL);
817 
818 	return html_from_plain_text (stripped);
819 }
820 
821 static gboolean
is_contenteditable_hardcoded(const char * content)822 is_contenteditable_hardcoded (const char *content)
823 {
824   return g_strstr_len (content, -1, "contenteditable=\"true\"") != NULL;
825 }
826 
827 static char *
remove_hardcoded_contenteditable(const char * content)828 remove_hardcoded_contenteditable (const char *content)
829 {
830   return biji_str_mass_replace (content,
831                                 "contenteditable=\"true\"", "",
832                                 NULL);
833 }
834 
835 char *
biji_note_obj_get_html(BijiNoteObj * self)836 biji_note_obj_get_html (BijiNoteObj *self)
837 {
838   char *content = BIJI_NOTE_OBJ_GET_CLASS (self)->get_html (self);
839 
840   if (content && is_webkit1 (content))
841     {
842       content = convert_webkit1_to_webkit2 (content);
843       biji_note_obj_set_html (self, content);
844       g_free (content);
845       content = BIJI_NOTE_OBJ_GET_CLASS (self)->get_html (self);
846     }
847   else if (content && is_contenteditable_hardcoded (content))
848     {
849       content = remove_hardcoded_contenteditable (content);
850       biji_note_obj_set_html (self, content);
851       g_free (content);
852       content = BIJI_NOTE_OBJ_GET_CLASS (self)->get_html (self);
853     }
854 
855   return content;
856 }
857 
858 void
biji_note_obj_set_html(BijiNoteObj * self,const char * html)859 biji_note_obj_set_html (BijiNoteObj *self,
860                         const char  *html)
861 {
862   BIJI_NOTE_OBJ_GET_CLASS (self)->set_html (self, html);
863 }
864 
865 gboolean
biji_note_obj_is_opened(BijiNoteObj * self)866 biji_note_obj_is_opened (BijiNoteObj *self)
867 {
868   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
869 
870   return BIJI_IS_WEBKIT_EDITOR (priv->editor);
871 }
872 
873 static void
on_biji_note_obj_closed_cb(BijiNoteObj * self)874 on_biji_note_obj_closed_cb (BijiNoteObj *self)
875 {
876   BijiNoteObjPrivate *priv;
877   BijiItem *item;
878   const char *title;
879 
880   priv = biji_note_obj_get_instance_private (self);
881   item = BIJI_ITEM (self);
882   priv->editor = NULL;
883   title = biji_item_get_title (item);
884 
885   /*
886    * Delete (not _trash_ if note is totaly blank
887    * A Cancellable would be better than needs->save
888    */
889   if (biji_note_obj_get_raw_text (self) == NULL)
890     {
891       priv->needs_save = FALSE;
892       biji_item_delete (item);
893     }
894 
895   /* If the note has no title */
896   else if (title == NULL)
897     {
898       title = biji_note_obj_get_raw_text (self);
899       biji_note_obj_set_title (self, title);
900     }
901 }
902 
903 GtkWidget *
biji_note_obj_open(BijiNoteObj * self)904 biji_note_obj_open (BijiNoteObj *self)
905 {
906   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
907 
908   priv->editor = biji_webkit_editor_new (self);
909 
910   g_signal_connect_swapped (priv->editor, "destroy",
911                             G_CALLBACK (on_biji_note_obj_closed_cb), self);
912 
913   return GTK_WIDGET (priv->editor);
914 }
915 
916 GtkWidget *
biji_note_obj_get_editor(BijiNoteObj * self)917 biji_note_obj_get_editor (BijiNoteObj *self)
918 {
919   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
920 
921   if (!biji_note_obj_is_opened (self))
922     return NULL;
923 
924   return GTK_WIDGET (priv->editor);
925 }
926 
927 gboolean
biji_note_obj_can_format(BijiNoteObj * self)928 biji_note_obj_can_format (BijiNoteObj *self)
929 {
930   return BIJI_NOTE_OBJ_GET_CLASS (self)->can_format (self);
931 }
932 
933 void
biji_note_obj_editor_apply_format(BijiNoteObj * self,int format)934 biji_note_obj_editor_apply_format (BijiNoteObj *self,
935                                    int          format)
936 {
937   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
938 
939   if (biji_note_obj_is_opened (self))
940     biji_webkit_editor_apply_format (priv->editor, format);
941 }
942 
943 gboolean
biji_note_obj_editor_has_selection(BijiNoteObj * self)944 biji_note_obj_editor_has_selection (BijiNoteObj *self)
945 {
946   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
947 
948   if (biji_note_obj_is_opened (self))
949     return biji_webkit_editor_has_selection (priv->editor);
950 
951   return FALSE;
952 }
953 
954 const char *
biji_note_obj_editor_get_selection(BijiNoteObj * self)955 biji_note_obj_editor_get_selection (BijiNoteObj *self)
956 {
957   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
958 
959   if (biji_note_obj_is_opened (self))
960     return biji_webkit_editor_get_selection (priv->editor);
961 
962   return NULL;
963 }
964 
965 void
biji_note_obj_editor_cut(BijiNoteObj * self)966 biji_note_obj_editor_cut (BijiNoteObj *self)
967 {
968   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
969 
970   if (biji_note_obj_is_opened (self))
971     biji_webkit_editor_cut (priv->editor);
972 }
973 
974 void
biji_note_obj_editor_copy(BijiNoteObj * self)975 biji_note_obj_editor_copy (BijiNoteObj *self)
976 {
977   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
978 
979   if (biji_note_obj_is_opened (self))
980     biji_webkit_editor_copy (priv->editor);
981 }
982 
983 void
biji_note_obj_editor_paste(BijiNoteObj * self)984 biji_note_obj_editor_paste (BijiNoteObj *self)
985 {
986   BijiNoteObjPrivate *priv = biji_note_obj_get_instance_private (self);
987 
988   if (biji_note_obj_is_opened (self))
989     biji_webkit_editor_paste (priv->editor);
990 }
991 
992 static void
biji_note_obj_class_init(BijiNoteObjClass * klass)993 biji_note_obj_class_init (BijiNoteObjClass *klass)
994 {
995   GObjectClass  *object_class = G_OBJECT_CLASS  (klass);
996   BijiItemClass *item_class   = BIJI_ITEM_CLASS (klass);
997 
998   object_class->constructed  = biji_note_obj_constructed;
999   object_class->finalize     = biji_note_obj_finalize;
1000   object_class->get_property = biji_note_obj_get_property;
1001   object_class->set_property = biji_note_obj_set_property;
1002 
1003   properties[PROP_PATH] =
1004     g_param_spec_string("path",
1005                         "The note file",
1006                         "The location where the note is stored and saved",
1007                         NULL,
1008                         G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
1009 
1010   properties[PROP_TITLE] =
1011     g_param_spec_string("title",
1012                         "The note title",
1013                         "Note current title",
1014                         NULL,
1015                         G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
1016 
1017   properties[PROP_MTIME] =
1018     g_param_spec_int64 ("mtime",
1019                         "Msec since epoch",
1020                         "The note last modification time",
1021                         G_MININT64, G_MAXINT64, 0,
1022                         G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
1023 
1024 
1025   properties[PROP_CONTENT] =
1026     g_param_spec_string("content",
1027                         "The note content",
1028                         "Plain text note content",
1029                         NULL,
1030                         G_PARAM_CONSTRUCT | G_PARAM_READWRITE);
1031 
1032   g_object_class_install_properties (object_class, BIJI_OBJ_PROPERTIES, properties);
1033 
1034   biji_obj_signals[NOTE_RENAMED] =
1035     g_signal_new ("renamed",
1036                   G_OBJECT_CLASS_TYPE (klass),
1037                   G_SIGNAL_RUN_LAST,
1038                   0,
1039                   NULL,
1040                   NULL,
1041                   g_cclosure_marshal_VOID__VOID,
1042                   G_TYPE_NONE,
1043                   0);
1044 
1045   biji_obj_signals[NOTE_CHANGED] =
1046     g_signal_new ("changed",
1047                   G_OBJECT_CLASS_TYPE (klass),
1048                   G_SIGNAL_RUN_LAST,
1049                   0,
1050                   NULL,
1051                   NULL,
1052                   g_cclosure_marshal_VOID__VOID,
1053                   G_TYPE_NONE,
1054                   0);
1055 
1056   biji_obj_signals[NOTE_COLOR_CHANGED] =
1057     g_signal_new ("color-changed" ,
1058                   G_OBJECT_CLASS_TYPE (klass),
1059                   G_SIGNAL_RUN_LAST,
1060                   0,
1061                   NULL,
1062                   NULL,
1063                   g_cclosure_marshal_VOID__VOID,
1064                   G_TYPE_NONE,
1065                   0);
1066 
1067   /* Interface
1068    * is_collectable is implemented at higher level, eg local_note */
1069   item_class->get_title       = get_title;
1070   item_class->get_uuid        = get_path;
1071   item_class->get_icon        = get_icon;
1072   item_class->get_emblem      = get_emblem;
1073   item_class->get_pristine    = get_pristine;
1074   item_class->get_mtime       = get_mtime;
1075   item_class->trash           = trash;
1076   item_class->has_notebook    = has_notebook;
1077   item_class->add_notebook    = add_notebook;
1078   item_class->remove_notebook = remove_notebook;
1079 }
1080 
1081