1 /*
2  * ROX-Filer, filer for the ROX desktop project
3  * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17  * Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 /* view_iface.c - operations supported by all views */
21 
22 #include "config.h"
23 
24 #include <string.h>
25 
26 #include "global.h"
27 
28 #include "view_iface.h"
29 #include "diritem.h"
30 
31 /* A word about interfaces:
32  *
33  * gobject's documentation's explanation of interfaces leaves something[1] to
34  * be desired, so I'd better explain here...
35  *
36  * [1] Like, eg, an explanation.
37  *
38  * A ViewIfaceClass is a struct which contains a number of function
39  * pointers. Each class that implements the View interface creates its
40  * own ViewIfaceClass with pointers to its implementation. This is stored
41  * with the class.
42  *
43  * When you want to call a method (eg, sort()) on a View, you call
44  * view_sort(object) here, which gets the class of object and then looks
45  * for that class's implementation of the View interface, and then calls
46  * the actual function through that.
47  */
48 
49 /* ViewIters
50  *
51  * A ViewIter is used to index items. They are usually allocated
52  * on the stack and then initialised using view_get_iter().
53  *
54  * Normally, an iterator starts off not pointing at any item, but
55  * each call to iter->next(iter) returns the next item, and leaves
56  * the iterator pointing at the returned item. If you like the item,
57  * you can then pass the iterator to view_cursor_to_iter(), etc.
58  *
59  * Using flags passed to view_get_iter, you can start the sequence from the
60  * beginning, end, cursor or 'base' (a saved cursor position). You can
61  * go forwards or backwards. You can opt to only get selected items returned.
62  *
63  * You can also have one-shot iterators which already point to an item, and
64  * you never call the next method (view_get_cursor, for example). In fact,
65  * these iterators return a sequence of one item, but next() gets called
66  * automatically for you.
67  *
68  * You don't need to free iterators, and they become invalid if the
69  * View changes (items added, removed or altered), so don't hang on to
70  * them!
71  */
72 
73 /****************************************************************
74  *			EXTERNAL INTERFACE			*
75  ****************************************************************/
76 
view_iface_get_type(void)77 GType view_iface_get_type(void)
78 {
79 	static GType iface_type = 0;
80 
81 	if (!iface_type)
82 	{
83 		static const GTypeInfo iface_info =
84 		{
85 			sizeof (ViewIfaceClass),
86 			NULL,		/* base_init */
87 			NULL,		/* base_finalize */
88 		};
89 
90 		iface_type = g_type_register_static(G_TYPE_INTERFACE,
91 						"ViewIface", &iface_info, 0);
92 
93 		/* Actually, all Views should be GTK_TYPE_WIDGETs, to be more
94 		 * accurate, but including gtk.h takes so long, and noone's
95 		 * going to get this wrong ;-)
96 		 */
97 		g_type_interface_add_prerequisite(iface_type, G_TYPE_OBJECT);
98 	}
99 
100 	return iface_type;
101 }
102 
103 /* The sort function has changed -- resort */
view_sort(ViewIface * obj)104 void view_sort(ViewIface *obj)
105 {
106 	g_return_if_fail(VIEW_IS_IFACE(obj));
107 	VIEW_IFACE_GET_CLASS(obj)->sort(obj);
108 }
109 
110 /* The style has changed -- shrink the grid and redraw.
111  * Also update ViewData (and name layout too) if appropriate
112  * flags are set.
113  */
view_style_changed(ViewIface * obj,int flags)114 void view_style_changed(ViewIface *obj, int flags)
115 {
116 	g_return_if_fail(VIEW_IS_IFACE(obj));
117 
118 	VIEW_IFACE_GET_CLASS(obj)->style_changed(obj, flags);
119 }
120 
121 /* Wink or move the cursor to this item, if present. Return TRUE on
122  * success (iff leaf was present).
123  */
view_autoselect(ViewIface * obj,const gchar * leaf)124 gboolean view_autoselect(ViewIface *obj, const gchar *leaf)
125 {
126 	DirItem *item;
127 	ViewIter iter;
128 
129 	g_return_val_if_fail(VIEW_IS_IFACE(obj), FALSE);
130 	g_return_val_if_fail(leaf != NULL, FALSE);
131 
132 	view_get_iter(obj, &iter, 0);
133 	while ((item = iter.next(&iter)))
134 	{
135 		if (strcmp(item->leafname, leaf) != 0)
136 			continue;
137 
138 		if (view_cursor_visible(obj))
139 			view_cursor_to_iter(obj, &iter);
140 		else
141 			view_wink_item(obj, &iter);
142 
143 		return TRUE;
144 	}
145 
146 	return FALSE;
147 }
148 
149 /* Scanning has turned up some new items... */
view_add_items(ViewIface * obj,GPtrArray * items)150 void view_add_items(ViewIface *obj, GPtrArray *items)
151 {
152 	VIEW_IFACE_GET_CLASS(obj)->add_items(obj, items);
153 }
154 
155 /* These items are already known, but have changed... */
view_update_items(ViewIface * obj,GPtrArray * items)156 void view_update_items(ViewIface *obj, GPtrArray *items)
157 {
158 	VIEW_IFACE_GET_CLASS(obj)->update_items(obj, items);
159 }
160 
161 /* Call test(item) for each item in the view and delete all those for
162  * which it returns TRUE.
163  */
view_delete_if(ViewIface * obj,gboolean (* test)(gpointer item,gpointer data),gpointer data)164 void view_delete_if(ViewIface *obj,
165 		    gboolean (*test)(gpointer item, gpointer data),
166 		    gpointer data)
167 {
168 	g_return_if_fail(VIEW_IS_IFACE(obj));
169 
170 	VIEW_IFACE_GET_CLASS(obj)->delete_if(obj, test, data);
171 }
172 
173 /* Remove all items from the view (used when changing directory) */
view_clear(ViewIface * obj)174 void view_clear(ViewIface *obj)
175 {
176 	g_return_if_fail(VIEW_IS_IFACE(obj));
177 
178 	VIEW_IFACE_GET_CLASS(obj)->clear(obj);
179 }
180 
181 /* Select all items */
view_select_all(ViewIface * obj)182 void view_select_all(ViewIface *obj)
183 {
184 	g_return_if_fail(VIEW_IS_IFACE(obj));
185 
186 	VIEW_IFACE_GET_CLASS(obj)->select_all(obj);
187 }
188 
189 /* Unselect all items */
view_clear_selection(ViewIface * obj)190 void view_clear_selection(ViewIface *obj)
191 {
192 	g_return_if_fail(VIEW_IS_IFACE(obj));
193 
194 	VIEW_IFACE_GET_CLASS(obj)->clear_selection(obj);
195 }
196 
197 /* Return the total number of items */
view_count_items(ViewIface * obj)198 int view_count_items(ViewIface *obj)
199 {
200 	g_return_val_if_fail(VIEW_IS_IFACE(obj), 0);
201 
202 	return VIEW_IFACE_GET_CLASS(obj)->count_items(obj);
203 }
204 
205 /* Return the number of selected items */
view_count_selected(ViewIface * obj)206 int view_count_selected(ViewIface *obj)
207 {
208 	g_return_val_if_fail(VIEW_IS_IFACE(obj), 0);
209 
210 	return VIEW_IFACE_GET_CLASS(obj)->count_selected(obj);
211 }
212 
view_show_cursor(ViewIface * obj)213 void view_show_cursor(ViewIface *obj)
214 {
215 	g_return_if_fail(VIEW_IS_IFACE(obj));
216 
217 	VIEW_IFACE_GET_CLASS(obj)->show_cursor(obj);
218 }
219 
220 /* Create an iterator which will return each element selected by 'flags'
221  * from successive calls to iter.next(&iter). NULL indicates the end
222  * of the sequence.
223  *
224  * The iterator does not need to be freed. It becomes invalid if the
225  * view is changed in any way.
226  */
view_get_iter(ViewIface * obj,ViewIter * iter,IterFlags flags)227 void view_get_iter(ViewIface *obj, ViewIter *iter, IterFlags flags)
228 {
229 	g_return_if_fail(VIEW_IS_IFACE(obj));
230 	g_return_if_fail(iter != NULL);
231 
232 	VIEW_IFACE_GET_CLASS(obj)->get_iter(obj, iter, flags);
233 }
234 
235 /* Make an 'iter' for the cursor item, if any. Use iter->peek() to get
236  * the DirItem (will be NULL if the cursor isn't on an item).
237  */
view_get_cursor(ViewIface * obj,ViewIter * iter)238 void view_get_cursor(ViewIface *obj, ViewIter *iter)
239 {
240 	g_return_if_fail(VIEW_IS_IFACE(obj));
241 	g_return_if_fail(iter != NULL);
242 
243 	VIEW_IFACE_GET_CLASS(obj)->get_iter(obj, iter,
244 				VIEW_ITER_FROM_CURSOR | VIEW_ITER_ONE_ONLY);
245 }
246 
247 /* Position cursor on the last item returned by iter.next().
248  * If iter is NULL, remove the cursor.
249  */
view_cursor_to_iter(ViewIface * obj,ViewIter * iter)250 void view_cursor_to_iter(ViewIface *obj, ViewIter *iter)
251 {
252 	g_return_if_fail(VIEW_IS_IFACE(obj));
253 
254 	VIEW_IFACE_GET_CLASS(obj)->cursor_to_iter(obj, iter);
255 }
256 
257 /* Select the item at this iter */
view_set_selected(ViewIface * obj,ViewIter * iter,gboolean selected)258 void view_set_selected(ViewIface *obj, ViewIter *iter, gboolean selected)
259 {
260 	g_return_if_fail(VIEW_IS_IFACE(obj));
261 
262 	VIEW_IFACE_GET_CLASS(obj)->set_selected(obj, iter, selected);
263 }
264 
265 /* TRUE if this item is selected */
view_get_selected(ViewIface * obj,ViewIter * iter)266 gboolean view_get_selected(ViewIface *obj, ViewIter *iter)
267 {
268 	g_return_val_if_fail(VIEW_IS_IFACE(obj), FALSE);
269 
270 	return VIEW_IFACE_GET_CLASS(obj)->get_selected(obj, iter);
271 }
272 
273 /* Flash / draw attention to this item */
view_wink_item(ViewIface * obj,ViewIter * iter)274 void view_wink_item(ViewIface *obj, ViewIter *iter)
275 {
276 	g_return_if_fail(VIEW_IS_IFACE(obj));
277 
278 	VIEW_IFACE_GET_CLASS(obj)->wink_item(obj, iter);
279 }
280 
281 /* Clear the selection, then select this item. Does it atomically to avoid
282  * problems with giving up and quickly reclaiming the primary selection.
283  */
view_select_only(ViewIface * obj,ViewIter * iter)284 void view_select_only(ViewIface *obj, ViewIter *iter)
285 {
286 	g_return_if_fail(VIEW_IS_IFACE(obj));
287 
288 	VIEW_IFACE_GET_CLASS(obj)->select_only(obj, iter);
289 }
290 
view_select_if(ViewIface * obj,gboolean (* test)(ViewIter * iter,gpointer data),gpointer data)291 void view_select_if(ViewIface *obj,
292 		    gboolean (*test)(ViewIter *iter, gpointer data),
293 		    gpointer data)
294 {
295 	ViewIter iter;
296 	gboolean should_select_first;
297 
298 	g_return_if_fail(VIEW_IS_IFACE(obj));
299 
300 	view_get_iter(obj, &iter, 0);
301 
302 	if (!iter.next(&iter))
303 		return;		/* No items */
304 
305 	view_freeze(obj);
306 
307 	/* If anything is currently selected then select the first item now
308 	 * and set it to its correct value at the end (avoids losing the
309 	 * primary and regaining it quickly). Do the test first in case it
310 	 * relies on the selected state!
311 	 */
312 	should_select_first = test(&iter, data);
313 	if (view_count_selected(obj))
314 		view_set_selected(obj, &iter, TRUE);
315 
316 	while (iter.next(&iter))
317 		view_set_selected(obj, &iter, test(&iter, data));
318 
319 	view_get_iter(obj, &iter, 0);
320 	iter.next(&iter);
321 	view_set_selected(obj, &iter, should_select_first);
322 
323 	view_thaw(obj);
324 }
325 
326 /* Prevent selection_changed events from being emitted */
view_freeze(ViewIface * obj)327 void view_freeze(ViewIface *obj)
328 {
329 	g_return_if_fail(VIEW_IS_IFACE(obj));
330 
331 	VIEW_IFACE_GET_CLASS(obj)->set_frozen(obj, TRUE);
332 }
333 
334 /* Undo a view_freeze (and emit the changed signal) */
view_thaw(ViewIface * obj)335 void view_thaw(ViewIface *obj)
336 {
337 	g_return_if_fail(VIEW_IS_IFACE(obj));
338 
339 	VIEW_IFACE_GET_CLASS(obj)->set_frozen(obj, FALSE);
340 }
341 
342 /* Resize the filer window to a sensible size.
343  * v_border is the height of the toolbar + the minibuffer (if visible).
344  * space is
345  * If allow_shrink is
346  */
view_autosize(ViewIface * obj)347 void view_autosize(ViewIface *obj)
348 {
349 	g_return_if_fail(VIEW_IS_IFACE(obj));
350 
351 	VIEW_IFACE_GET_CLASS(obj)->autosize(obj);
352 }
353 
354 /* Return TRUE if the cursor is shown. Note that the cursor may be visible
355  * even if their are no items (so get_cursor().peek() would return NULL).
356  */
view_cursor_visible(ViewIface * obj)357 gboolean view_cursor_visible(ViewIface *obj)
358 {
359 	g_return_val_if_fail(VIEW_IS_IFACE(obj), FALSE);
360 
361 	return VIEW_IFACE_GET_CLASS(obj)->cursor_visible(obj);
362 }
363 
364 /* The 'base' position is used to record the position of the cursor
365  * when the minibuffer is opened, for interactive searching.
366  */
view_set_base(ViewIface * obj,ViewIter * iter)367 void view_set_base(ViewIface *obj, ViewIter *iter)
368 {
369 	g_return_if_fail(VIEW_IS_IFACE(obj));
370 
371 	VIEW_IFACE_GET_CLASS(obj)->set_base(obj, iter);
372 }
373 
374 /* Returns an interator which yields just the item under the pointer.
375  * iter.peek() will return NULL if no item was under the pointer.
376  * x, y is relative to 'window'.
377  */
view_get_iter_at_point(ViewIface * obj,ViewIter * iter,GdkWindow * window,int x,int y)378 void view_get_iter_at_point(ViewIface *obj, ViewIter *iter,
379 			    GdkWindow *window, int x, int y)
380 {
381 	g_return_if_fail(VIEW_IS_IFACE(obj));
382 
383 	VIEW_IFACE_GET_CLASS(obj)->get_iter_at_point(obj, iter, window, x, y);
384 }
385 
386 /* Begin a drag to select a group of icons */
view_start_lasso_box(ViewIface * obj,GdkEventButton * event)387 void view_start_lasso_box(ViewIface *obj, GdkEventButton *event)
388 {
389 	g_return_if_fail(VIEW_IS_IFACE(obj));
390 
391 	VIEW_IFACE_GET_CLASS(obj)->start_lasso_box(obj, event);
392 }
393 
394 /* Add anything useful to the tooltip string. Used to include the name of
395  * items where the name is shown truncated.
396  */
view_extend_tip(ViewIface * obj,ViewIter * iter,GString * tip)397 void view_extend_tip(ViewIface *obj, ViewIter *iter, GString *tip)
398 {
399 	g_return_if_fail(VIEW_IS_IFACE(obj));
400 
401 	VIEW_IFACE_GET_CLASS(obj)->extend_tip(obj, iter, tip);
402 }
403 
404 /* This is called frequently while auto_scroll is on.
405  * Checks the pointer position and scrolls the window if it's
406  * near the top or bottom.
407  */
view_auto_scroll_callback(ViewIface * obj)408 gboolean view_auto_scroll_callback(ViewIface *obj)
409 {
410 	g_return_val_if_fail(VIEW_IS_IFACE(obj), FALSE);
411 
412 	return VIEW_IFACE_GET_CLASS(obj)->auto_scroll_callback(obj);
413 }
414 
415