1 /**
2 * This file is a part of the Cairo-Dock project
3 *
4 * Copyright : (C) see the 'copyright' file.
5 * E-mail    : see the 'copyright' file.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 
23 #define __USE_BSD 1
24 #include <string.h>
25 #include <dirent.h>
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
30 
31 #include "applet-struct.h"
32 #include "applet-listing.h"
33 
34 #define NB_STEPS 12
35 #define DELTA 4
36 #define EPSILON 4
37 #define NB_STEPS_FOR_1_ENTRY 6
38 #define NB_STEPS_LATE 1
39 #define _listing_compute_nb_steps(pListing) (NB_STEPS_FOR_1_ENTRY + NB_STEPS_LATE * (MIN (myConfig.iNbLinesInListing, pListing->iNbEntries) - 1))
40 #define _listing_compute_width(pListing) (.4 * g_desktopGeometry.Xscreen.width)
41 #define _listing_compute_height(pListing) ((myDialogsParam.dialogTextDescription.iSize + 2) * (myConfig.iNbLinesInListing + 5) + 2*GAP)
42 #define NB_STEPS_FOR_CURRENT_ENTRY 8
43 #define NB_STEPS_FOR_SCROLL 2
44 #define GAP 3
45 
46   /////////////////////////////////////////
47  /// Definition du container CDListing ///
48 /////////////////////////////////////////
49 
cd_do_free_entry(CDEntry * pEntry)50 void cd_do_free_entry (CDEntry *pEntry)
51 {
52 	if (pEntry == NULL)
53 		return ;
54 
55 	g_free (pEntry->cPath);
56 	g_free (pEntry->cName);
57 	g_free (pEntry->cLowerCaseName);
58 	g_free (pEntry->cIconName);
59 	cairo_surface_destroy (pEntry->pIconSurface);
60 }
61 
cd_do_free_listing_backup(CDListingBackup * pBackup)62 void cd_do_free_listing_backup (CDListingBackup *pBackup)
63 {
64 	if (pBackup == NULL)
65 		return ;
66 
67 	g_list_foreach (pBackup->pEntries, (GFunc)cd_do_free_entry, NULL);
68 	g_list_free (pBackup->pEntries);
69 	g_free (pBackup);
70 }
71 
on_expose_listing(GtkWidget * pWidget,cairo_t * ctx,CDListing * pListing)72 static gboolean on_expose_listing (GtkWidget *pWidget, cairo_t *ctx, CDListing *pListing)
73 {
74 	/**if (g_bUseOpenGL && pListing->container.glContext)
75 	{
76 		if (! gldi_glx_begin_draw_container (CAIRO_CONTAINER (pListing)))
77 			return FALSE;
78 
79 		if (pExpose->area.x + pExpose->area.y != 0)
80 		{
81 			glEnable (GL_SCISSOR_TEST);
82 			glScissor ((int) pExpose->area.x,
83 				(int) (pListing->container.bIsHorizontal ? pListing->container.iHeight : pListing->container.iWidth) -
84 					pExpose->area.y - pExpose->area.height,  // lower left corner of the scissor box.
85 				(int) pExpose->area.width,
86 				(int) pExpose->area.height);
87 		}
88 
89 		gldi_object_notify (CAIRO_CONTAINER (pListing), NOTIFICATION_RENDER, pListing, NULL);
90 
91 		glDisable (GL_SCISSOR_TEST);
92 
93 		gldi_glx_end_draw_container (CAIRO_CONTAINER (pListing));
94 	}
95 	else*/
96 	{
97 		gldi_object_notify (CAIRO_CONTAINER (pListing), NOTIFICATION_RENDER, pListing, ctx);
98 	}
99 	return FALSE;
100 }
on_configure_listing(GtkWidget * pWidget,GdkEventConfigure * pEvent,CDListing * pListing)101 static gboolean on_configure_listing (GtkWidget* pWidget, GdkEventConfigure* pEvent, CDListing *pListing)
102 {
103 	gint iNewWidth, iNewHeight;
104 	if (pListing->container.bIsHorizontal)
105 	{
106 		pListing->container.iWindowPositionX = pEvent->x;
107 		pListing->container.iWindowPositionY = pEvent->y;
108 		iNewWidth = pEvent->width;
109 		iNewHeight = pEvent->height;
110 	}
111 	else
112 	{
113 		pListing->container.iWindowPositionX = pEvent->y;
114 		pListing->container.iWindowPositionY = pEvent->x;
115 		iNewWidth = pEvent->height;
116 		iNewHeight = pEvent->width;
117 	}
118 
119 	if (pListing->container.iWidth != iNewWidth || pListing->container.iHeight != iNewHeight)
120 	{
121 		pListing->container.iWidth = iNewWidth;
122 		pListing->container.iHeight = iNewHeight;
123 
124 		/*if (g_bUseOpenGL && pListing->container.glContext)
125 		{
126 			GLsizei w = pEvent->width;
127 			GLsizei h = pEvent->height;
128 			if (! gldi_gl_container_begin_draw (CAIRO_CONTAINER (pListing)))
129 				return FALSE;
130 
131 			glViewport(0, 0, w, h);
132 
133 			cairo_dock_set_ortho_view (CAIRO_CONTAINER (pListing));
134 		}*/
135 	}
136 	return FALSE;
137 }
138 
on_key_press_listing(GtkWidget * pWidget,GdkEventKey * pKey,CDListing * pListing)139 static gboolean on_key_press_listing (GtkWidget *pWidget, GdkEventKey *pKey, CDListing *pListing)
140 {
141 	if (pKey->type == GDK_KEY_PRESS)
142 	{
143 		gldi_object_notify (CAIRO_CONTAINER (pListing), NOTIFICATION_KEY_PRESSED, pListing, pKey->keyval, pKey->state, pKey->string);
144 	}
145 	return FALSE;
146 }
147 /*static gboolean on_motion_notify_listing (GtkWidget* pWidget, GdkEventMotion* pMotion, CDListing *pListing)
148 {
149 	pListing->container.iMouseX = pMotion->x;
150 	pListing->container.iMouseY = pMotion->y;
151 
152 	gboolean bStartAnimation = FALSE;
153 	cairo_dock_notify_on_container (pListing, CAIRO_DOCK_MOUSE_MOVED, pListing, &bStartAnimation);
154 	if (bStartAnimation)
155 		cairo_dock_launch_animation (CAIRO_CONTAINER (pListing));
156 
157 	gdk_device_get_state (pMotion->device, pMotion->window, NULL, NULL);  // pour recevoir d'autres MotionNotify.
158 	return FALSE;
159 }*/
_place_listing(CDListing * pListing)160 static inline void _place_listing (CDListing *pListing)
161 {
162 	int iX, iY;
163 	if (g_pMainDock->container.bIsHorizontal)
164 	{
165 		iX = g_pMainDock->container.iWindowPositionX + g_pMainDock->container.iWidth/2 - pListing->container.iWidth/2;
166 		iY = g_pMainDock->container.iWindowPositionY + (g_pMainDock->container.bDirectionUp ? - pListing->container.iHeight : g_pMainDock->container.iHeight);
167 	}
168 	else
169 	{
170 		iX = g_pMainDock->container.iWindowPositionY + (g_pMainDock->container.bDirectionUp ? - pListing->container.iWidth : g_pMainDock->container.iHeight);
171 		iY = g_pMainDock->container.iWindowPositionX + g_pMainDock->container.iWidth/2 - pListing->container.iHeight/2;
172 	}
173 	cd_debug ("(%d;%d) %dx%d", iX, iY, pListing->container.iWidth, pListing->container.iHeight);
174 	gtk_window_move (GTK_WINDOW (pListing->container.pWidget), iX, iY);
175 }
cd_do_create_listing(void)176 CDListing *cd_do_create_listing (void)
177 {
178 	CDListing *pListing = g_new0 (CDListing, 1);
179 	GldiContainerAttr attr;
180 	memset (&attr, 0, sizeof (GldiContainerAttr));
181 	gldi_object_init (GLDI_OBJECT(pListing), &myContainerObjectMgr, &attr);
182 
183 	/*pListing->container.iType = CAIRO_DOCK_NB_CONTAINER_TYPES+1;
184 	pListing->container.bIsHorizontal = TRUE;
185 	pListing->container.bDirectionUp = TRUE;
186 	pListing->container.fRatio = 1.;
187 	GtkWidget *pWindow = cairo_dock_init_container_no_opengl (CAIRO_CONTAINER (pListing));*/
188 	GtkWidget *pWindow = pListing->container.pWidget;
189 	gtk_window_set_title (GTK_WINDOW (pWindow), "cairo-dock-listing");
190 	//gtk_widget_add_events (pWindow, GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
191 	g_signal_connect (G_OBJECT (pWindow),
192 		"draw",
193 		G_CALLBACK (on_expose_listing),
194 		pListing);
195 	g_signal_connect (G_OBJECT (pWindow),
196 		"configure-event",
197 		G_CALLBACK (on_configure_listing),
198 		pListing);
199 	g_signal_connect (G_OBJECT (pWindow),
200 		"key-press-event",
201 		G_CALLBACK (on_key_press_listing),
202 		pListing);
203 	/*g_signal_connect (G_OBJECT (pWindow),
204 		"motion-notify-event",
205 		G_CALLBACK (on_motion_notify_listing),
206 		pListing);
207 	g_signal_connect (G_OBJECT (pWindow),
208 		"button-press-event",
209 		G_CALLBACK (on_button_press_listing),
210 		pListing);
211 	g_signal_connect (G_OBJECT (pWindow),
212 		"scroll-event",
213 		G_CALLBACK (on_scroll_listing),
214 		pListing);*/
215 	pListing->container.pWidget = pWindow;
216 
217 	gtk_widget_show_all (pWindow);
218 	gtk_window_stick (GTK_WINDOW (pWindow));
219 	gtk_window_set_keep_above (GTK_WINDOW (pWindow), TRUE);
220 	gtk_window_set_transient_for (GTK_WINDOW (pWindow), GTK_WINDOW (g_pMainDock->container.pWidget));
221 	gtk_window_set_modal (GTK_WINDOW (pWindow), TRUE);
222 
223 	pListing->container.iWidth = _listing_compute_width (pListing);
224 	pListing->container.iHeight = _listing_compute_height (pListing);
225 	gtk_window_resize (GTK_WINDOW (pWindow),
226 		pListing->container.iWidth,
227 		pListing->container.iHeight);
228 
229 	_place_listing (pListing);
230 
231 	return pListing;
232 }
233 
cd_do_destroy_listing(CDListing * pListing)234 void cd_do_destroy_listing (CDListing *pListing)
235 {
236 	if (pListing == NULL)
237 		return;
238 
239 	if (pListing->iSidFillEntries != 0)
240 		g_source_remove (pListing->iSidFillEntries);
241 
242 	gldi_object_unref (GLDI_OBJECT(pListing));
243 }
244 
245   ////////////////////////
246  /// Fonctions utiles ///
247 ////////////////////////
248 
cd_do_update_listing_notification(gpointer pUserData,CDListing * pListing,gboolean * bContinueAnimation)249 gboolean cd_do_update_listing_notification (gpointer pUserData, CDListing *pListing, gboolean *bContinueAnimation)
250 {
251 	//g_print ("%s ()\n", __func__);
252 	if (pListing->iAppearanceAnimationCount > 0)
253 	{
254 		pListing->iAppearanceAnimationCount --;
255 		if (pListing->iAppearanceAnimationCount != 0)
256 			*bContinueAnimation = TRUE;
257 	}
258 	if (pListing->iCurrentEntryAnimationCount > 0)
259 	{
260 		pListing->iCurrentEntryAnimationCount --;
261 		if (pListing->iCurrentEntryAnimationCount != 0)
262 			*bContinueAnimation = TRUE;
263 		/// optimisation : ne retracer que la zone concernee...
264 
265 	}
266 	if (pListing->iScrollAnimationCount > 0)
267 	{
268 		pListing->iScrollAnimationCount --;
269 		if (pListing->iScrollAnimationCount != 0)
270 			*bContinueAnimation = TRUE;
271 		double f = (double) pListing->iScrollAnimationCount / NB_STEPS_FOR_SCROLL;
272 		pListing->fCurrentOffset = pListing->fPreviousOffset * f + pListing->fAimedOffset * (1 - f);
273 	}
274 	double fRadius = MIN (6, myDialogsParam.dialogTextDescription.iSize/2+1);
275 	if (myData.pListing->iTitleWidth > myData.pListing->container.iWidth - 2*fRadius + 10)  // 10 pixels de rab
276 	{
277 		myData.pListing->iTitleOffset += 2 * myData.pListing->sens;
278 		if (myData.pListing->container.iWidth - 2*fRadius + myData.pListing->iTitleOffset > myData.pListing->iTitleWidth)
279 		{
280 			myData.pListing->iTitleOffset = myData.pListing->iTitleWidth - (myData.pListing->container.iWidth - 2*fRadius);
281 			myData.pListing->sens = -1;
282 		}
283 		else if (myData.pListing->iTitleOffset < 0)
284 		{
285 			myData.pListing->iTitleOffset = 0;
286 			myData.pListing->sens = 1;
287 		}
288 		*bContinueAnimation = TRUE;
289 		/// optimisation : ne retracer que la zone concernee...
290 
291 	}
292 	cairo_dock_redraw_container (CAIRO_CONTAINER (pListing));
293 	return GLDI_NOTIFICATION_LET_PASS;
294 }
295 
cd_do_render_listing_notification(gpointer pUserData,CDListing * pListing,cairo_t * pCairoContext)296 gboolean cd_do_render_listing_notification (gpointer pUserData, CDListing *pListing, cairo_t *pCairoContext)
297 {
298 	//g_print ("%s ()\n", __func__);
299 	int iWidth = pListing->container.iWidth, iHeight = pListing->container.iHeight;
300 	int iLeftMargin = myDialogsParam.dialogTextDescription.iSize + 4, iRightMargin = (myDialogsParam.dialogTextDescription.iSize + 4) / 2;
301 	int iTopMargin = (myDialogsParam.dialogTextDescription.iSize + 2) + GAP, iBottomMargin = (myDialogsParam.dialogTextDescription.iSize + 2) * 4 + GAP;
302 	CDEntry *pEntry;
303 
304 	// on dessine un cadre et un fond
305 	double fRadius = MIN (6, myDialogsParam.dialogTextDescription.iSize/2+1);
306 	double fLineWidth = 1.;
307 	cairo_set_line_width (pCairoContext, fLineWidth);
308 
309 	cairo_save (pCairoContext);
310 	cairo_translate (pCairoContext, 0, fLineWidth);
311 	cairo_dock_draw_rounded_rectangle (pCairoContext, fRadius, fLineWidth, iWidth - 2 * fRadius - fLineWidth, iTopMargin - GAP);
312 	cairo_set_source_rgba (pCairoContext, .7, .7, 1., 1.);
313 	cairo_stroke_preserve (pCairoContext);
314 	cairo_set_source_rgba (pCairoContext, 1., 1., 1., .8);
315 	cairo_fill (pCairoContext);
316 
317 	cairo_translate (pCairoContext, 0, iTopMargin + fLineWidth);
318 	cairo_dock_draw_rounded_rectangle (pCairoContext, fRadius, fLineWidth, iWidth - 2 * fRadius - fLineWidth, iHeight - iTopMargin - iBottomMargin - GAP);
319 	cairo_set_source_rgba (pCairoContext, .7, .7, 1., 1.);
320 	cairo_stroke_preserve (pCairoContext);
321 	cairo_set_source_rgba (pCairoContext, 1., 1., 1., .8);
322 	cairo_fill (pCairoContext);
323 
324 	cairo_translate (pCairoContext, 0, iHeight - iTopMargin - 2*fLineWidth - iBottomMargin + GAP);
325 	cairo_dock_draw_rounded_rectangle (pCairoContext, fRadius, fLineWidth, iWidth - 2 * fRadius - fLineWidth, iBottomMargin - GAP - fLineWidth);
326 	cairo_set_source_rgba (pCairoContext, .7, .7, 1., 1.);
327 	cairo_stroke_preserve (pCairoContext);
328 	cairo_set_source_rgba (pCairoContext, 1., 1., 1., .8);
329 	cairo_fill (pCairoContext);
330 	cairo_restore (pCairoContext);
331 
332 	PangoLayout *pLayout = pango_cairo_create_layout (pCairoContext);
333 	PangoFontDescription *pDesc = gldi_text_description_get_description (&myDialogsParam.dialogTextDescription);
334 
335 	pango_layout_set_font_description (pLayout, pDesc);
336 
337 	// on dessine les entrees.
338 	if (pListing->pEntries != NULL)
339 	{
340 		// on dessine chaque entree.
341 		int i2 = (double) (myConfig.iNbLinesInListing + DELTA) * (NB_STEPS - pListing->iAppearanceAnimationCount) / NB_STEPS;
342 		int i1 = i2 - DELTA;  // le profil de transparence est : ``\__ , avec 0 <= i1 <= i2 <= L.
343 		double dh = myDialogsParam.dialogTextDescription.iSize + 2;  // hauteur d'une ligne.
344 		double h1 = (double) i1 * dh;  // hauteur correspondant a la ligne i1.
345 		double h2 = (double) i2 * dh;  // hauteur correspondant a la ligne i2.
346 		double h;  // hauteur de la ligne courante en tenant compte du scroll.
347 		double H = myConfig.iNbLinesInListing * dh;  // hauteur totale disponible.
348 		double h_ = H - MIN (EPSILON * dh, pListing->iNbVisibleEntries * dh - H - pListing->fCurrentOffset);
349 		double alpha;  // transparence de la ligne courante.
350 		double dx, dy = iTopMargin + 1;  // marges.
351 		int i;  // ligne courante.
352 		GList *e;  // entree courante.
353 		for (e = pListing->pEntries, i = 0; e != NULL; e = e->next, i ++)
354 		{
355 			h = i * dh - pListing->fCurrentOffset;
356 			if (h + dh <= 0 || h >= H || h > h2)  // entree non visible.
357 				continue;
358 
359 			pEntry = e->data;
360 			if (pEntry->bHidden)
361 				continue ;
362 
363 			dx = iLeftMargin;  // marge a gauche.
364 			if (! pEntry->bMainEntry && myData.pListingHistory == NULL)
365 				dx += iLeftMargin;
366 			cairo_save (pCairoContext);
367 			cairo_translate (pCairoContext, dx, dy + h);
368 
369 			// on fait un clip si necessaire.
370 			if (h > H - dh || h < 0)  // cette entree n'est que partiellement visible.
371 			{
372 				if (h < 0)  // elle depasse en haut.
373 					cairo_rectangle (pCairoContext, -iLeftMargin, -h, iWidth, dh + h);
374 				else  // elle depasse en bas.
375 					cairo_rectangle (pCairoContext, -iLeftMargin, 0, iWidth, H - h);
376 				cairo_clip (pCairoContext);
377 			}
378 
379 			// on dessine l'icone.
380 			if (pEntry->pIconSurface != NULL)
381 			{
382 				cairo_set_source_surface (pCairoContext, pEntry->pIconSurface, - iLeftMargin, 0.);
383 				cairo_paint (pCairoContext);
384 			}
385 
386 			// on surligne l'entree courante.
387 			if (e == pListing->pCurrentEntry)
388 			{
389 				double f = 1. - .5 * pListing->iCurrentEntryAnimationCount / NB_STEPS_FOR_CURRENT_ENTRY;
390 				cairo_save (pCairoContext);
391 				double rx = .5*(iWidth - iLeftMargin - iRightMargin);
392 				double ry = .5*(myDialogsParam.dialogTextDescription.iSize + 2);
393 				cairo_pattern_t *pPattern = cairo_pattern_create_radial (ry,
394 					ry,
395 					0.,
396 					ry,
397 					ry,
398 					f * ry);
399 				cairo_pattern_set_extend (pPattern, CAIRO_EXTEND_NONE);
400 
401 				cairo_pattern_add_color_stop_rgba (pPattern,
402 					0.,
403 					0., 0., 1., .3);
404 				cairo_pattern_add_color_stop_rgba (pPattern,
405 					1.,
406 					0., 0., 0., 0.);
407 				cairo_scale (pCairoContext, rx/ry, 1.);
408 				cairo_set_source (pCairoContext, pPattern);
409 				cairo_paint (pCairoContext);
410 				cairo_pattern_destroy (pPattern);
411 				cairo_restore (pCairoContext);
412 
413 				// on dessine l'indicateur de sous-listing.
414 				if (pEntry->list != NULL)
415 				{
416 					cairo_set_source_rgba (pCairoContext, 0., 0., 0., f);
417 					cairo_move_to (pCairoContext, iWidth - iLeftMargin - iRightMargin - (! pEntry->bMainEntry && myData.pListingHistory == NULL ? iLeftMargin : 0), myDialogsParam.dialogTextDescription.iSize/4);
418 					cairo_rel_line_to (pCairoContext, iRightMargin, myDialogsParam.dialogTextDescription.iSize/3);
419 					cairo_rel_line_to (pCairoContext, -iRightMargin, myDialogsParam.dialogTextDescription.iSize/3);
420 					cairo_close_path (pCairoContext);
421 					cairo_stroke_preserve (pCairoContext);
422 					cairo_set_source_rgba (pCairoContext, 0.1, 0.3, 0.5, f*.7);
423 					cairo_fill (pCairoContext);
424 				}
425 			}
426 
427 			// on dessine le texte.
428 			if (h < h1)
429 				alpha = 1.;
430 			else
431 				alpha = (h2 - h) / DELTA / dh;
432 			if (h >= h_)
433 				alpha *= 1. - (h - h_ + dh) / (H - h_ + dh);
434 			cairo_set_source_rgba (pCairoContext, 0., 0., 0., alpha);
435 			int iWeight = pango_font_description_get_weight (pDesc);
436 			if (pEntry->bMainEntry)
437 			{
438 				pango_font_description_set_weight (pDesc, PANGO_WEIGHT_HEAVY);
439 				pango_layout_set_font_description (pLayout, pDesc);
440 			}
441 			pango_layout_set_text (pLayout, pEntry->cName, -1);
442 			pango_cairo_show_layout (pCairoContext, pLayout);
443 			if (pEntry->bMainEntry)
444 			{
445 				pango_font_description_set_weight (pDesc, iWeight);
446 				pango_layout_set_font_description (pLayout, pDesc);
447 			}
448 
449 			cairo_restore (pCairoContext);
450 		}
451 
452 		// on dessine le chemin de l'entree courante.
453 		if (pListing->pCurrentEntry)
454 		{
455 			pEntry = pListing->pCurrentEntry->data;
456 			cairo_save (pCairoContext);
457 			cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
458 			cairo_translate (pCairoContext, fRadius - pListing->iTitleOffset, 0.);
459 			pango_layout_set_text (pLayout, pEntry->cPath ? pEntry->cPath : pEntry->cName, -1);
460 			PangoRectangle ink, log;
461 			pango_layout_get_pixel_extents (pLayout, &ink, &log);
462 			pListing->iTitleWidth = ink.width;
463 			pango_cairo_show_layout (pCairoContext, pLayout);
464 			cairo_restore (pCairoContext);
465 		}
466 	}
467 
468 	// on dessine l'etat de la recherche.
469 	cairo_translate (pCairoContext, 0, iHeight - iBottomMargin);
470 	cairo_set_source_surface (pCairoContext, myData.pScoobySurface, 0., 0.);
471 	cairo_paint (pCairoContext);
472 
473 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
474 	cairo_translate (pCairoContext, 2 * (myDialogsParam.dialogTextDescription.iSize + 2), GAP);
475 	if (myData.cStatus != NULL)
476 	{
477 		pango_layout_set_text (pLayout, myData.cStatus, -1);
478 	}
479 	pango_cairo_show_layout (pCairoContext, pLayout);
480 
481 	// on dessine le filtre.
482 	cairo_translate (pCairoContext, 0., myDialogsParam.dialogTextDescription.iSize + 2);
483 	cairo_set_source_surface (pCairoContext, (myData.iCurrentFilter & DO_MATCH_CASE) ? myData.pActiveButtonSurface : myData.pInactiveButtonSurface, 0., 0.);
484 	cairo_paint (pCairoContext);
485 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
486 	pango_layout_set_text (pLayout, D_("(F1) Match case"), -1);
487 	pango_cairo_show_layout (pCairoContext, pLayout);
488 
489 	cairo_translate (pCairoContext, iWidth/3, 0.);
490 	cairo_set_source_surface (pCairoContext, (myData.iCurrentFilter & DO_TYPE_MUSIC) ? myData.pActiveButtonSurface : myData.pInactiveButtonSurface, 0., 0.);
491 	cairo_paint (pCairoContext);
492 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
493 	pango_layout_set_text (pLayout, D_("(F2) Music"), -1);
494 	pango_cairo_show_layout (pCairoContext, pLayout);
495 
496 	cairo_translate (pCairoContext, iWidth/3, 0.);
497 	cairo_set_source_surface (pCairoContext, (myData.iCurrentFilter & DO_TYPE_IMAGE) ? myData.pActiveButtonSurface : myData.pInactiveButtonSurface, 0., 0.);
498 	cairo_paint (pCairoContext);
499 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
500 	pango_layout_set_text (pLayout, D_("(F3) Image"), -1);
501 	pango_cairo_show_layout (pCairoContext, pLayout);
502 
503 	cairo_translate (pCairoContext, -2*iWidth/3, myDialogsParam.dialogTextDescription.iSize + 2);
504 	cairo_set_source_surface (pCairoContext, (myData.iCurrentFilter & DO_TYPE_VIDEO) ? myData.pActiveButtonSurface : myData.pInactiveButtonSurface, 0., 0.);
505 	cairo_paint (pCairoContext);
506 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
507 	pango_layout_set_text (pLayout, D_("(F4) Video"), -1);
508 	pango_cairo_show_layout (pCairoContext, pLayout);
509 
510 	cairo_translate (pCairoContext, iWidth/3, 0.);
511 	cairo_set_source_surface (pCairoContext, (myData.iCurrentFilter & DO_TYPE_TEXT) ? myData.pActiveButtonSurface : myData.pInactiveButtonSurface, 0., 0.);
512 	cairo_paint (pCairoContext);
513 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
514 	pango_layout_set_text (pLayout, D_("(F5) Text"), -1);
515 	pango_cairo_show_layout (pCairoContext, pLayout);
516 
517 	cairo_translate (pCairoContext, iWidth/3, 0.);
518 	cairo_set_source_surface (pCairoContext, (myData.iCurrentFilter & DO_TYPE_HTML) ? myData.pActiveButtonSurface : myData.pInactiveButtonSurface, 0., 0.);
519 	cairo_paint (pCairoContext);
520 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
521 	pango_layout_set_text (pLayout, D_("(F6) Html"), -1);
522 	pango_cairo_show_layout (pCairoContext, pLayout);
523 
524 	cairo_translate (pCairoContext, -2*iWidth/3, myDialogsParam.dialogTextDescription.iSize + 2);
525 	cairo_set_source_surface (pCairoContext, (myData.iCurrentFilter & DO_TYPE_SOURCE) ? myData.pActiveButtonSurface : myData.pInactiveButtonSurface, 0., 0.);
526 	cairo_paint (pCairoContext);
527 	cairo_set_source_rgb (pCairoContext, 0., 0., 0.);
528 	pango_layout_set_text (pLayout, D_("(F7) Sources"), -1);
529 	pango_cairo_show_layout (pCairoContext, pLayout);
530 
531 	g_object_unref (pLayout);
532 	return GLDI_NOTIFICATION_LET_PASS;
533 }
534 
535 
cd_do_show_listing(void)536 void cd_do_show_listing (void)
537 {
538 	if (myData.pListing == NULL)
539 	{
540 		myData.pListing = cd_do_create_listing ();
541 
542 		gldi_object_register_notification (CAIRO_CONTAINER (myData.pListing),
543 			NOTIFICATION_UPDATE,
544 			(GldiNotificationFunc) cd_do_update_listing_notification,
545 			GLDI_RUN_AFTER,
546 			NULL);
547 		gldi_object_register_notification (CAIRO_CONTAINER (myData.pListing),
548 			NOTIFICATION_RENDER,
549 			(GldiNotificationFunc) cd_do_render_listing_notification,
550 			GLDI_RUN_AFTER,
551 			NULL);
552 		if (myData.pScoobySurface == NULL)
553 		{
554 			myData.pScoobySurface = cairo_dock_create_surface_from_image_simple (MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE,
555 				2 * (myDialogsParam.dialogTextDescription.iSize + 2),
556 				2 * (myDialogsParam.dialogTextDescription.iSize + 2));
557 		}
558 		if (myData.pActiveButtonSurface == NULL)
559 		{
560 			cd_debug ("load button : %dx%d", myDialogsParam.dialogTextDescription.iSize + 2, myData.pListing->container.iWidth);
561 			cairo_t* pSourceContext = cairo_dock_create_drawing_context_generic (CAIRO_CONTAINER (g_pMainDock));
562 			myData.pActiveButtonSurface = cairo_dock_create_surface_from_image_simple (MY_APPLET_SHARE_DATA_DIR"/active-button.svg",
563 				(myData.pListing->container.iWidth - (myDialogsParam.dialogTextDescription.iSize + 2) * 3) / 3,
564 				myDialogsParam.dialogTextDescription.iSize + 2);
565 			myData.pInactiveButtonSurface = cairo_dock_create_surface_from_image_simple (MY_APPLET_SHARE_DATA_DIR"/inactive-button.svg",
566 				(myData.pListing->container.iWidth - (myDialogsParam.dialogTextDescription.iSize + 2) * 3) / 3,
567 				myDialogsParam.dialogTextDescription.iSize + 2);
568 			cairo_destroy (pSourceContext);
569 		}
570 	}
571 	else
572 	{
573 		gtk_widget_show (myData.pListing->container.pWidget);
574 		gtk_window_set_transient_for (GTK_WINDOW (myData.pListing->container.pWidget), GTK_WINDOW (g_pMainDock->container.pWidget));
575 		gtk_window_set_modal (GTK_WINDOW (myData.pListing->container.pWidget), TRUE);
576 
577 		int iWidth = _listing_compute_width (pListing);
578 		int iHeight = _listing_compute_height (pListing);
579 		if (myData.pListing->container.iWidth != iWidth || myData.pListing->container.iHeight != iHeight)
580 		{
581 			gtk_window_resize (GTK_WINDOW (myData.pListing->container.pWidget),
582 				iWidth,
583 				iHeight);
584 		}
585 		_place_listing (myData.pListing);
586 		cairo_dock_redraw_container (CAIRO_CONTAINER (myData.pListing));
587 	}
588 }
589 
cd_do_hide_listing(void)590 void cd_do_hide_listing (void)
591 {
592 	if (myData.pListing == NULL)
593 		return;
594 	if (myData.pListing->iSidFillEntries != 0)
595 	{
596 		g_source_remove (myData.pListing->iSidFillEntries);
597 		myData.pListing->iSidFillEntries = 0;
598 	}
599 	myData.pListing->pEntryToFill = NULL;
600 
601 	g_list_foreach (myData.pListing->pEntries, (GFunc)cd_do_free_entry, NULL);
602 	g_list_free (myData.pListing->pEntries);
603 	myData.pListing->pEntries = NULL;
604 	myData.pListing->iNbEntries = 0;
605 	myData.pListing->pCurrentEntry = NULL;
606 
607 	if (myData.pListingHistory != NULL)
608 	{
609 		g_list_foreach (myData.pListingHistory, (GFunc) cd_do_free_listing_backup, NULL);
610 		g_list_free (myData.pListingHistory);
611 		myData.pListingHistory = NULL;
612 	}
613 
614 	myData.pListing->iAppearanceAnimationCount = 0;
615 	myData.pListing->iCurrentEntryAnimationCount = 0;
616 	myData.pListing->iScrollAnimationCount = 0;
617 	myData.pListing->fAimedOffset = 0;
618 	myData.pListing->fPreviousOffset = myData.pListing->fCurrentOffset = 0;
619 	myData.pListing->iTitleWidth = 0;
620 	myData.pListing->iTitleOffset = 0;
621 	myData.pListing->sens = 1;
622 
623 	g_free (myData.cStatus);
624 	myData.cStatus = NULL;
625 
626 	gtk_window_set_transient_for (GTK_WINDOW (myData.pListing->container.pWidget), NULL);
627 	gtk_window_set_modal (GTK_WINDOW (myData.pListing->container.pWidget), FALSE);
628 	gtk_widget_hide (myData.pListing->container.pWidget);
629 }
630 
cd_do_load_entries_into_listing(GList * pEntries,int iNbEntries)631 void cd_do_load_entries_into_listing (GList *pEntries, int iNbEntries)
632 {
633 	cd_do_show_listing ();
634 
635 	if (myData.pListing->pEntries != NULL)
636 	{
637 		cd_debug ("%d entrees precedemment", myData.pListing->iNbEntries);
638 		g_list_foreach (myData.pListing->pEntries, (GFunc)cd_do_free_entry, NULL);
639 		g_list_free (myData.pListing->pEntries);
640 	}
641 	myData.pListing->pEntries = pEntries;
642 
643 	myData.pListing->iNbEntries = iNbEntries;
644 	myData.pListing->iNbVisibleEntries = iNbEntries;
645 
646 	if (iNbEntries == 0)
647 		cd_do_set_status (D_("No result"));
648 	else if (iNbEntries >= myConfig.iNbResultMax)
649 		cd_do_set_status_printf ("> %d results", myConfig.iNbResultMax);
650 	else
651 		cd_do_set_status_printf ("%d %s", iNbEntries, iNbEntries > 1 ? D_("results") : D_("result"));
652 
653 	cd_do_rewind_current_entry ();
654 	myData.pListing->iCurrentEntryAnimationCount = NB_STEPS_FOR_CURRENT_ENTRY;  // pas de surlignage pendant l'apparition.
655 
656 	myData.pListing->iScrollAnimationCount = 0;
657 	myData.pListing->fAimedOffset = 0;
658 	myData.pListing->fPreviousOffset = myData.pListing->fCurrentOffset = 0;
659 	myData.pListing->sens = 1;
660 	myData.pListing->iTitleOffset = 0;
661 	myData.pListing->iTitleWidth = 0;
662 
663 	myData.pListing->iAppearanceAnimationCount = NB_STEPS;
664 	if (iNbEntries != 0)
665 		cairo_dock_launch_animation (CAIRO_CONTAINER (myData.pListing));
666 
667 	cd_do_fill_listing_entries (myData.pListing);
668 }
669 
670 
_fill_entry_icon_idle(CDListing * pListing)671 static gboolean _fill_entry_icon_idle (CDListing *pListing)
672 {
673 	cd_debug ("%s (%x)", __func__, pListing->pEntryToFill);
674 
675 	CDEntry *pEntry;
676 	gboolean bHasBeenFilled = FALSE;
677 	while (pListing->pEntryToFill != NULL && ! bHasBeenFilled)
678 	{
679 		pEntry = pListing->pEntryToFill->data;
680 		if (! pEntry->bHidden && pEntry->fill)
681 			bHasBeenFilled = pEntry->fill (pEntry);
682 		pListing->pEntryToFill = pListing->pEntryToFill->next;
683 	}
684 
685 	if (pListing->pEntryToFill == NULL)  // on a tout rempli. ajouter || bHasBeenFilled pour redessiner au fur et a mesure.
686 	{
687 		cairo_dock_redraw_container (CAIRO_CONTAINER (myData.pListing));
688 		pListing->iSidFillEntries = 0;
689 		return FALSE;
690 	}
691 	return TRUE;
692 }
cd_do_fill_listing_entries(CDListing * pListing)693 void cd_do_fill_listing_entries (CDListing *pListing)
694 {
695 	pListing->pEntryToFill = pListing->pEntries;
696 	if (pListing->iSidFillEntries == 0 && pListing->iNbVisibleEntries != 0)
697 		pListing->iSidFillEntries = g_idle_add ((GSourceFunc)_fill_entry_icon_idle, pListing);
698 }
699 
700 
_update_scroll(int iNewPosition,int delta)701 static inline void _update_scroll (int iNewPosition, int delta)
702 {
703 	if (iNewPosition > myConfig.iNbLinesInListing/2 && iNewPosition < myData.pListing->iNbVisibleEntries - myConfig.iNbLinesInListing/2)
704 	{
705 		myData.pListing->fAimedOffset += delta * (myDialogsParam.dialogTextDescription.iSize + 2);
706 		if (myData.pListing->fAimedOffset > (myData.pListing->iNbVisibleEntries - myConfig.iNbLinesInListing) * (myDialogsParam.dialogTextDescription.iSize + 2))
707 			myData.pListing->fAimedOffset = (myData.pListing->iNbVisibleEntries - myConfig.iNbLinesInListing) * (myDialogsParam.dialogTextDescription.iSize + 2);
708 		else if (myData.pListing->fAimedOffset < 0)
709 			myData.pListing->fAimedOffset = 0.;
710 	}
711 	else if (iNewPosition <= myConfig.iNbLinesInListing/2)
712 		myData.pListing->fAimedOffset = 0.;
713 	else
714 		myData.pListing->fAimedOffset = (myData.pListing->iNbVisibleEntries - myConfig.iNbLinesInListing) * (myDialogsParam.dialogTextDescription.iSize + 2);
715 	myData.pListing->iScrollAnimationCount = NB_STEPS_FOR_SCROLL;
716 }
_launch_new_entry_animation(void)717 static inline void _launch_new_entry_animation (void)
718 {
719 	myData.pListing->iCurrentEntryAnimationCount = NB_STEPS_FOR_CURRENT_ENTRY;
720 	myData.pListing->iTitleOffset = 0;
721 	myData.pListing->sens = 1;
722 	cairo_dock_launch_animation (CAIRO_CONTAINER (myData.pListing));
723 	cairo_dock_redraw_container (CAIRO_CONTAINER (myData.pListing));
724 }
cd_do_select_prev_next_entry_in_listing(gboolean bNext)725 void cd_do_select_prev_next_entry_in_listing (gboolean bNext)
726 {
727 	myData.pListing->fPreviousOffset = myData.pListing->fCurrentOffset;
728 	if (myData.pListing->pCurrentEntry == NULL)
729 		return;
730 
731 	// on cherche la nouvelle entree courante.
732 	GList *e = myData.pListing->pCurrentEntry;
733 	CDEntry *pEntry = e->data;
734 	if (bNext)
735 	{
736 		while (e->next != NULL)
737 		{
738 			e = e->next;
739 			pEntry = e->data;
740 			if (! pEntry->bHidden)
741 				break;
742 		};
743 	}
744 	else
745 	{
746 		while (e->prev != NULL)
747 		{
748 			e = e->prev;
749 			pEntry = e->data;
750 			if (! pEntry->bHidden)
751 				break;
752 		};
753 	}
754 	if (e == myData.pListing->pCurrentEntry)
755 		return ;
756 	myData.pListing->pCurrentEntry = e;
757 
758 	// on scrolle si necessaire.
759 	if (myData.pListing->iNbVisibleEntries > myConfig.iNbLinesInListing)
760 	{
761 		int i = g_list_position (myData.pListing->pEntries, e);
762 		_update_scroll (i, (bNext ? 1:-1));
763 	}
764 
765 	// on arme l'animation du titre et du surlignage et on lance l'animation.
766 	_launch_new_entry_animation ();
767 }
768 
cd_do_select_prev_next_page_in_listing(gboolean bNext)769 void cd_do_select_prev_next_page_in_listing (gboolean bNext)
770 {
771 	cd_debug ("%s (%d/%d)", __func__, myData.pListing->iNbVisibleEntries, myConfig.iNbLinesInListing);
772 	myData.pListing->fPreviousOffset = myData.pListing->fCurrentOffset;
773 	GList *e, *f;
774 	e = myData.pListing->pCurrentEntry;
775 	if (! e)
776 		e = myData.pListing->pEntries;
777 	f = e;
778 	CDEntry *pEntry;
779 	int k = 0;
780 	if (bNext)
781 	{
782 		do
783 		{
784 			if (e->next == NULL)
785 				break;
786 			e = e->next;
787 			pEntry = e->data;
788 			if (! pEntry->bHidden)
789 			{
790 				f = e;
791 				k ++;
792 			}
793 		} while (k < myConfig.iNbLinesInListing);
794 	}
795 	else
796 	{
797 		do
798 		{
799 			if (e->prev == NULL)
800 				break;
801 			e = e->prev;
802 			pEntry = e->data;
803 			if (! pEntry->bHidden)
804 			{
805 				f = e;
806 				k ++;
807 			}
808 		} while (k < myConfig.iNbLinesInListing);
809 	}
810 	myData.pListing->pCurrentEntry = f;
811 	///myData.pListing->fAimedOffset = g_list_position (myData.pListing->pEntries, f) * (myDialogsParam.dialogTextDescription.iSize + 2);
812 
813 	// on scrolle si necessaire.
814 	if (myData.pListing->iNbVisibleEntries > myConfig.iNbLinesInListing)
815 	{
816 		int i = 0;
817 		for (e = myData.pListing->pEntries; e != f; e = e->next)
818 		{
819 			pEntry = e->data;
820 			if (! pEntry->bHidden)
821 				i ++;
822 		}
823 		_update_scroll (i, (bNext ? 1:-1) * k);
824 	}
825 
826 	// on arme l'animation du titre et du surlignage et on lance l'animation.
827 	_launch_new_entry_animation ();
828 }
829 
cd_do_select_last_first_entry_in_listing(gboolean bLast)830 void cd_do_select_last_first_entry_in_listing (gboolean bLast)
831 {
832 	cd_debug ("%s (%d/%d)", __func__, myData.pListing->iNbVisibleEntries, myConfig.iNbLinesInListing);
833 	myData.pListing->fPreviousOffset = myData.pListing->fCurrentOffset;
834 	GList *e;
835 	int k;
836 	if (bLast)
837 	{
838 		e = g_list_last (myData.pListing->pEntries);
839 		k = myData.pListing->iNbVisibleEntries - 1;
840 		while (e->prev != NULL && ((CDEntry *)(e->data))->bHidden)
841 		{
842 			e = e->prev;
843 			k --;
844 		}
845 	}
846 	else
847 	{
848 		e = myData.pListing->pEntries;
849 		k = 0;
850 		while (e->next != NULL && ((CDEntry *)(e->data))->bHidden)
851 		{
852 			e = e->next;
853 			k ++;
854 		}
855 	}
856 	myData.pListing->pCurrentEntry = e;
857 	///myData.pListing->fAimedOffset = i * (myDialogsParam.dialogTextDescription.iSize + 2);
858 
859 	// on scrolle si necessaire.
860 	if (myData.pListing->iNbVisibleEntries > myConfig.iNbLinesInListing)
861 	{
862 		int i = (bLast ? myData.pListing->iNbVisibleEntries : 0);
863 		_update_scroll (i, (bLast ? 1:-1) * k);
864 	}
865 
866 	// on arme l'animation du titre et du surlignage et on lance l'animation.
867 	_launch_new_entry_animation ();
868 }
869 
cd_do_rewind_current_entry(void)870 void cd_do_rewind_current_entry (void)
871 {
872 	if (myData.pListing == NULL)
873 	{
874 		myData.pListing->pCurrentEntry = NULL;
875 		return ;
876 	}
877 	int i = 0;
878 	GList *e;
879 	CDEntry *pEntry;
880 	for (e = myData.pListing->pEntries; e && e->next != NULL; e = e->next)
881 	{
882 		pEntry = e->data;
883 		if (! pEntry->bHidden)
884 		{
885 			i ++;
886 			if (i == myConfig.iNbLinesInListing/2)
887 				break ;
888 		}
889 	}
890 	myData.pListing->pCurrentEntry = e;
891 }
892 
893 
cd_do_set_status(const gchar * cStatus)894 void cd_do_set_status (const gchar *cStatus)
895 {
896 	g_free (myData.cStatus);
897 	myData.cStatus = g_strdup (cStatus);
898 	if (myData.pListing)
899 		cairo_dock_redraw_container (CAIRO_CONTAINER (myData.pListing));
900 }
901 
cd_do_set_status_printf(const gchar * cStatusFormat,...)902 void cd_do_set_status_printf (const gchar *cStatusFormat, ...)
903 {
904 	g_return_if_fail (cStatusFormat != NULL);
905 	va_list args;
906 	va_start (args, cStatusFormat);
907 	gchar *cStatus = g_strdup_vprintf (cStatusFormat, args);
908 	cd_do_set_status (cStatus);
909 	g_free (cStatus);
910 	va_end (args);
911 }
912