1 /*
2  * Copyright 2005 Adrian Lees <adrianl@users.sourceforge.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 #include <stdlib.h>
20 #include <string.h>
21 #include <stdbool.h>
22 
23 #include "utils/log.h"
24 #include "utils/messages.h"
25 #include "utils/utf8.h"
26 #include "utils/utils.h"
27 
28 #include "riscos/gui.h"
29 #include "riscos/query.h"
30 #include "riscos/wimp.h"
31 #include "riscos/wimp_event.h"
32 #include "riscos/ucstables.h"
33 #include "riscos/dialog.h"
34 
35 #define ICON_QUERY_MESSAGE 0
36 #define ICON_QUERY_YES 1
37 #define ICON_QUERY_NO 2
38 #define ICON_QUERY_HELP 3
39 
40 /** Data for a query window */
41 struct gui_query_window
42 {
43 	struct gui_query_window *prev;	/** Previous query in list */
44 	struct gui_query_window *next;	/** Next query in list */
45 
46 	query_id id;	/** unique ID number for this query */
47 	wimp_w window;	/** RISC OS window handle */
48 
49 	const query_callback *cb;	/** Table of callback functions */
50 	void *pw;	/** Handle passed to callback functions */
51 
52 	bool default_confirm;	/** Default action is to confirm */
53 };
54 
55 
56 /** Next unallocated query id */
57 static query_id next_id = (query_id)1;
58 
59 /** List of all query windows. */
60 static struct gui_query_window *gui_query_window_list = 0;
61 
62 /** Template for a query window. */
63 static struct wimp_window *query_template;
64 
65 /** Widths of Yes and No buttons */
66 static int query_yes_width = 0;
67 static int query_no_width  = 0;
68 
69 static struct gui_query_window *ro_gui_query_window_lookup_id(query_id id);
70 
71 static bool ro_gui_query_click(wimp_pointer *pointer);
72 static void ro_gui_query_close(wimp_w w);
73 static bool ro_gui_query_apply(wimp_w w);
74 
75 
ro_gui_query_init(void)76 void ro_gui_query_init(void)
77 {
78 	query_template = ro_gui_dialog_load_template("query");
79 }
80 
81 
82 /**
83  * Lookup a query window using its ID number
84  *
85  * \param  id  id to search for
86  * \return pointer to query window or NULL
87  */
88 
ro_gui_query_window_lookup_id(query_id id)89 struct gui_query_window *ro_gui_query_window_lookup_id(query_id id)
90 {
91 	struct gui_query_window *qw = gui_query_window_list;
92 	while (qw && qw->id != id)
93 		qw = qw->next;
94 	return qw;
95 }
96 
97 
98 /**
99  * Display a query to the user, requesting a response, near the current
100  * pointer position to keep the required mouse travel small, but also
101  * protecting against spurious mouse clicks.
102  *
103  * \param  query   message token of query
104  * \param  detail  parameter used in expanding tokenised message
105  * \param  cb      table of callback functions to be called when user responds
106  * \param  pw      handle to be passed to callback functions
107  * \param  yes     text to use for 'Yes' button' (or NULL for default)
108  * \param  no      text to use for 'No' button (or NULL for default)
109  * \return id number of the query (or QUERY_INVALID if it failed)
110  */
111 
query_user(const char * query,const char * detail,const query_callback * cb,void * pw,const char * yes,const char * no)112 query_id query_user(const char *query, const char *detail,
113 		const query_callback *cb, void *pw,
114 		const char *yes, const char *no)
115 {
116 	wimp_pointer pointer;
117 	if (xwimp_get_pointer_info(&pointer))
118 		pointer.pos.y = pointer.pos.x = -1;
119 
120 	return query_user_xy(query, detail, cb, pw, yes, no,
121 				pointer.pos.x, pointer.pos.y);
122 }
123 
124 
125 /**
126  * Display a query to the user, requesting a response, at a specified
127  * screen position (x,y). The window is positioned relative to the given
128  * location such that the required mouse travel is small, but non-zero
129  * for protection spurious double-clicks.
130  *
131  * \param  query   message token of query
132  * \param  detail  parameter used in expanding tokenised message
133  * \param  cb      table of callback functions to be called when user responds
134  * \param  pw      handle to be passed to callback functions
135  * \param  yes     text to use for 'Yes' button' (or NULL for default)
136  * \param  no      text to use for 'No' button (or NULL for default)
137  * \param  x       x position in screen coordinates (-1 = centred on screen)
138  * \param  y       y position in screen coordinates (-1 = centred on screen)
139  * \return id number of the query (or QUERY_INVALID if it failed)
140  */
141 
query_user_xy(const char * query,const char * detail,const query_callback * cb,void * pw,const char * yes,const char * no,int x,int y)142 query_id query_user_xy(const char *query, const char *detail,
143 		const query_callback *cb, void *pw,
144 		const char *yes, const char *no,
145 		int x, int y)
146 {
147 	struct gui_query_window *qw;
148 	char query_buffer[300];
149 	os_error *error;
150 	wimp_icon *icn;
151 	int width;
152 	int len;
153 	int tx;
154 	char *local_text = NULL;
155 	nserror err;
156 
157 	qw = malloc(sizeof(struct gui_query_window));
158 	if (!qw) {
159 		ro_warn_user("NoMemory", NULL);
160 		return QUERY_INVALID;
161 	}
162 
163 	qw->cb = cb;
164 	qw->pw = pw;
165 	qw->id = next_id++;
166 	qw->default_confirm = false;
167 
168 	if (next_id == QUERY_INVALID)
169 		next_id++;
170 
171 	if (!yes) yes = messages_get("Yes");
172 	if (!no) no = messages_get("No");
173 
174 	/* set the text of the 'Yes' button and size accordingly */
175 	err = utf8_to_local_encoding(yes, 0, &local_text);
176 	if (err != NSERROR_OK) {
177 		assert(err != NSERROR_BAD_ENCODING);
178 		NSLOG(netsurf, INFO, "utf8_to_local_encoding_failed");
179 		local_text = NULL;
180 	}
181 
182 	icn = &query_template->icons[ICON_QUERY_YES];
183 	len = strlen(local_text ? local_text : yes);
184 	len = max(len, icn->data.indirected_text.size - 1);
185 	memcpy(icn->data.indirected_text.text,
186 			local_text ? local_text: yes, len);
187 	icn->data.indirected_text.text[len] = '\0';
188 
189 	free(local_text);
190 	local_text = NULL;
191 
192 	error = xwimptextop_string_width(icn->data.indirected_text.text, len, &width);
193 	if (error) {
194 		NSLOG(netsurf, INFO, "xwimptextop_string_width: 0x%x:%s",
195 		      error->errnum, error->errmess);
196 		width = len * 16;
197 	}
198 	if (!query_yes_width) query_yes_width = icn->extent.x1 - icn->extent.x0;
199 	width += 44;
200 	if (width < query_yes_width)
201 		width = query_yes_width;
202 	icn->extent.x0 = tx = icn->extent.x1 - width;
203 
204 	/* set the text of the 'No' button and size accordingly */
205 	err = utf8_to_local_encoding(no, 0, &local_text);
206 	if (err != NSERROR_OK) {
207 		assert(err != NSERROR_BAD_ENCODING);
208 		NSLOG(netsurf, INFO, "utf8_to_local_encoding_failed");
209 		local_text = NULL;
210 	}
211 
212 	icn = &query_template->icons[ICON_QUERY_NO];
213 	len = strlen(local_text ? local_text : no);
214 	len = max(len, icn->data.indirected_text.size - 1);
215 	memcpy(icn->data.indirected_text.text,
216 			local_text ? local_text : no, len);
217 	icn->data.indirected_text.text[len] = '\0';
218 
219 	free(local_text);
220 	local_text = NULL;
221 
222 	if (!query_no_width) query_no_width = icn->extent.x1 - icn->extent.x0;
223 	icn->extent.x1 = tx - 16;
224 	error = xwimptextop_string_width(icn->data.indirected_text.text, len, &width);
225 	if (error) {
226 		NSLOG(netsurf, INFO, "xwimptextop_string_width: 0x%x:%s",
227 		      error->errnum, error->errmess);
228 		width = len * 16;
229 	}
230 	width += 28;
231 	if (width < query_no_width)
232 		width = query_no_width;
233 	icn->extent.x0 = icn->extent.x1 - width;
234 
235 	error = xwimp_create_window(query_template, &qw->window);
236 	if (error) {
237 		ro_warn_user("WimpError", error->errmess);
238 		free(qw);
239 		return QUERY_INVALID;
240 	}
241 
242 	snprintf(query_buffer, sizeof query_buffer, "%s %s",
243 			messages_get(query), detail ? detail : "");
244 	query_buffer[sizeof query_buffer - 1] = 0;
245 
246 	ro_gui_set_icon_string(qw->window, ICON_QUERY_MESSAGE,
247 			query_buffer, true);
248 
249 	xwimp_set_icon_state(qw->window, ICON_QUERY_HELP,
250 			wimp_ICON_DELETED, wimp_ICON_DELETED);
251 
252 	if (x >= 0 && y >= 0) {
253 		x -= tx - 8;
254 		y += (query_template->visible.y1 - query_template->visible.y0) / 2;
255 		ro_gui_dialog_open_xy(qw->window, x, y);
256 	}
257 	else
258 		ro_gui_dialog_open(qw->window);
259 
260 	ro_gui_wimp_event_set_user_data(qw->window, qw);
261 	ro_gui_wimp_event_register_mouse_click(qw->window, ro_gui_query_click);
262 	ro_gui_wimp_event_register_cancel(qw->window, ICON_QUERY_NO);
263 	ro_gui_wimp_event_register_ok(qw->window, ICON_QUERY_YES, ro_gui_query_apply);
264 	ro_gui_wimp_event_register_close_window(qw->window, ro_gui_query_close);
265 
266 	error = xwimp_set_caret_position(qw->window, (wimp_i)-1, 0, 0, 1 << 25, -1);
267 	if (error) {
268 		NSLOG(netsurf, INFO, "xwimp_get_caret_position: 0x%x : %s",
269 		      error->errnum, error->errmess);
270 		ro_warn_user("WimpError", error->errmess);
271 	}
272 
273 	/* put this query window at the head of our list */
274 	if (gui_query_window_list)
275 		gui_query_window_list->prev = qw;
276 
277 	qw->prev = NULL;
278 	qw->next = gui_query_window_list;
279 	gui_query_window_list = qw;
280 
281 	return qw->id;
282 }
283 
284 
285 /**
286  * Close a query window without waiting for a response from the user.
287  * (should normally only be called if the user has responded in some other
288  *  way of which the query window in unaware.)
289  *
290  * \param  id  id of query window to close
291  */
292 
query_close(query_id id)293 void query_close(query_id id)
294 {
295 	struct gui_query_window *qw = ro_gui_query_window_lookup_id(id);
296 	if (!qw)
297 		return;
298 	ro_gui_query_close(qw->window);
299 
300 }
301 
302 
ro_gui_query_window_bring_to_front(query_id id)303 void ro_gui_query_window_bring_to_front(query_id id)
304 {
305 	struct gui_query_window *qw = ro_gui_query_window_lookup_id(id);
306 	if (qw) {
307 		os_error *error;
308 
309 		ro_gui_dialog_open(qw->window);
310 
311 		error = xwimp_set_caret_position(qw->window, (wimp_i)-1, 0, 0, 1 << 25, -1);
312 		if (error) {
313 			NSLOG(netsurf, INFO,
314 			      "xwimp_get_caret_position: 0x%x : %s",
315 			      error->errnum,
316 			      error->errmess);
317 			ro_warn_user("WimpError", error->errmess);
318 		}
319 	}
320 }
321 
322 
323 /**
324  * Handle closing of query dialog
325  */
ro_gui_query_close(wimp_w w)326 void ro_gui_query_close(wimp_w w)
327 {
328 	struct gui_query_window *qw;
329 	os_error *error;
330 
331 	qw = (struct gui_query_window *)ro_gui_wimp_event_get_user_data(w);
332 
333 	ro_gui_dialog_close(w);
334 	error = xwimp_delete_window(qw->window);
335 	if (error) {
336 		NSLOG(netsurf, INFO, "xwimp_delete_window: 0x%x:%s",
337 		      error->errnum, error->errmess);
338 		ro_warn_user("WimpError", error->errmess);
339 	}
340 	ro_gui_wimp_event_finalise(w);
341 
342 	/* remove from linked-list of query windows and release memory */
343 	if (qw->prev)
344 		qw->prev->next = qw->next;
345 	else
346 		gui_query_window_list = qw->next;
347 
348 	if (qw->next)
349 		qw->next->prev = qw->prev;
350 	free(qw);
351 }
352 
353 
354 /**
355  * Handle acceptance of query dialog
356  */
ro_gui_query_apply(wimp_w w)357 bool ro_gui_query_apply(wimp_w w)
358 {
359 	struct gui_query_window *qw;
360 	const query_callback *cb;
361 
362 	qw = (struct gui_query_window *)ro_gui_wimp_event_get_user_data(w);
363 	cb = qw->cb;
364 	cb->confirm(qw->id, QUERY_YES, qw->pw);
365 	return true;
366 }
367 
368 
369 /**
370  * Handle clicks in query dialog
371  */
ro_gui_query_click(wimp_pointer * pointer)372 bool ro_gui_query_click(wimp_pointer *pointer)
373 {
374 	struct gui_query_window *qw;
375 	const query_callback *cb;
376 
377 	qw = (struct gui_query_window *)ro_gui_wimp_event_get_user_data(pointer->w);
378 	cb = qw->cb;
379 
380 	switch (pointer->i) {
381 		case ICON_QUERY_NO:
382 			cb->cancel(qw->id, QUERY_NO, qw->pw);
383 			break;
384 		default:
385 			return false;
386 	}
387 	return false;
388 }
389