1 /*
2  *      fm-nav-history.c
3  *
4  *      Copyright 2009 PCMan <pcman.tw@gmail.com>
5  *      Copyright 2012 Andriy Grytsenko (LStranger) <andrej@rep.kiev.ua>
6  *
7  *      This file is a part of the Libfm library.
8  *
9  *      This library is free software; you can redistribute it and/or
10  *      modify it under the terms of the GNU Lesser General Public
11  *      License as published by the Free Software Foundation; either
12  *      version 2.1 of the License, or (at your option) any later version.
13  *
14  *      This library is distributed in the hope that it will be useful,
15  *      but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  *      Lesser General Public License for more details.
18  *
19  *      You should have received a copy of the GNU Lesser General Public
20  *      License along with this library; if not, write to the Free Software
21  *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22  */
23 
24 /**
25  * SECTION:fm-nav-history
26  * @short_description: Simple navigation history management.
27  * @title: FmNavHistory
28  *
29  * @include: libfm/fm.h
30  *
31  * The #FmNavHistory object implements history for paths that were
32  * entered in some input bar and allows to add, remove or move items in it.
33  */
34 
35 #include "fm-nav-history.h"
36 
37 struct _FmNavHistory
38 {
39     GObject parent;
40     GQueue items;
41     GList* cur;
42     gint n_max;
43     guint n_cur;
44 };
45 
46 struct _FmNavHistoryClass
47 {
48     GObjectClass parent_class;
49 };
50 
51 static void fm_nav_history_finalize (GObject *object);
52 
53 G_DEFINE_TYPE(FmNavHistory, fm_nav_history, G_TYPE_OBJECT);
54 
55 
fm_nav_history_class_init(FmNavHistoryClass * klass)56 static void fm_nav_history_class_init(FmNavHistoryClass *klass)
57 {
58     GObjectClass *g_object_class;
59 
60     g_object_class = G_OBJECT_CLASS(klass);
61     g_object_class->finalize = fm_nav_history_finalize;
62 }
63 
fm_nav_history_item_free(FmNavHistoryItem * item)64 static void fm_nav_history_item_free(FmNavHistoryItem* item)
65 {
66     fm_path_unref(item->path);
67     g_slice_free(FmNavHistoryItem, item);
68 }
69 
fm_nav_history_finalize(GObject * object)70 static void fm_nav_history_finalize(GObject *object)
71 {
72     FmNavHistory *self;
73 
74     g_return_if_fail(object != NULL);
75     g_return_if_fail(FM_IS_NAV_HISTORY(object));
76 
77     self = FM_NAV_HISTORY(object);
78     g_queue_foreach(&self->items, (GFunc)fm_nav_history_item_free, NULL);
79     g_queue_clear(&self->items);
80 
81     G_OBJECT_CLASS(fm_nav_history_parent_class)->finalize(object);
82 }
83 
fm_nav_history_init(FmNavHistory * self)84 static void fm_nav_history_init(FmNavHistory *self)
85 {
86     g_queue_init(&self->items);
87     self->n_max = FM_NAV_HISTORY_DEFAULT_SIZE;
88 }
89 
90 /**
91  * fm_nav_history_new
92  *
93  * Creates a new #FmNavHistory object with empty history.
94  *
95  * Returns: a new #FmNavHistory object.
96  *
97  * Since: 0.1.0
98  */
fm_nav_history_new(void)99 FmNavHistory *fm_nav_history_new(void)
100 {
101     return g_object_new(FM_NAV_HISTORY_TYPE, NULL);
102 }
103 
104 /**
105  * fm_nav_history_list
106  * @nh: the history
107  *
108  * Retrieves full list of the history as #GList of #FmNavHistoryItem.
109  * The returned #GList belongs to #FmNavHistory and shouldn't be freed.
110  *
111  * Returns: (transfer none) (element-type FmNavHistoryItem): full history.
112  *
113  * Since: 0.1.0
114  *
115  * Deprecated: 1.0.2:
116  */
fm_nav_history_list(FmNavHistory * nh)117 const GList* fm_nav_history_list(FmNavHistory* nh)
118 {
119     return nh->items.head;
120 }
121 
122 /**
123  * fm_nav_history_get_cur
124  * @nh: the history
125  *
126  * Retrieves current selected item of the history. The returned item
127  * belongs to #FmNavHistory and shouldn't be freed by caller.
128  *
129  * Returns: (transfer none): current item.
130  *
131  * Since: 0.1.0
132  *
133  * Deprecated: 1.0.2: Use fm_nav_history_get_nth_path() instead.
134  */
fm_nav_history_get_cur(FmNavHistory * nh)135 const FmNavHistoryItem* fm_nav_history_get_cur(FmNavHistory* nh)
136 {
137     return nh->cur ? (FmNavHistoryItem*)nh->cur->data : NULL;
138 }
139 
140 /**
141  * fm_nav_history_get_cur_link
142  * @nh: the history
143  *
144  * Retrieves current selected item as #GList element containing
145  * #FmNavHistoryItem. The returned item belongs to #FmNavHistory and
146  * shouldn't be freed by caller.
147  *
148  * Returns: (transfer none) (element-type FmNavHistoryItem): current item.
149  *
150  * Since: 0.1.0
151  *
152  * Deprecated: 1.0.2:
153  */
fm_nav_history_get_cur_link(FmNavHistory * nh)154 const GList* fm_nav_history_get_cur_link(FmNavHistory* nh)
155 {
156     return nh->cur;
157 }
158 
159 /**
160  * fm_nav_history_can_forward
161  * @nh: the history
162  *
163  * Checks if current selected item is the last item in the history.
164  *
165  * Before 1.0.0 this call had name fm_nav_history_get_can_forward.
166  *
167  * Returns: %TRUE if cursor can go forward in history.
168  *
169  * Since: 0.1.0
170  *
171  * Deprecated: 1.0.2: Use fm_nav_history_get_cur_index() instead.
172  */
fm_nav_history_can_forward(FmNavHistory * nh)173 gboolean fm_nav_history_can_forward(FmNavHistory* nh)
174 {
175     return nh->n_cur > 0;
176 }
177 
178 /**
179  * fm_nav_history_forward
180  * @nh: the history
181  * @old_scroll_pos: the scroll position to associate with current item
182  *
183  * If there is a previous item in the history then sets @old_scroll_pos
184  * into current item data and marks previous item current.
185  *
186  * Since: 0.1.0
187  *
188  * Deprecated: 1.0.2: Use fm_nav_history_go_to() instead.
189  */
fm_nav_history_forward(FmNavHistory * nh,int old_scroll_pos)190 void fm_nav_history_forward(FmNavHistory* nh, int old_scroll_pos)
191 {
192     if(nh->cur && nh->cur->prev)
193     {
194         FmNavHistoryItem* tmp = (FmNavHistoryItem*)nh->cur->data;
195         if(tmp) /* remember current scroll pos */
196             tmp->scroll_pos = old_scroll_pos;
197         nh->cur = nh->cur->prev;
198         nh->n_cur--;
199     }
200 }
201 
202 /**
203  * fm_nav_history_can_back
204  * @nh: the history
205  *
206  * Checks if current selected item is the first item in the history.
207  *
208  * Before 1.0.0 this call had name fm_nav_history_get_can_back.
209  *
210  * Returns: %TRUE if cursor can go backward in history.
211  *
212  * Since: 0.1.0
213  */
fm_nav_history_can_back(FmNavHistory * nh)214 gboolean fm_nav_history_can_back(FmNavHistory* nh)
215 {
216     g_return_val_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh), FALSE);
217 
218     return nh->cur ? (nh->cur->next != NULL) : FALSE;
219 }
220 
221 /**
222  * fm_nav_history_back
223  * @nh: the history
224  * @old_scroll_pos: the scroll position to associate with current item
225  *
226  * If there is a next item in the history then sets @old_scroll_pos into
227  * current item data and marks next item current.
228  *
229  * Since: 0.1.0
230  *
231  * Deprecated: 1.0.2: Use fm_nav_history_go_to() instead.
232  */
fm_nav_history_back(FmNavHistory * nh,int old_scroll_pos)233 void fm_nav_history_back(FmNavHistory* nh, int old_scroll_pos)
234 {
235     if(nh->cur && nh->cur->next)
236     {
237         FmNavHistoryItem* tmp = (FmNavHistoryItem*)nh->cur->data;
238         if(tmp) /* remember current scroll pos */
239             tmp->scroll_pos = old_scroll_pos;
240         nh->cur = nh->cur->next;
241         nh->n_cur++;
242     }
243 }
244 
cut_history(FmNavHistory * nh,guint num)245 static inline void cut_history(FmNavHistory* nh, guint num)
246 {
247     while(g_queue_get_length(&nh->items) > num)
248     {
249         FmNavHistoryItem* item = (FmNavHistoryItem*)g_queue_pop_tail(&nh->items);
250         fm_nav_history_item_free(item);
251     }
252 }
253 
254 /**
255  * fm_nav_history_chdir
256  * @nh: the history
257  * @path: new path to add
258  * @old_scroll_pos: the scroll position to associate with current item
259  *
260  * Sets @old_scroll_pos into current item data and then adds new @path
261  * to the beginning of the @nh.
262  *
263  * Since: 0.1.0
264  */
fm_nav_history_chdir(FmNavHistory * nh,FmPath * path,gint old_scroll_pos)265 void fm_nav_history_chdir(FmNavHistory* nh, FmPath* path, gint old_scroll_pos)
266 {
267     FmNavHistoryItem* tmp;
268 
269     g_return_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh));
270 
271     /* now we're at the top of the queue. */
272     tmp = nh->cur ? (FmNavHistoryItem*)nh->cur->data : NULL;
273     if(tmp) /* remember current scroll pos */
274         tmp->scroll_pos = old_scroll_pos;
275 
276     if( !tmp || !fm_path_equal(tmp->path, path) ) /* we're not chdir to the same path */
277     {
278         /* if we're not at the top of the queue, remove all items beyond us. */
279         while(nh->n_cur > 0)
280         {
281             tmp = (FmNavHistoryItem*)g_queue_pop_head(&nh->items);
282             if(tmp)
283                 fm_nav_history_item_free(tmp);
284             nh->n_cur--;
285         }
286 
287         /* add a new item */
288         tmp = g_slice_new0(FmNavHistoryItem);
289         tmp->path = fm_path_ref(path);
290         g_queue_push_head(&nh->items, tmp);
291         nh->cur = g_queue_peek_head_link(&nh->items);
292         cut_history(nh, nh->n_max);
293     }
294 }
295 
296 /**
297  * fm_nav_history_jump
298  * @nh: the history
299  * @l: (element-type FmNavHistoryItem): new current item
300  * @old_scroll_pos: the scroll position to associate with current item
301  *
302  * Sets @old_scroll_pos into current item data and then
303  * sets current item of @nh to one from @l.
304  *
305  * Since: 0.1.0
306  *
307  * Deprecated: 1.0.2: Use fm_nav_history_go_to() instead.
308  */
fm_nav_history_jump(FmNavHistory * nh,GList * l,int old_scroll_pos)309 void fm_nav_history_jump(FmNavHistory* nh, GList* l, int old_scroll_pos)
310 {
311     gint n = g_queue_index(&nh->items, l->data);
312     if(n >= 0)
313         fm_nav_history_go_to(nh, n, old_scroll_pos);
314 }
315 
316 /**
317  * fm_nav_history_clear
318  * @nh: the history
319  *
320  * Removes all items from the history @nh.
321  *
322  * Since: 0.1.0
323  */
fm_nav_history_clear(FmNavHistory * nh)324 void fm_nav_history_clear(FmNavHistory* nh)
325 {
326     g_return_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh));
327     g_queue_foreach(&nh->items, (GFunc)fm_nav_history_item_free, NULL);
328     g_queue_clear(&nh->items);
329     nh->cur = NULL;
330     nh->n_cur = 0;
331 }
332 
333 /**
334  * fm_nav_history_set_max
335  * @nh: the history
336  * @num: new size of history
337  *
338  * Sets maximum length of the history @nh to be @num.
339  *
340  * Since: 0.1.0
341  */
fm_nav_history_set_max(FmNavHistory * nh,guint num)342 void fm_nav_history_set_max(FmNavHistory* nh, guint num)
343 {
344     g_return_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh));
345     if(num <= nh->n_cur)
346     {
347         nh->cur = NULL;
348         nh->n_cur = 0;
349     }
350     nh->n_max = num;
351     if(num < 1)
352         num = 1;
353     cut_history(nh, num);
354 }
355 
356 /**
357  * fm_nav_history_get_cur_index
358  * @nh: the history
359  *
360  * Retrieves index of current item in the history @nh. 0 means current
361  * item is at top.
362  *
363  * Returns: index of current item.
364  *
365  * Since: 1.0.2
366  */
fm_nav_history_get_cur_index(FmNavHistory * nh)367 guint fm_nav_history_get_cur_index(FmNavHistory* nh)
368 {
369     g_return_val_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh), 0);
370     return nh->n_cur;
371 }
372 
373 /**
374  * fm_nav_history_get_nth_path
375  * @nh: the history
376  * @n: index of item
377  *
378  * Retrieves path of the item @n in the history @nh.
379  *
380  * Returns: path of the item or %NULL if no such item was found.
381  *
382  * Since: 1.0.2
383  */
fm_nav_history_get_nth_path(FmNavHistory * nh,guint n)384 FmPath* fm_nav_history_get_nth_path(FmNavHistory* nh, guint n)
385 {
386     FmNavHistoryItem *item;
387 
388     g_debug("fm_nav_history_get_nth_path %u", n);
389     g_return_val_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh), NULL);
390     if(n == nh->n_cur)
391         item = nh->cur->data;
392     else
393         item = g_queue_peek_nth(&nh->items, n);
394     if(item == NULL)
395         return NULL;
396     return item->path;
397 }
398 
399 /**
400  * fm_nav_history_go_to
401  * @nh: the history
402  * @n: new index
403  * @old_scroll_pos: scroll position of current folder view
404  *
405  * Saves the current scroll position into the history. If item with index
406  * @n exists in the history then sets it as current item.
407  *
408  * Returns: path of selected item or %NULL if no such item was found.
409  *
410  * Since: 1.0.2
411  */
fm_nav_history_go_to(FmNavHistory * nh,guint n,gint old_scroll_pos)412 FmPath* fm_nav_history_go_to(FmNavHistory* nh, guint n, gint old_scroll_pos)
413 {
414     GList *link;
415 
416     g_return_val_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh), NULL);
417     if(nh->cur)
418         ((FmNavHistoryItem*)nh->cur->data)->scroll_pos = old_scroll_pos;
419     if(n == nh->n_cur)
420         return ((FmNavHistoryItem*)nh->cur->data)->path;
421     link = g_queue_peek_nth_link(&nh->items, n);
422     if(link == NULL)
423         return NULL;
424     nh->n_cur = n;
425     nh->cur = link;
426     return ((FmNavHistoryItem*)link->data)->path;
427 }
428 
429 /**
430  * fm_nav_history_get_scroll_pos
431  * @nh: the history
432  *
433  * Retrieves saved scroll position for current item.
434  *
435  * Returns: saved scroll position.
436  *
437  * Since: 1.0.2
438  */
fm_nav_history_get_scroll_pos(FmNavHistory * nh)439 gint fm_nav_history_get_scroll_pos(FmNavHistory* nh)
440 {
441     g_return_val_if_fail(nh != NULL && FM_IS_NAV_HISTORY(nh), -1);
442     return ((FmNavHistoryItem*)nh->cur->data)->scroll_pos;
443 }
444