1 /*
2  * xed-docinfo-plugin.c
3  *
4  * Copyright (C) 2002-2005 Paolo Maggi
5  *
6  * This program 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; either version 2, or (at your option)
9  * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  */
21 
22 #include <config.h>
23 #include <string.h> /* For strlen (...) */
24 #include <glib/gi18n.h>
25 #include <pango/pango-break.h>
26 #include <gmodule.h>
27 
28 #include <xed/xed-window.h>
29 #include <xed/xed-window-activatable.h>
30 #include <xed/xed-debug.h>
31 #include <xed/xed-utils.h>
32 
33 #include "xed-docinfo-plugin.h"
34 
35 #define MENU_PATH "/MenuBar/ToolsMenu/ToolsOps_2"
36 
37 struct _XedDocInfoPluginPrivate
38 {
39     XedWindow *window;
40 
41     GtkActionGroup *action_group;
42     guint ui_id;
43 
44     GtkWidget *dialog;
45     GtkWidget *file_name_label;
46     GtkWidget *lines_label;
47     GtkWidget *words_label;
48     GtkWidget *chars_label;
49     GtkWidget *chars_ns_label;
50     GtkWidget *bytes_label;
51     GtkWidget *selection_vbox;
52     GtkWidget *selected_lines_label;
53     GtkWidget *selected_words_label;
54     GtkWidget *selected_chars_label;
55     GtkWidget *selected_chars_ns_label;
56     GtkWidget *selected_bytes_label;
57 };
58 
59 enum
60 {
61     PROP_0,
62     PROP_WINDOW
63 };
64 
65 static void xed_window_activatable_iface_init (XedWindowActivatableInterface *iface);
66 
67 G_DEFINE_DYNAMIC_TYPE_EXTENDED (XedDocInfoPlugin,
68                                 xed_docinfo_plugin,
69                                 PEAS_TYPE_EXTENSION_BASE,
70                                 0,
71                                 G_IMPLEMENT_INTERFACE_DYNAMIC (XED_TYPE_WINDOW_ACTIVATABLE,
72                                                                xed_window_activatable_iface_init)
73                                 G_ADD_PRIVATE_DYNAMIC (XedDocInfoPlugin))
74 
75 static void
calculate_info(XedDocument * doc,GtkTextIter * start,GtkTextIter * end,gint * chars,gint * words,gint * white_chars,gint * bytes)76 calculate_info (XedDocument *doc,
77                 GtkTextIter *start,
78                 GtkTextIter *end,
79                 gint        *chars,
80                 gint        *words,
81                 gint        *white_chars,
82                 gint        *bytes)
83 {
84     gchar *text;
85 
86     xed_debug (DEBUG_PLUGINS);
87 
88     text = gtk_text_buffer_get_slice (GTK_TEXT_BUFFER (doc), start, end, TRUE);
89 
90     *chars = g_utf8_strlen (text, -1);
91     *bytes = strlen (text);
92 
93     if (*chars > 0)
94     {
95         PangoLogAttr *attrs;
96         gint i;
97 
98         attrs = g_new0 (PangoLogAttr, *chars + 1);
99 
100         pango_get_log_attrs (text, -1, 0, pango_language_from_string ("C"), attrs, *chars + 1);
101 
102         for (i = 0; i < (*chars); i++)
103         {
104             if (attrs[i].is_white)
105             {
106                 ++(*white_chars);
107             }
108 
109             if (attrs[i].is_word_start)
110             {
111                 ++(*words);
112             }
113         }
114 
115         g_free (attrs);
116     }
117     else
118     {
119         *white_chars = 0;
120         *words = 0;
121     }
122 
123     g_free (text);
124 }
125 
126 static void
update_document_info(XedDocInfoPlugin * plugin,XedDocument * doc)127 update_document_info (XedDocInfoPlugin *plugin,
128                       XedDocument *doc)
129 {
130     XedDocInfoPluginPrivate *priv;
131     GtkTextIter start, end;
132     gint words = 0;
133     gint chars = 0;
134     gint white_chars = 0;
135     gint lines = 0;
136     gint bytes = 0;
137     gchar *tmp_str;
138     gchar *doc_name;
139 
140     xed_debug (DEBUG_PLUGINS);
141 
142     priv = plugin->priv;
143 
144     gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (doc), &start, &end);
145 
146     lines = gtk_text_buffer_get_line_count (GTK_TEXT_BUFFER (doc));
147 
148     calculate_info (doc, &start, &end, &chars, &words, &white_chars, &bytes);
149 
150     if (chars == 0)
151     {
152         lines = 0;
153     }
154 
155     xed_debug_message (DEBUG_PLUGINS, "Chars: %d", chars);
156     xed_debug_message (DEBUG_PLUGINS, "Lines: %d", lines);
157     xed_debug_message (DEBUG_PLUGINS, "Words: %d", words);
158     xed_debug_message (DEBUG_PLUGINS, "Chars non-space: %d", chars - white_chars);
159     xed_debug_message (DEBUG_PLUGINS, "Bytes: %d", bytes);
160 
161     doc_name = xed_document_get_short_name_for_display (doc);
162     tmp_str = g_strdup_printf ("<span weight=\"bold\">%s</span>", doc_name);
163     gtk_label_set_markup (GTK_LABEL (priv->file_name_label), tmp_str);
164     g_free (doc_name);
165     g_free (tmp_str);
166 
167     tmp_str = g_strdup_printf("%d", lines);
168     gtk_label_set_text (GTK_LABEL (priv->lines_label), tmp_str);
169     g_free (tmp_str);
170 
171     tmp_str = g_strdup_printf("%d", words);
172     gtk_label_set_text (GTK_LABEL (priv->words_label), tmp_str);
173     g_free (tmp_str);
174 
175     tmp_str = g_strdup_printf("%d", chars);
176     gtk_label_set_text (GTK_LABEL (priv->chars_label), tmp_str);
177     g_free (tmp_str);
178 
179     tmp_str = g_strdup_printf("%d", chars - white_chars);
180     gtk_label_set_text (GTK_LABEL (priv->chars_ns_label), tmp_str);
181     g_free (tmp_str);
182 
183     tmp_str = g_strdup_printf("%d", bytes);
184     gtk_label_set_text (GTK_LABEL (priv->bytes_label), tmp_str);
185     g_free (tmp_str);
186 }
187 
188 static void
update_selection_info(XedDocInfoPlugin * plugin,XedDocument * doc)189 update_selection_info (XedDocInfoPlugin *plugin,
190                        XedDocument *doc)
191 {
192     XedDocInfoPluginPrivate *priv;
193     gboolean sel;
194     GtkTextIter start, end;
195     gint words = 0;
196     gint chars = 0;
197     gint white_chars = 0;
198     gint lines = 0;
199     gint bytes = 0;
200     gchar *tmp_str;
201 
202     xed_debug (DEBUG_PLUGINS);
203 
204     priv = plugin->priv;
205 
206     sel = gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (doc), &start, &end);
207 
208     if (sel)
209     {
210         lines = gtk_text_iter_get_line (&end) - gtk_text_iter_get_line (&start) + 1;
211 
212         calculate_info (doc, &start, &end, &chars, &words, &white_chars, &bytes);
213 
214         xed_debug_message (DEBUG_PLUGINS, "Selected chars: %d", chars);
215         xed_debug_message (DEBUG_PLUGINS, "Selected lines: %d", lines);
216         xed_debug_message (DEBUG_PLUGINS, "Selected words: %d", words);
217         xed_debug_message (DEBUG_PLUGINS, "Selected chars non-space: %d", chars - white_chars);
218         xed_debug_message (DEBUG_PLUGINS, "Selected bytes: %d", bytes);
219 
220         gtk_widget_set_sensitive (priv->selection_vbox, TRUE);
221     }
222     else
223     {
224         gtk_widget_set_sensitive (priv->selection_vbox, FALSE);
225 
226         xed_debug_message (DEBUG_PLUGINS, "Selection empty");
227     }
228 
229     if (chars == 0)
230     {
231         lines = 0;
232     }
233 
234     tmp_str = g_strdup_printf("%d", lines);
235     gtk_label_set_text (GTK_LABEL (priv->selected_lines_label), tmp_str);
236     g_free (tmp_str);
237 
238     tmp_str = g_strdup_printf("%d", words);
239     gtk_label_set_text (GTK_LABEL (priv->selected_words_label), tmp_str);
240     g_free (tmp_str);
241 
242     tmp_str = g_strdup_printf("%d", chars);
243     gtk_label_set_text (GTK_LABEL (priv->selected_chars_label), tmp_str);
244     g_free (tmp_str);
245 
246     tmp_str = g_strdup_printf("%d", chars - white_chars);
247     gtk_label_set_text (GTK_LABEL (priv->selected_chars_ns_label), tmp_str);
248     g_free (tmp_str);
249 
250     tmp_str = g_strdup_printf("%d", bytes);
251     gtk_label_set_text (GTK_LABEL (priv->selected_bytes_label), tmp_str);
252     g_free (tmp_str);
253 }
254 
255 static void
docinfo_dialog_response_cb(GtkDialog * widget,gint res_id,XedDocInfoPlugin * plugin)256 docinfo_dialog_response_cb (GtkDialog        *widget,
257                             gint              res_id,
258                             XedDocInfoPlugin *plugin)
259 {
260     XedDocInfoPluginPrivate *priv;
261 
262     priv = plugin->priv;
263 
264     switch (res_id)
265     {
266         case GTK_RESPONSE_CLOSE:
267         {
268             xed_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_CLOSE");
269             gtk_widget_destroy (priv->dialog);
270             break;
271         }
272 
273         case GTK_RESPONSE_OK:
274         {
275             XedDocument *doc;
276 
277             xed_debug_message (DEBUG_PLUGINS, "GTK_RESPONSE_OK");
278 
279             doc = xed_window_get_active_document (priv->window);
280 
281             update_document_info (plugin, doc);
282             update_selection_info (plugin, doc);
283             break;
284         }
285     }
286 }
287 
288 static void
create_docinfo_dialog(XedDocInfoPlugin * plugin)289 create_docinfo_dialog (XedDocInfoPlugin *plugin)
290 {
291     XedDocInfoPluginPrivate *priv;
292     gchar *data_dir;
293     gchar *ui_file;
294     GtkWidget *content;
295     GtkWidget *error_widget;
296     gboolean ret;
297 
298     xed_debug (DEBUG_PLUGINS);
299 
300     priv = plugin->priv;
301 
302     data_dir = peas_extension_base_get_data_dir (PEAS_EXTENSION_BASE (plugin));
303     ui_file = g_build_filename (data_dir, "docinfo.ui", NULL);
304     ret = xed_utils_get_ui_objects (ui_file,
305                                     NULL,
306                                     &error_widget,
307                                     "dialog", &priv->dialog,
308                                     "docinfo_dialog_content", &content,
309                                     "file_name_label", &priv->file_name_label,
310                                     "words_label", &priv->words_label,
311                                     "bytes_label", &priv->bytes_label,
312                                     "lines_label", &priv->lines_label,
313                                     "chars_label", &priv->chars_label,
314                                     "chars_ns_label", &priv->chars_ns_label,
315                                     "selection_vbox", &priv->selection_vbox,
316                                     "selected_words_label", &priv->selected_words_label,
317                                     "selected_bytes_label", &priv->selected_bytes_label,
318                                     "selected_lines_label", &priv->selected_lines_label,
319                                     "selected_chars_label", &priv->selected_chars_label,
320                                     "selected_chars_ns_label", &priv->selected_chars_ns_label,
321                                     NULL);
322 
323     g_free (data_dir);
324     g_free (ui_file);
325 
326     if (!ret)
327     {
328         const gchar *err_message;
329 
330         err_message = gtk_label_get_label (GTK_LABEL (error_widget));
331         xed_warning (GTK_WINDOW (priv->window), "%s", err_message);
332 
333         gtk_widget_destroy (error_widget);
334 
335         return;
336     }
337 
338     gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog), GTK_RESPONSE_OK);
339     gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), GTK_WINDOW (priv->window));
340 
341     g_signal_connect (priv->dialog, "destroy",
342                       G_CALLBACK (gtk_widget_destroyed), &priv->dialog);
343     g_signal_connect (priv->dialog, "response",
344                       G_CALLBACK (docinfo_dialog_response_cb), plugin);
345 }
346 
347 static void
docinfo_cb(GtkAction * action,XedDocInfoPlugin * plugin)348 docinfo_cb (GtkAction        *action,
349             XedDocInfoPlugin *plugin)
350 {
351     XedDocInfoPluginPrivate *priv;
352     XedDocument *doc;
353 
354     xed_debug (DEBUG_PLUGINS);
355 
356     priv = plugin->priv;
357 
358     doc = xed_window_get_active_document (priv->window);
359 
360     if (priv->dialog != NULL)
361     {
362         gtk_window_present (GTK_WINDOW (priv->dialog));
363         gtk_widget_grab_focus (GTK_WIDGET (priv->dialog));
364     }
365     else
366     {
367         create_docinfo_dialog (plugin);
368         gtk_widget_show (GTK_WIDGET (priv->dialog));
369     }
370 
371     update_document_info (plugin, doc);
372     update_selection_info (plugin, doc);
373 }
374 
375 static const GtkActionEntry action_entries[] =
376 {
377     { "DocumentStatistics",
378       NULL,
379       N_("_Document Statistics"),
380       NULL,
381       N_("Get statistical information on the current document"),
382       G_CALLBACK (docinfo_cb) }
383 };
384 
385 static void
xed_docinfo_plugin_init(XedDocInfoPlugin * plugin)386 xed_docinfo_plugin_init (XedDocInfoPlugin *plugin)
387 {
388     xed_debug_message (DEBUG_PLUGINS, "XedDocInfoPlugin initializing");
389     plugin->priv = xed_docinfo_plugin_get_instance_private (plugin);
390 }
391 
392 static void
xed_docinfo_plugin_dispose(GObject * object)393 xed_docinfo_plugin_dispose (GObject *object)
394 {
395     XedDocInfoPlugin *plugin = XED_DOCINFO_PLUGIN (object);
396 
397     xed_debug_message (DEBUG_PLUGINS, "XedDocInfoPlugin dispose");
398 
399     g_clear_object (&plugin->priv->action_group);
400     g_clear_object (&plugin->priv->window);
401 
402     G_OBJECT_CLASS (xed_docinfo_plugin_parent_class)->dispose (object);
403 }
404 
405 static void
xed_docinfo_plugin_finalize(GObject * object)406 xed_docinfo_plugin_finalize (GObject *object)
407 {
408     xed_debug_message (DEBUG_PLUGINS, "XedDocInfoPlugin finalizing");
409 
410     G_OBJECT_CLASS (xed_docinfo_plugin_parent_class)->finalize (object);
411 }
412 
413 static void
xed_docinfo_plugin_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)414 xed_docinfo_plugin_set_property (GObject      *object,
415                                  guint         prop_id,
416                                  const GValue *value,
417                                  GParamSpec   *pspec)
418 {
419     XedDocInfoPlugin *plugin = XED_DOCINFO_PLUGIN (object);
420 
421     switch (prop_id)
422     {
423         case PROP_WINDOW:
424             plugin->priv->window = XED_WINDOW (g_value_dup_object (value));
425             break;
426         default:
427             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
428             break;
429     }
430 }
431 
432 static void
xed_docinfo_plugin_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)433 xed_docinfo_plugin_get_property (GObject    *object,
434                                  guint       prop_id,
435                                  GValue     *value,
436                                  GParamSpec *pspec)
437 {
438     XedDocInfoPlugin *plugin = XED_DOCINFO_PLUGIN (object);
439 
440     switch (prop_id)
441    {
442         case PROP_WINDOW:
443             g_value_set_object (value, plugin->priv->window);
444             break;
445         default:
446             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
447             break;
448    }
449 }
450 
451 static void
update_ui(XedDocInfoPlugin * plugin)452 update_ui (XedDocInfoPlugin *plugin)
453 {
454     XedDocInfoPluginPrivate *priv;
455     XedView *view;
456 
457     xed_debug (DEBUG_PLUGINS);
458 
459     priv = plugin->priv;
460 
461     view = xed_window_get_active_view (priv->window);
462 
463     gtk_action_group_set_sensitive (priv->action_group, (view != NULL));
464 
465     if (priv->dialog != NULL)
466     {
467         gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog), GTK_RESPONSE_OK, (view != NULL));
468     }
469 }
470 
471 static void
xed_docinfo_plugin_activate(XedWindowActivatable * activatable)472 xed_docinfo_plugin_activate (XedWindowActivatable *activatable)
473 {
474     XedDocInfoPluginPrivate *priv;
475     GtkUIManager *manager;
476 
477     xed_debug (DEBUG_PLUGINS);
478 
479     priv = XED_DOCINFO_PLUGIN (activatable)->priv;
480     manager = xed_window_get_ui_manager (priv->window);
481 
482     priv->action_group = gtk_action_group_new ("XedDocinfoPluginActions");
483     gtk_action_group_set_translation_domain (priv->action_group, GETTEXT_PACKAGE);
484     gtk_action_group_add_actions (priv->action_group, action_entries, G_N_ELEMENTS (action_entries), activatable);
485 
486     gtk_ui_manager_insert_action_group (manager, priv->action_group, -1);
487 
488     priv->ui_id = gtk_ui_manager_new_merge_id (manager);
489 
490     gtk_ui_manager_add_ui (manager,
491                            priv->ui_id,
492                            MENU_PATH,
493                            "DocumentStatistics",
494                            "DocumentStatistics",
495                            GTK_UI_MANAGER_MENUITEM,
496                            FALSE);
497 
498     update_ui (XED_DOCINFO_PLUGIN (activatable));
499 }
500 
501 static void
xed_docinfo_plugin_deactivate(XedWindowActivatable * activatable)502 xed_docinfo_plugin_deactivate (XedWindowActivatable *activatable)
503 {
504     XedDocInfoPluginPrivate *priv;
505     GtkUIManager *manager;
506 
507     xed_debug (DEBUG_PLUGINS);
508 
509     priv = XED_DOCINFO_PLUGIN (activatable)->priv;
510     manager = xed_window_get_ui_manager (priv->window);
511 
512     gtk_ui_manager_remove_ui (manager, priv->ui_id);
513     gtk_ui_manager_remove_action_group (manager, priv->action_group);
514 }
515 
516 static void
xed_docinfo_plugin_update_state(XedWindowActivatable * activatable)517 xed_docinfo_plugin_update_state (XedWindowActivatable *activatable)
518 {
519     xed_debug (DEBUG_PLUGINS);
520 
521     update_ui (XED_DOCINFO_PLUGIN (activatable));
522 }
523 
524 static void
xed_docinfo_plugin_class_init(XedDocInfoPluginClass * klass)525 xed_docinfo_plugin_class_init (XedDocInfoPluginClass *klass)
526 {
527     GObjectClass *object_class = G_OBJECT_CLASS (klass);
528 
529     object_class->dispose = xed_docinfo_plugin_dispose;
530     object_class->finalize = xed_docinfo_plugin_finalize;
531     object_class->set_property = xed_docinfo_plugin_set_property;
532     object_class->get_property = xed_docinfo_plugin_get_property;
533 
534     g_object_class_override_property (object_class, PROP_WINDOW, "window");
535 }
536 
537 static void
xed_window_activatable_iface_init(XedWindowActivatableInterface * iface)538 xed_window_activatable_iface_init (XedWindowActivatableInterface *iface)
539 {
540     iface->activate = xed_docinfo_plugin_activate;
541     iface->deactivate = xed_docinfo_plugin_deactivate;
542     iface->update_state = xed_docinfo_plugin_update_state;
543 }
544 
545 static void
xed_docinfo_plugin_class_finalize(XedDocInfoPluginClass * klass)546 xed_docinfo_plugin_class_finalize (XedDocInfoPluginClass *klass)
547 {
548 }
549 
550 G_MODULE_EXPORT void
peas_register_types(PeasObjectModule * module)551 peas_register_types (PeasObjectModule *module)
552 {
553     xed_docinfo_plugin_register_type (G_TYPE_MODULE (module));
554 
555     peas_object_module_register_extension_type (module,
556                                                 XED_TYPE_WINDOW_ACTIVATABLE,
557                                                 XED_TYPE_DOCINFO_PLUGIN);
558 }
559