1 /*
2 * Copyright (c) 2008-2012 Mike Massonnet <mmassonnet@xfce.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17 */
18
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <glib.h>
24 #include <glib-object.h>
25 #include <gtk/gtk.h>
26 #include <libxfce4util/libxfce4util.h>
27
28 #include "common.h"
29
30 #include "history.h"
31
32 /*
33 * GObject declarations
34 */
35
36 struct _ClipmanHistoryPrivate
37 {
38 GSList *items;
39 const ClipmanHistoryItem *item_to_restore;
40 guint max_texts_in_history;
41 guint max_images_in_history;
42 gboolean save_on_quit;
43 gboolean reorder_items;
44 };
45
46 G_DEFINE_TYPE_WITH_PRIVATE (ClipmanHistory, clipman_history, G_TYPE_OBJECT)
47
48 enum
49 {
50 MAX_TEXTS_IN_HISTORY = 1,
51 MAX_IMAGES_IN_HISTORY,
52 SAVE_ON_QUIT,
53 REORDER_ITEMS,
54 };
55
56 enum
57 {
58 ITEM_ADDED,
59 CLEAR,
60 LAST_SIGNAL,
61 };
62 static guint signals[LAST_SIGNAL];
63
64 static void clipman_history_finalize (GObject *object);
65 static void clipman_history_set_property (GObject *object,
66 guint property_id,
67 const GValue *value,
68 GParamSpec *pspec);
69 static void clipman_history_get_property (GObject *object,
70 guint property_id,
71 GValue *value,
72 GParamSpec *pspec);
73
74 /*
75 * Private methods declarations
76 */
77
78 static void _clipman_history_add_item (ClipmanHistory *history,
79 ClipmanHistoryItem *item);
80
81 /*
82 * Misc functions declarations
83 */
84
85 static void __clipman_history_item_free (ClipmanHistoryItem *item);
86 static gint __g_slist_compare_texts (gconstpointer a,
87 gconstpointer b);
88
89
90
91 /*
92 * Private methods
93 */
94
95 static void
_clipman_history_add_item(ClipmanHistory * history,ClipmanHistoryItem * item)96 _clipman_history_add_item (ClipmanHistory *history,
97 ClipmanHistoryItem *item)
98 {
99 GSList *list;
100 ClipmanHistoryItem *_item;
101 guint list_length;
102 guint n_texts = 0;
103 guint n_images = 0;
104
105 /* Count initial items */
106 for (list = history->priv->items; list != NULL; list = list->next)
107 {
108 _item = list->data;
109 if (_item->type == CLIPMAN_HISTORY_TYPE_TEXT)
110 {
111 n_texts++;
112 }
113 else if (_item->type == CLIPMAN_HISTORY_TYPE_IMAGE)
114 {
115 n_images++;
116 }
117 }
118
119 list_length = n_texts + n_images;
120
121 /* First truncate history to max_items (max_texts stands for the size of the history) */
122 while (list_length > history->priv->max_texts_in_history)
123 {
124 DBG ("Delete oldest content from the history");
125 list = g_slist_last (history->priv->items);
126 _item = list->data;
127
128 if (_item->type == CLIPMAN_HISTORY_TYPE_TEXT)
129 {
130 n_texts--;
131 }
132 else if (_item->type == CLIPMAN_HISTORY_TYPE_IMAGE)
133 {
134 n_images--;
135 }
136 list_length--;
137
138 __clipman_history_item_free (_item);
139 history->priv->items = g_slist_remove (history->priv->items, _item);
140 }
141
142 /* Free last image from history if max_images is reached, otherwise last item from history */
143 if (item->type == CLIPMAN_HISTORY_TYPE_IMAGE && n_images >= history->priv->max_images_in_history)
144 {
145 while (n_images >= history->priv->max_images_in_history)
146 {
147 guint i = 1;
148
149 for (list = history->priv->items; list != NULL; list = list->next)
150 {
151 _item = list->data;
152
153 if (_item->type != CLIPMAN_HISTORY_TYPE_IMAGE)
154 continue;
155
156 i++;
157
158 if (i < n_images)
159 continue;
160
161 if (n_images >= history->priv->max_images_in_history)
162 {
163 __clipman_history_item_free (_item);
164 history->priv->items = g_slist_remove (history->priv->items, _item);
165 }
166 n_images--;
167
168 break;
169 }
170 }
171 }
172 else if (list_length == history->priv->max_texts_in_history)
173 {
174 list = g_slist_last (history->priv->items);
175 _item = list->data;
176 __clipman_history_item_free (_item);
177 history->priv->items = g_slist_remove (history->priv->items, _item);
178 }
179
180 /* Prepend item to start of the history */
181 history->priv->items = g_slist_prepend (history->priv->items, item);
182 history->priv->item_to_restore = item;
183
184 /* Emit signal */
185 g_signal_emit (history, signals[ITEM_ADDED], 0);
186 }
187
188 /*
189 * Misc functions
190 */
191
192 static void
__clipman_history_item_free(ClipmanHistoryItem * item)193 __clipman_history_item_free (ClipmanHistoryItem *item)
194 {
195 switch (item->type)
196 {
197 case CLIPMAN_HISTORY_TYPE_TEXT:
198 DBG ("Delete text `%s'", item->content.text);
199 g_free (item->content.text);
200 g_free (item->preview.text);
201 break;
202
203 case CLIPMAN_HISTORY_TYPE_IMAGE:
204 DBG ("Delete image (%p)", item->content.image);
205 g_object_unref (item->content.image);
206 g_object_unref (item->preview.image);
207 break;
208
209 default:
210 g_assert_not_reached ();
211 }
212 g_slice_free (ClipmanHistoryItem, item);
213 }
214
215 static gint
__g_slist_compare_texts(gconstpointer a,gconstpointer b)216 __g_slist_compare_texts (gconstpointer a,
217 gconstpointer b)
218 {
219 const ClipmanHistoryItem *item = a;
220 const gchar *text = b;
221 if (item->type != CLIPMAN_HISTORY_TYPE_TEXT)
222 return -1;
223 return g_ascii_strcasecmp (item->content.text, text);
224 }
225
226 /*
227 * Public methods
228 */
229
230 /**
231 * clipman_history_add_text:
232 * @history: a #ClipmanHistory
233 * @text: the text to add to the history
234 *
235 * Stores a text inside the history. If the history is growing over the
236 * maximum number of items, it will delete the oldest text.
237 */
238 void
clipman_history_add_text(ClipmanHistory * history,const gchar * text)239 clipman_history_add_text (ClipmanHistory *history,
240 const gchar *text)
241 {
242 ClipmanHistoryItem *item;
243 gchar *tmp1, *tmp2;
244 const gchar *offset;
245 gint preview_length = 48;
246 GSList *list;
247
248 /* Search for a previously existing content */
249 list = g_slist_find_custom (history->priv->items, text, (GCompareFunc)__g_slist_compare_texts);
250 if (list != NULL)
251 {
252 DBG ("Found a previous occurence for text `%s'", text);
253 item = list->data;
254 if (history->priv->reorder_items)
255 {
256 __clipman_history_item_free (item);
257 history->priv->items = g_slist_remove (history->priv->items, list->data);
258 }
259 else
260 {
261 history->priv->item_to_restore = item;
262 return;
263 }
264 }
265
266 /* Store the text */
267 DBG ("Store text `%s')", text);
268
269 item = g_slice_new0 (ClipmanHistoryItem);
270 item->type = CLIPMAN_HISTORY_TYPE_TEXT;
271 item->content.text = g_strdup (text);
272
273 /* Strip white spaces for preview */
274 tmp1 = g_strchomp (g_strdup (text));
275
276 tmp2 = tmp1;
277 while (tmp2)
278 {
279 tmp2 = g_strchug(++tmp2);
280 tmp2 = g_strstr_len (tmp1, preview_length, " ");
281 }
282
283 /* Shorten preview */
284 if (g_utf8_strlen (tmp1, -1) > preview_length)
285 {
286 offset = g_utf8_offset_to_pointer (tmp1, preview_length);
287 tmp2 = g_strndup (tmp1, offset - tmp1);
288 g_free (tmp1);
289 tmp1 = g_strconcat (tmp2, "...", NULL);
290 g_free (tmp2);
291 }
292
293 /* Cleanup special characters from preview */
294 tmp1 = g_strdelimit (tmp1, "\n\r\t", ' ');
295
296 /* Set preview */
297 item->preview.text = tmp1;
298
299 _clipman_history_add_item (history, item);
300 }
301
302 /**
303 * clipman_history_add_image:
304 * @history: a #ClipmanHistory
305 * @image: the image to add to the history
306 *
307 * Stores an image inside the history. If the history is growing over the
308 * maximum number of items, it will delete the oldest image.
309 */
310 void
clipman_history_add_image(ClipmanHistory * history,const GdkPixbuf * image)311 clipman_history_add_image (ClipmanHistory *history,
312 const GdkPixbuf *image)
313 {
314 ClipmanHistoryItem *item;
315
316 if (history->priv->max_images_in_history == 0)
317 return;
318
319 DBG ("Store image (%p)", image);
320
321 item = g_slice_new0 (ClipmanHistoryItem);
322 item->type = CLIPMAN_HISTORY_TYPE_IMAGE;
323 item->content.image = gdk_pixbuf_copy (image);
324 item->preview.image = gdk_pixbuf_scale_simple (GDK_PIXBUF (image), 128, 128, GDK_INTERP_BILINEAR);
325
326 DBG ("Copy of image (%p) is (%p)", image, item->content.image);
327
328 _clipman_history_add_item (history, item);
329 }
330
331 /**
332 * clipman_history_get_list:
333 * @history: a #ClipmanHistory
334 *
335 * Returns a unique list of the images and texts.
336 *
337 * Returns: a newly allocated #GSList that must be freed with g_slist_free()
338 */
339 GSList *
clipman_history_get_list(ClipmanHistory * history)340 clipman_history_get_list (ClipmanHistory *history)
341 {
342 return g_slist_copy (history->priv->items);
343 }
344
345 /**
346 * clipman_history_get_max_texts_in_history:
347 * @history: a #ClipmanHistory
348 *
349 * Returns the most recent item that has been added to #ClipmanHistory.
350 *
351 * Returns: a #const #ClipmanHistoryItem
352 */
353 guint
clipman_history_get_max_texts_in_history(ClipmanHistory * history)354 clipman_history_get_max_texts_in_history (ClipmanHistory *history)
355 {
356 return history->priv->max_texts_in_history;
357 }
358
359 /**
360 * clipman_history_get_item_to_restore:
361 * @history: a #ClipmanHistory
362 *
363 * Returns the most recent item that has been added to #ClipmanHistory.
364 *
365 * Returns: a #const #ClipmanHistoryItem
366 */
367 const ClipmanHistoryItem *
clipman_history_get_item_to_restore(ClipmanHistory * history)368 clipman_history_get_item_to_restore (ClipmanHistory *history)
369 {
370 return history->priv->item_to_restore;
371 }
372
373 /**
374 * clipman_history_set_item_to_restore:
375 * @history: a #ClipmanHistory
376 * @item: a #ClipmanHistoryItem that must exist inside #ClipmanHistory
377 *
378 * This function is used as a hack around the images as they cannot be
379 * compared. Before setting the clipboard with an image, the #ClipmanCollector
380 * must be set as being restored with clipman_collector_set_is_restoring(),
381 * than this function is called with the item that contains the image that is
382 * getting restored.
383 * Instead of being destroyed/recreated inside the history, it will remain at
384 * the same position in the history (unlike being pushed to the top) but will
385 * be marked as being the most recent item in the history.
386 */
387 void
clipman_history_set_item_to_restore(ClipmanHistory * history,const ClipmanHistoryItem * item)388 clipman_history_set_item_to_restore (ClipmanHistory *history,
389 const ClipmanHistoryItem *item)
390 {
391 /* TODO Verify that the item exists in the history */
392 history->priv->item_to_restore = item;
393 }
394
395 /**
396 * clipman_history_clear:
397 * @history: a #ClipmanHistory
398 *
399 * Clears the lists containing the history of the texts and the images.
400 */
401 void
clipman_history_clear(ClipmanHistory * history)402 clipman_history_clear (ClipmanHistory *history)
403 {
404 GSList *list;
405
406 DBG ("Clear the history");
407
408 for (list = history->priv->items; list != NULL; list = list->next)
409 __clipman_history_item_free (list->data);
410
411 g_slist_free (history->priv->items);
412 history->priv->items = NULL;
413 history->priv->item_to_restore = NULL;
414
415 g_signal_emit (history, signals[CLEAR], 0);
416 }
417
418 ClipmanHistory *
clipman_history_get(void)419 clipman_history_get (void)
420 {
421 static ClipmanHistory *singleton = NULL;
422
423 if (singleton == NULL)
424 {
425 singleton = g_object_new (CLIPMAN_TYPE_HISTORY, NULL);
426 g_object_add_weak_pointer (G_OBJECT (singleton), (gpointer)&singleton);
427 }
428 else
429 g_object_ref (G_OBJECT (singleton));
430
431 return singleton;
432 }
433
434 /*
435 * GObject
436 */
437
438 static void
clipman_history_class_init(ClipmanHistoryClass * klass)439 clipman_history_class_init (ClipmanHistoryClass *klass)
440 {
441 GObjectClass *object_class;
442
443 clipman_history_parent_class = g_type_class_peek_parent (klass);
444
445 object_class = G_OBJECT_CLASS (klass);
446 object_class->finalize = clipman_history_finalize;
447 object_class->set_property = clipman_history_set_property;
448 object_class->get_property = clipman_history_get_property;
449
450 signals[ITEM_ADDED] =
451 g_signal_new ("item-added", G_TYPE_FROM_CLASS (klass),
452 G_SIGNAL_RUN_LAST|G_SIGNAL_ACTION,
453 G_STRUCT_OFFSET (ClipmanHistoryClass, item_added),
454 0, NULL, g_cclosure_marshal_VOID__VOID,
455 G_TYPE_NONE, 0);
456
457 signals[CLEAR] =
458 g_signal_new ("clear", G_TYPE_FROM_CLASS (klass),
459 G_SIGNAL_RUN_LAST|G_SIGNAL_ACTION,
460 G_STRUCT_OFFSET (ClipmanHistoryClass, clear),
461 0, NULL, g_cclosure_marshal_VOID__VOID,
462 G_TYPE_NONE, 0);
463
464 g_object_class_install_property (object_class,
465 MAX_TEXTS_IN_HISTORY,
466 g_param_spec_uint ("max-texts-in-history",
467 "MaxTextsInHistory",
468 "The number of maximum texts in history",
469 5, 1000, DEFAULT_MAX_TEXTS_IN_HISTORY,
470 G_PARAM_CONSTRUCT|G_PARAM_READWRITE));
471 g_object_class_install_property (object_class,
472 MAX_IMAGES_IN_HISTORY,
473 g_param_spec_uint ("max-images-in-history",
474 "MaxImagesInHistory",
475 "The number of maximum images in history",
476 0, 5, DEFAULT_MAX_IMAGES_IN_HISTORY,
477 G_PARAM_CONSTRUCT|G_PARAM_READWRITE));
478 g_object_class_install_property (object_class,
479 SAVE_ON_QUIT,
480 g_param_spec_boolean ("save-on-quit",
481 "SaveOnQuit",
482 "True if the history must be saved on quit",
483 DEFAULT_SAVE_ON_QUIT,
484 G_PARAM_CONSTRUCT|G_PARAM_READWRITE));
485
486 g_object_class_install_property (object_class,
487 REORDER_ITEMS,
488 g_param_spec_boolean ("reorder-items",
489 "ReorderItems",
490 "Always push last clipboard content to the top of the history",
491 DEFAULT_REORDER_ITEMS,
492 G_PARAM_CONSTRUCT|G_PARAM_READWRITE));
493 }
494
495 static void
clipman_history_init(ClipmanHistory * history)496 clipman_history_init (ClipmanHistory *history)
497 {
498 history->priv = clipman_history_get_instance_private (history);
499 history->priv->item_to_restore = NULL;
500 }
501
502 static void
clipman_history_finalize(GObject * object)503 clipman_history_finalize (GObject *object)
504 {
505 clipman_history_clear (CLIPMAN_HISTORY (object));
506 G_OBJECT_CLASS (clipman_history_parent_class)->finalize (object);
507 }
508
509 static void
clipman_history_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)510 clipman_history_set_property (GObject *object,
511 guint property_id,
512 const GValue *value,
513 GParamSpec *pspec)
514 {
515 ClipmanHistoryPrivate *priv = CLIPMAN_HISTORY (object)->priv;
516
517 switch (property_id)
518 {
519 case MAX_TEXTS_IN_HISTORY:
520 priv->max_texts_in_history = g_value_get_uint (value);
521 break;
522
523 case MAX_IMAGES_IN_HISTORY:
524 priv->max_images_in_history = g_value_get_uint (value);
525 break;
526
527 case SAVE_ON_QUIT:
528 priv->save_on_quit = g_value_get_boolean (value);
529 break;
530
531 case REORDER_ITEMS:
532 priv->reorder_items = g_value_get_boolean (value);
533 break;
534
535 default:
536 break;
537 }
538 }
539
540 static void
clipman_history_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)541 clipman_history_get_property (GObject *object,
542 guint property_id,
543 GValue *value,
544 GParamSpec *pspec)
545 {
546 ClipmanHistoryPrivate *priv = CLIPMAN_HISTORY (object)->priv;
547
548 switch (property_id)
549 {
550 case MAX_TEXTS_IN_HISTORY:
551 g_value_set_uint (value, priv->max_texts_in_history);
552 break;
553
554 case MAX_IMAGES_IN_HISTORY:
555 g_value_set_uint (value, priv->max_images_in_history);
556 break;
557
558 case SAVE_ON_QUIT:
559 g_value_set_boolean (value, priv->save_on_quit);
560 break;
561
562 case REORDER_ITEMS:
563 g_value_set_boolean (value, priv->reorder_items);
564 break;
565
566 default:
567 break;
568 }
569 }
570