1 /*
2  * Copyright 2006 Richard Wilson <info@tinct.net>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /** \file
20  * Progress bar (implementation).
21  */
22 
23 #include <assert.h>
24 #include <stdbool.h>
25 #include <string.h>
26 #include "swis.h"
27 #include "oslib/colourtrans.h"
28 #include "oslib/os.h"
29 #include "oslib/osspriteop.h"
30 #include "oslib/wimp.h"
31 #include "oslib/wimpspriteop.h"
32 
33 #include "utils/log.h"
34 #include "utils/utils.h"
35 #include "netsurf/plotters.h"
36 
37 #include "riscos/gui.h"
38 #include "riscos/tinct.h"
39 #include "riscos/wimp_event.h"
40 #include "riscos/gui/progress_bar.h"
41 
42 #define MARGIN 6
43 
44 struct progress_bar {
45 	wimp_w w;			/**< progress bar window handle */
46 	unsigned int range;		/**< progress bar range */
47 	unsigned int value;		/**< progress bar value */
48 	char icon[13];			/**< current icon */
49 	int offset;			/**< progress bar rotation */
50 	os_box visible;			/**< progress bar position */
51 	int icon_x0;			/**< icon x0 */
52 	int icon_y0;			/**< icon y0 */
53 	osspriteop_header *icon_img;	/**< icon image */
54 	bool animating;			/**< progress bar is animating */
55 	bool recalculate;		/**< recalculation required */
56 	int cur_width;			/**< current calculated width */
57 	int cur_height;			/**< current calculated height */
58 	bool icon_obscured;		/**< icon is partially obscured */
59 };
60 
61 static char progress_animation_sprite[] = "progress";
62 static osspriteop_header *progress_icon;
63 static unsigned int progress_width;
64 static unsigned int progress_height;
65 
66 struct wimp_window_base progress_bar_definition = {
67 	{0, 0, 1, 1},
68 	0,
69 	0,
70 	wimp_TOP,
71 	wimp_WINDOW_NEW_FORMAT | wimp_WINDOW_MOVEABLE | wimp_WINDOW_NO_BOUNDS,
72 	0xff,
73 	wimp_COLOUR_LIGHT_GREY,
74 	wimp_COLOUR_LIGHT_GREY,
75 	wimp_COLOUR_VERY_LIGHT_GREY,
76 	wimp_COLOUR_DARK_GREY,
77 	wimp_COLOUR_MID_LIGHT_GREY,
78 	wimp_COLOUR_CREAM,
79 	wimp_WINDOW_NEVER3D | 0x16u /* RISC OS 5.03+ */,
80 	{0, 0, 65535, 65535},
81 	0,
82 	0,
83 	wimpspriteop_AREA,
84 	1,
85 	1,
86 	{""},
87 	0
88 };
89 
90 
91 static void ro_gui_progress_bar_calculate(struct progress_bar *pb, int width,
92 		int height);
93 static void ro_gui_progress_bar_redraw(wimp_draw *redraw);
94 static void ro_gui_progress_bar_redraw_window(wimp_draw *redraw,
95 		struct progress_bar *pb);
96 static void ro_gui_progress_bar_animate(void *p);
97 
98 
99 /**
100  * Initialise the progress bar
101  *
102  * \param  icons  the sprite area to use for icons
103  */
ro_gui_progress_bar_init(osspriteop_area * icons)104 void ro_gui_progress_bar_init(osspriteop_area *icons)
105 {
106 	const char *name = progress_animation_sprite;
107 	os_error *error;
108 
109 	progress_bar_definition.sprite_area = icons;
110 
111 	progress_icon = NULL;
112 	error = xosspriteop_select_sprite(osspriteop_USER_AREA,
113 			progress_bar_definition.sprite_area,
114 			(osspriteop_id) name, &progress_icon);
115 	if (!error) {
116 		xosspriteop_read_sprite_info(osspriteop_USER_AREA,
117 			progress_bar_definition.sprite_area,
118 			(osspriteop_id) name,
119 			(int *) &progress_width, (int *) &progress_height,
120 			0, 0);
121 	}
122 }
123 
124 
125 /**
126  * Create a new progress bar
127  */
ro_gui_progress_bar_create(void)128 struct progress_bar *ro_gui_progress_bar_create(void)
129 {
130 	struct progress_bar *pb;
131 	os_error *error;
132 
133 	pb = calloc(1, sizeof(*pb));
134 	if (!pb)
135 		return NULL;
136 
137 	error = xwimp_create_window((wimp_window *)&progress_bar_definition,
138 				&pb->w);
139 	if (error) {
140 		NSLOG(netsurf, INFO, "xwimp_create_window: 0x%x: %s",
141 		      error->errnum, error->errmess);
142 		free(pb);
143 		return NULL;
144 	}
145 
146 	ro_gui_wimp_event_register_redraw_window(pb->w,
147 			ro_gui_progress_bar_redraw);
148 	ro_gui_wimp_event_set_user_data(pb->w, pb);
149 	return pb;
150 }
151 
152 
153 /**
154  * Destroy a progress bar and free all associated resources
155  *
156  * \param  pb  the progress bar to destroy
157  */
ro_gui_progress_bar_destroy(struct progress_bar * pb)158 void ro_gui_progress_bar_destroy(struct progress_bar *pb)
159 {
160 	os_error *error;
161 	assert(pb);
162 
163 	if (pb->animating) {
164 		riscos_schedule(-1, ro_gui_progress_bar_animate, pb);
165 	}
166 	ro_gui_wimp_event_finalise(pb->w);
167 	error = xwimp_delete_window(pb->w);
168 	if (error) {
169 		NSLOG(netsurf, INFO, "xwimp_delete_window: 0x%x:%s",
170 		      error->errnum, error->errmess);
171 	}
172 
173 	free(pb);
174 }
175 
176 
177 /**
178  * Get the handle of the window that represents a progress bar
179  *
180  * \param  pb  the progress bar to get the window handle of
181  * \return the progress bar's window handle
182  */
ro_gui_progress_bar_get_window(struct progress_bar * pb)183 wimp_w ro_gui_progress_bar_get_window(struct progress_bar *pb)
184 {
185 	assert(pb);
186 
187 	return pb->w;
188 }
189 
190 
191 /**
192  * Set the icon for a progress bar
193  *
194  * \param  pb  the progress bar to set the icon for
195  * \param  icon  the icon to use, or NULL for no icon
196  */
ro_gui_progress_bar_set_icon(struct progress_bar * pb,const char * icon)197 void ro_gui_progress_bar_set_icon(struct progress_bar *pb, const char *icon)
198 {
199 	assert(pb);
200 
201 	if (!strcmp(icon, pb->icon))
202 		return;
203 	if (!icon)
204 		pb->icon[0] = '\0';
205 	else {
206 		strncpy(pb->icon, icon, 12);
207 		pb->icon[12] = '\0';
208 	}
209 	pb->recalculate = true;
210 	xwimp_force_redraw(pb->w, 0, 0, 32, 32);
211 	ro_gui_progress_bar_update(pb, pb->cur_width, pb->cur_height);
212 }
213 
214 
215 /**
216  * Set the value of a progress bar
217  *
218  * \param  pb  the progress bar to set the value for
219  * \param  value  the value to use
220  */
ro_gui_progress_bar_set_value(struct progress_bar * pb,unsigned int value)221 void ro_gui_progress_bar_set_value(struct progress_bar *pb, unsigned int value)
222 {
223 	assert(pb);
224 
225 	pb->value = value;
226 	if (pb->value > pb->range)
227 		pb->range = pb->value;
228 	ro_gui_progress_bar_update(pb, pb->cur_width, pb->cur_height);
229 }
230 
231 
232 /**
233  * Get the value of a progress bar
234  *
235  * \param  pb  the progress bar to get the value of
236  * \return the current value
237  */
ro_gui_progress_bar_get_value(struct progress_bar * pb)238 unsigned int ro_gui_progress_bar_get_value(struct progress_bar *pb)
239 {
240 	assert(pb);
241 
242 	return pb->value;
243 }
244 
245 
246 /**
247  * Set the range of a progress bar
248  *
249  * \param  pb  the progress bar to set the range for
250  * \param  range  the range to use
251  */
ro_gui_progress_bar_set_range(struct progress_bar * pb,unsigned int range)252 void ro_gui_progress_bar_set_range(struct progress_bar *pb, unsigned int range)
253 {
254 	assert(pb);
255 
256 	pb->range = range;
257 	if (pb->value > pb->range)
258 		pb->value = pb->range;
259 	ro_gui_progress_bar_update(pb, pb->cur_width, pb->cur_height);
260 }
261 
262 
263 /**
264  * Get the range of a progress bar
265  *
266  * \param  pb  the progress bar to get the range of
267  * \return the current range
268  */
ro_gui_progress_bar_get_range(struct progress_bar * pb)269 unsigned int ro_gui_progress_bar_get_range(struct progress_bar *pb)
270 {
271 	assert(pb);
272 
273 	return pb->range;
274 }
275 
276 
277 /**
278  * Update the progress bar to a new dimension.
279  *
280  * \param  pb  the progress bar to update
281  * \param  width  the new progress bar width
282  * \param  height  the new progress bar height
283  */
ro_gui_progress_bar_update(struct progress_bar * pb,int width,int height)284 void ro_gui_progress_bar_update(struct progress_bar *pb, int width, int height)
285 {
286   	wimp_draw redraw;
287 	os_error *error;
288 	osbool more;
289   	os_box cur;
290 
291 	/* don't allow negative dimensions */
292 	width = max(width, 0);
293 	height = max(height, 0);
294 
295  	/* update the animation state */
296 	if ((pb->value == 0) || (pb->value == pb->range)) {
297 		if (pb->animating) {
298 			riscos_schedule(-1, ro_gui_progress_bar_animate, pb);
299 		}
300 		pb->animating = false;
301 	} else {
302 	  	if (!pb->animating) {
303 			riscos_schedule(200, ro_gui_progress_bar_animate, pb);
304 		}
305 		pb->animating = true;
306 	}
307 
308   	/* get old and new positions */
309   	cur = pb->visible;
310   	pb->recalculate = true;
311   	ro_gui_progress_bar_calculate(pb, width, height);
312 
313   	/* see if the progress bar hasn't moved. we don't need to consider
314   	 * the left edge moving as this is handled by the icon setting
315   	 * function */
316   	if (cur.x1 == pb->visible.x1)
317   		return;
318 
319   	/* if size has decreased then we must force a redraw */
320   	if (cur.x1 > pb->visible.x1) {
321   		xwimp_force_redraw(pb->w, pb->visible.x1, pb->visible.y0,
322   				cur.x1, pb->visible.y1);
323   		return;
324   	}
325 
326   	/* perform a minimal redraw update */
327 	redraw.w = pb->w;
328 	redraw.box = pb->visible;
329 	redraw.box.x0 = cur.x1;
330 	error = xwimp_update_window(&redraw, &more);
331 	if (error) {
332 		NSLOG(netsurf, INFO, "Error getting update window: 0x%x: %s",
333 		      error->errnum, error->errmess);
334 		return;
335 	}
336 	if (more)
337 		ro_gui_progress_bar_redraw_window(&redraw, pb);
338 }
339 
340 
341 /**
342  * Process a WIMP redraw request
343  *
344  * \param  redraw  the redraw request to process
345  */
ro_gui_progress_bar_redraw(wimp_draw * redraw)346 void ro_gui_progress_bar_redraw(wimp_draw *redraw)
347 {
348 	struct progress_bar *pb;
349 	os_error *error;
350 	osbool more;
351 
352 	pb = (struct progress_bar *)ro_gui_wimp_event_get_user_data(redraw->w);
353 	assert(pb);
354 
355 	error = xwimp_redraw_window(redraw, &more);
356 	if (error) {
357 		NSLOG(netsurf, INFO, "xwimp_redraw_window: 0x%x: %s",
358 		      error->errnum, error->errmess);
359 		return;
360 	}
361 	if (more)
362 		ro_gui_progress_bar_redraw_window(redraw, pb);
363 }
364 
365 
366 /**
367  * Animate the progress bar
368  *
369  * \param  p  the progress bar to animate
370  */
ro_gui_progress_bar_animate(void * p)371 void ro_gui_progress_bar_animate(void *p)
372 {
373   	wimp_draw redraw;
374 	os_error *error;
375 	osbool more;
376 	struct progress_bar *pb = p;
377 
378 	if (!progress_icon)
379 		return;
380 	pb->offset -= 6;
381 	if (pb->offset < 0)
382 		pb->offset += progress_width * 2;
383 
384 	if (pb->animating) {
385 		riscos_schedule(200, ro_gui_progress_bar_animate, pb);
386 	}
387 
388 	redraw.w = pb->w;
389 	redraw.box = pb->visible;
390 	error = xwimp_update_window(&redraw, &more);
391 	if (error != NULL) {
392 		NSLOG(netsurf, INFO, "Error getting update window: '%s'",
393 		      error->errmess);
394 		return;
395 	}
396 	if (more)
397 		ro_gui_progress_bar_redraw_window(&redraw, pb);
398 }
399 
400 
401 /**
402  * Calculate the position of the progress bar
403  *
404  * \param  pb  the progress bar to recalculate
405  * \param  width  the width of the progress bar
406  * \param  height  the height of the progress bar
407  * \return the address of the associated icon, or NULL
408  */
ro_gui_progress_bar_calculate(struct progress_bar * pb,int width,int height)409 void ro_gui_progress_bar_calculate(struct progress_bar *pb, int width,
410 		int height)
411 {
412 	int icon_width, icon_height;
413 	int icon_x0 = 0, icon_y0 = 0, progress_x0, progress_x1;
414 	osspriteop_header *icon = NULL;
415 	bool icon_redraw = false;
416 
417 	/* try to use cached values */
418 	if ((!pb->recalculate) && (pb->cur_width == width) &&
419 			(pb->cur_height == height))
420 		return;
421 
422 	/* update cache status */
423 	pb->recalculate = false;
424 	pb->cur_width = width;
425 	pb->cur_height = height;
426 
427 	/* get the window dimensions */
428 	width -= MARGIN * 2;
429 	icon_width = icon_height = 0;
430 	progress_x0 = MARGIN;
431 
432 	/* get the icon information */
433 	if (progress_bar_definition.sprite_area != wimpspriteop_AREA) {
434 		int progress_ymid = height / 2;
435 		os_error *error;
436 		error = xosspriteop_read_sprite_info(osspriteop_USER_AREA,
437 				progress_bar_definition.sprite_area,
438 				(osspriteop_id)pb->icon,
439 				&icon_width, &icon_height, 0, 0);
440 		if (!error) {
441 			error = xosspriteop_select_sprite(osspriteop_USER_AREA,
442 					progress_bar_definition.sprite_area,
443 					(osspriteop_id)pb->icon, &icon);
444 		}
445 		if (!error) {
446 			progress_x0 += 32 + MARGIN;
447 			width -= 32 + MARGIN;
448 			icon_x0 = MARGIN + 16 - icon_width;
449 			icon_y0 = progress_ymid - icon_height;
450 			if (width < -MARGIN) {
451 				icon_x0 += width + MARGIN;
452 				icon_redraw = true;
453 			}
454 		}
455 	}
456 
457 	/* update the icon */
458 	if ((pb->icon_obscured) || (icon_redraw)) {
459 		if (icon_x0 != pb->icon_x0)
460 			xwimp_force_redraw(pb->w, 0, 0, 32 + MARGIN, 65536);
461 	}
462 	pb->icon_obscured = icon_redraw;
463 
464 	progress_x1 = progress_x0;
465 	if ((pb->range > 0) && (width > 0))
466 		progress_x1 += (width * pb->value) / pb->range;
467 
468 	pb->visible.x0 = progress_x0;
469 	pb->visible.y0 = MARGIN;
470 	pb->visible.x1 = progress_x1;
471 	pb->visible.y1 = height - MARGIN;
472 	pb->icon_x0 = icon_x0;
473 	pb->icon_y0 = icon_y0;
474 	pb->icon_img = icon;
475 }
476 
477 
478 /**
479  * Redraw a section of a progress bar window
480  *
481  * \param  redraw  the section of the window to redraw
482  * \param  pb  the progress bar to redraw
483  */
ro_gui_progress_bar_redraw_window(wimp_draw * redraw,struct progress_bar * pb)484 void ro_gui_progress_bar_redraw_window(wimp_draw *redraw,
485 		struct progress_bar *pb)
486 {
487 	osbool more = true;
488 	struct rect clip;
489 	int progress_ymid;
490 	struct redraw_context ctx = {
491 		.interactive = true,
492 		.background_images = true,
493 		.plot = &ro_plotters
494 	};
495 
496 	/* initialise the plotters */
497   	ro_plot_origin_x = 0;
498   	ro_plot_origin_y = 0;
499 
500 	/* recalculate the progress bar */
501 	ro_gui_progress_bar_calculate(pb, redraw->box.x1 - redraw->box.x0,
502 			redraw->box.y1 - redraw->box.y0);
503 	progress_ymid = redraw->box.y0 + pb->visible.y0 +
504 			((pb->visible.y1 - pb->visible.y0) >> 1);
505 
506 	/* redraw the window */
507 	while (more) {
508 		os_error *error;
509 		if (pb->icon)
510 			_swix(Tinct_PlotAlpha, _IN(2) | _IN(3) | _IN(4) | _IN(7),
511 					pb->icon_img,
512 					redraw->box.x0 + pb->icon_x0,
513 					redraw->box.y0 + pb->icon_y0,
514 					tinct_ERROR_DIFFUSE);
515 		if (!pb->icon_obscured) {
516 		  	clip.x0 = max(redraw->clip.x0,
517 		  			redraw->box.x0 + pb->visible.x0) >> 1;
518 			clip.y0 = -min(redraw->clip.y1,
519 		  			redraw->box.y0 + pb->visible.y1) >> 1;
520 			clip.x1 = min(redraw->clip.x1,
521 					redraw->box.x0 + pb->visible.x1) >> 1;
522 		  	clip.y1 = -max(redraw->clip.y0,
523 					redraw->box.y0 + pb->visible.y0) >> 1;
524 		  	if ((clip.x0 < clip.x1) && (clip.y0 < clip.y1)) {
525 				if (progress_icon) {
526 			  		ctx.plot->clip(&ctx, &clip);
527 					_swix(Tinct_Plot, _IN(2) | _IN(3) | _IN(4) | _IN(7),
528 							progress_icon,
529 							redraw->box.x0 - pb->offset,
530 							progress_ymid - progress_height,
531 							tinct_FILL_HORIZONTALLY);
532 				} else {
533 				  	ctx.plot->rectangle(&ctx,
534 							    plot_style_fill_red,
535 							    &clip);
536 			  	}
537 			}
538 		}
539 		error = xwimp_get_rectangle(redraw, &more);
540 		if (error) {
541 			NSLOG(netsurf, INFO, "xwimp_get_rectangle: 0x%x: %s",
542 			      error->errnum, error->errmess);
543 			return;
544 		}
545 	}
546 }
547