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