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, ¬e_color))
620 gdk_cairo_set_source_rgba (cr, ¬e_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, ¬e_color))
688 gdk_cairo_set_source_rgba (cr, ¬e_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, ¬e_color))
720 gdk_cairo_set_source_rgba (cr, ¬e_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 "&", "&",
781 "<", "<",
782 ">", ">",
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