1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2008 Imendio AB
4 * Copyright (C) 2008 Sven Herzberg
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19 * USA
20 */
21
22 #include "config.h"
23 #include <string.h>
24 #include <glib/gi18n-lib.h>
25 #include <webkit/webkit.h>
26 #include "dh-assistant-view.h"
27 #include "dh-link.h"
28 #include "dh-util.h"
29 #include "dh-book-manager.h"
30 #include "dh-book.h"
31 #include "dh-window.h"
32
33 typedef struct {
34 DhBase *base;
35 DhLink *link;
36 gchar *current_search;
37 gboolean snippet_loaded;
38 } DhAssistantViewPriv;
39
40 G_DEFINE_TYPE (DhAssistantView, dh_assistant_view, WEBKIT_TYPE_WEB_VIEW);
41
42 #define GET_PRIVATE(instance) G_TYPE_INSTANCE_GET_PRIVATE \
43 (instance, DH_TYPE_ASSISTANT_VIEW, DhAssistantViewPriv)
44
45 static void
view_finalize(GObject * object)46 view_finalize (GObject *object)
47 {
48 DhAssistantViewPriv *priv = GET_PRIVATE (object);
49
50 if (priv->link) {
51 g_object_unref (priv->link);
52 }
53
54 if (priv->base) {
55 g_object_unref (priv->base);
56 }
57
58 g_free (priv->current_search);
59
60 G_OBJECT_CLASS (dh_assistant_view_parent_class)->finalize (object);
61 }
62
63 static WebKitNavigationResponse
assistant_navigation_requested(WebKitWebView * web_view,WebKitWebFrame * frame,WebKitNetworkRequest * request)64 assistant_navigation_requested (WebKitWebView *web_view,
65 WebKitWebFrame *frame,
66 WebKitNetworkRequest *request)
67 {
68 DhAssistantViewPriv *priv;
69 const gchar *uri;
70
71 priv = GET_PRIVATE (web_view);
72
73 uri = webkit_network_request_get_uri (request);
74 if (strcmp (uri, "about:blank") == 0) {
75 return WEBKIT_NAVIGATION_RESPONSE_ACCEPT;
76 }
77 else if (! priv->snippet_loaded) {
78 priv->snippet_loaded = TRUE;
79 return WEBKIT_NAVIGATION_RESPONSE_ACCEPT;
80 }
81 else if (g_str_has_prefix (uri, "file://")) {
82 GtkWidget *window;
83
84 window = dh_base_get_window (priv->base);
85 _dh_window_display_uri (DH_WINDOW (window), uri);
86 }
87
88 return WEBKIT_NAVIGATION_RESPONSE_IGNORE;
89 }
90
91 static gboolean
assistant_button_press_event(GtkWidget * widget,GdkEventButton * event)92 assistant_button_press_event (GtkWidget *widget,
93 GdkEventButton *event)
94 {
95 /* Block webkit's builtin context menu. */
96 if (event->button != 1) {
97 return TRUE;
98 }
99
100 return GTK_WIDGET_CLASS (dh_assistant_view_parent_class)->button_press_event (widget, event);
101 }
102
103 static void
dh_assistant_view_class_init(DhAssistantViewClass * klass)104 dh_assistant_view_class_init (DhAssistantViewClass* klass)
105 {
106 GObjectClass *object_class = G_OBJECT_CLASS (klass);
107 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
108 WebKitWebViewClass *web_view_class = WEBKIT_WEB_VIEW_CLASS (klass);
109
110 object_class->finalize = view_finalize;
111
112 widget_class->button_press_event = assistant_button_press_event;
113
114 web_view_class->navigation_requested = assistant_navigation_requested;
115
116 g_type_class_add_private (klass, sizeof (DhAssistantViewPriv));
117 }
118
119 static void
dh_assistant_view_init(DhAssistantView * view)120 dh_assistant_view_init (DhAssistantView *view)
121 {
122 }
123
124 DhBase*
dh_assistant_view_get_base(DhAssistantView * view)125 dh_assistant_view_get_base (DhAssistantView *view)
126 {
127 DhAssistantViewPriv *priv;
128
129 g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), NULL);
130
131 priv = GET_PRIVATE (view);
132
133 return priv->base;
134 }
135
136 GtkWidget*
dh_assistant_view_new(void)137 dh_assistant_view_new (void)
138 {
139 return g_object_new (DH_TYPE_ASSISTANT_VIEW, NULL);
140 }
141
142 static const gchar *
find_in_buffer(const gchar * buffer,const gchar * key,gsize length,gsize key_length)143 find_in_buffer (const gchar *buffer,
144 const gchar *key,
145 gsize length,
146 gsize key_length)
147 {
148 gsize m = 0;
149 gsize i = 0;
150
151 while (i < length) {
152 if (key[m] == buffer[i]) {
153 m++;
154 if (m == key_length) {
155 return buffer + i - m + 1;
156 }
157 } else {
158 m = 0;
159 }
160 i++;
161 }
162
163 return NULL;
164 }
165
166 /**
167 * dh_assistant_view_set_link:
168 * @view: an devhelp assistant view
169 * @link: the #DhLink
170 *
171 * Open @link in the assistant view, if %NULL the view will be blanked.
172 *
173 * Return value: %TRUE if the requested link is open, %FALSE otherwise.
174 **/
175 gboolean
dh_assistant_view_set_link(DhAssistantView * view,DhLink * link)176 dh_assistant_view_set_link (DhAssistantView *view,
177 DhLink *link)
178 {
179 DhAssistantViewPriv *priv;
180 gchar *uri;
181 const gchar *anchor;
182 gchar *filename;
183 GMappedFile *file;
184 const gchar *contents;
185 gsize length;
186 gchar *key;
187 gsize key_length;
188 gsize offset = 0;
189 const gchar *start;
190 const gchar *end;
191
192 g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE);
193
194 priv = GET_PRIVATE (view);
195
196 if (priv->link == link) {
197 return TRUE;
198 }
199
200 if (priv->link) {
201 dh_link_unref (priv->link);
202 priv->link = NULL;
203 }
204
205 if (link) {
206 link = dh_link_ref (link);
207 } else {
208 webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank");
209 return TRUE;
210 }
211
212 uri = dh_link_get_uri (link);
213 anchor = strrchr (uri, '#');
214 if (anchor) {
215 filename = g_strndup (uri, anchor - uri);
216 anchor++;
217 g_free (uri);
218 } else {
219 g_free (uri);
220 return FALSE;
221 }
222
223 if (g_str_has_prefix (filename, "file://"))
224 offset = 7;
225
226 file = g_mapped_file_new (filename + offset, FALSE, NULL);
227 if (!file) {
228 g_free (filename);
229 return FALSE;
230 }
231
232 contents = g_mapped_file_get_contents (file);
233 length = g_mapped_file_get_length (file);
234
235 key = g_strdup_printf ("<a name=\"%s\"", anchor);
236 key_length = strlen (key);
237
238 start = find_in_buffer (contents, key, length, key_length);
239 g_free (key);
240
241 end = NULL;
242
243 if (start) {
244 const gchar *start_key;
245 const gchar *end_key;
246
247 length -= start - contents;
248
249 start_key = "<pre class=\"programlisting\">";
250
251 start = find_in_buffer (start,
252 start_key,
253 length,
254 strlen (start_key));
255
256 end_key = "<div class=\"refsect";
257
258 if (start) {
259 end = find_in_buffer (start, end_key,
260 length - strlen (start_key),
261 strlen (end_key));
262 if (!end) {
263 end_key = "<div class=\"footer";
264 end = find_in_buffer (start, end_key,
265 length - strlen (start_key),
266 strlen (end_key));
267 }
268 }
269 }
270
271 if (start && end) {
272 gchar *buf;
273 gboolean break_line;
274 const gchar *function;
275 gchar *stylesheet;
276 gchar *javascript;
277 gchar *html;
278
279 buf = g_strndup (start, end-start);
280
281 /* Try to reformat function signatures so they take less
282 * space and look nicer. Don't reformat things that don't
283 * look like functions.
284 */
285 switch (dh_link_get_link_type (link)) {
286 case DH_LINK_TYPE_FUNCTION:
287 break_line = TRUE;
288 function = "onload=\"reformatSignature()\"";
289 break;
290 case DH_LINK_TYPE_MACRO:
291 break_line = TRUE;
292 function = "onload=\"cleanupSignature()\"";
293 break;
294 default:
295 break_line = FALSE;
296 function = "";
297 break;
298 }
299
300 if (break_line) {
301 gchar *name;
302
303 name = strstr (buf, dh_link_get_name (link));
304 if (name && name > buf) {
305 name[-1] = '\n';
306 }
307 }
308
309 stylesheet = dh_util_build_data_filename ("devhelp",
310 "assistant",
311 "assistant.css",
312 NULL);
313 javascript = dh_util_build_data_filename ("devhelp",
314 "assistant",
315 "assistant.js",
316 NULL);
317
318 html = g_strdup_printf (
319 "<html>"
320 "<head>"
321 "<link rel=\"stylesheet\" type=\"text/css\" href=\"file://%s\"/>"
322 "<script src=\"file://%s\"></script>"
323 "</head>"
324 "<body %s>"
325 "<div class=\"title\">%s: <a href=\"%s\">%s</a></div>"
326 "<div class=\"subtitle\">%s %s</div>"
327 "<div class=\"content\">%s</div>"
328 "</body>"
329 "</html>",
330 stylesheet,
331 javascript,
332 function,
333 dh_link_get_type_as_string (link),
334 dh_link_get_uri (link),
335 dh_link_get_name (link),
336 _("Book:"),
337 dh_link_get_book_name (link),
338 buf);
339 g_free (buf);
340
341 g_free (stylesheet);
342 g_free (javascript);
343
344 priv->snippet_loaded = FALSE;
345 webkit_web_view_load_string (
346 WEBKIT_WEB_VIEW (view),
347 html,
348 "text/html",
349 NULL,
350 filename);
351
352 g_free (html);
353 } else {
354 webkit_web_view_load_uri (WEBKIT_WEB_VIEW (view), "about:blank");
355 }
356
357 #if GLIB_CHECK_VERSION(2,21,3)
358 g_mapped_file_unref (file);
359 #else
360 g_mapped_file_free (file);
361 #endif
362
363 g_free (filename);
364
365 return TRUE;
366 }
367
368 gboolean
dh_assistant_view_search(DhAssistantView * view,const gchar * str)369 dh_assistant_view_search (DhAssistantView *view,
370 const gchar *str)
371 {
372 DhAssistantViewPriv *priv;
373 const gchar *name;
374 DhLink *link;
375 DhLink *exact_link;
376 DhLink *prefix_link;
377 DhBookManager *book_manager;
378 GList *books;
379
380 g_return_val_if_fail (DH_IS_ASSISTANT_VIEW (view), FALSE);
381 g_return_val_if_fail (str, FALSE);
382
383 priv = GET_PRIVATE (view);
384
385 /* Filter out very short strings. */
386 if (strlen (str) < 4) {
387 return FALSE;
388 }
389
390 if (priv->current_search && strcmp (priv->current_search, str) == 0) {
391 return FALSE;
392 }
393 g_free (priv->current_search);
394 priv->current_search = g_strdup (str);
395
396 book_manager = dh_base_get_book_manager (dh_assistant_view_get_base (view));
397
398 prefix_link = NULL;
399 exact_link = NULL;
400
401 for (books = dh_book_manager_get_books (book_manager);
402 !exact_link && books;
403 books = g_list_next (books)) {
404 GList *l;
405
406 for (l = dh_book_get_keywords (DH_BOOK (books->data));
407 l && exact_link == NULL;
408 l = l->next) {
409 DhLinkType type;
410
411 link = l->data;
412
413 type = dh_link_get_link_type (link);
414
415 if (type == DH_LINK_TYPE_BOOK ||
416 type == DH_LINK_TYPE_PAGE ||
417 type == DH_LINK_TYPE_KEYWORD) {
418 continue;
419 }
420
421 name = dh_link_get_name (link);
422 if (strcmp (name, str) == 0) {
423 exact_link = link;
424 }
425 else if (g_str_has_prefix (name, str)) {
426 /* Prefer shorter prefix matches. */
427 if (!prefix_link) {
428 prefix_link = link;
429 }
430 else if (strlen (dh_link_get_name (prefix_link)) > strlen (name)) {
431 prefix_link = link;
432 }
433 }
434 }
435 }
436
437 if (exact_link) {
438 /*g_print ("exact hit: '%s' '%s'\n", exact_link->name, str);*/
439 dh_assistant_view_set_link (view, exact_link);
440 }
441 else if (prefix_link) {
442 /*g_print ("prefix hit: '%s' '%s'\n", prefix_link->name, str);*/
443 dh_assistant_view_set_link (view, prefix_link);
444 } else {
445 /*g_print ("no hit\n");*/
446 /*assistant_view_set_link (view, NULL);*/
447 return FALSE;
448 }
449
450 return TRUE;
451 }
452
453 void
dh_assistant_view_set_base(DhAssistantView * view,DhBase * base)454 dh_assistant_view_set_base (DhAssistantView *view,
455 DhBase *base)
456 {
457 DhAssistantViewPriv *priv;
458
459 g_return_if_fail (DH_IS_ASSISTANT_VIEW (view));
460 g_return_if_fail (DH_IS_BASE (base));
461
462 priv = GET_PRIVATE (view);
463
464 priv->base = g_object_ref (base);
465 }
466