1 /*
2  * Copyright (C) 2007  Ignacio Casal Quinteiro <nacho.resa@gmail.com>
3  *                     Fatih Demir <kabalak@kabalak.net>
4  *		       Ross Golder <ross@golder.org>
5  *		       Gediminas Paulauskas <menesis@kabalak.net>
6  *               2008  Pablo Sanxiao <psanxiao@gmail.com>
7  *                     Igalia
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  *
22  * Authors:
23  *   Pablo Sanxiao <psanxiao@gmail.com>
24  *   Ignacio Casal Quinteiro <nacho.resa@gmail.com>
25  *   Fatih Demir <kabalak@kabalak.net>
26  *   Ross Golder <ross@golder.org>
27  *   Gediminas Paulauskas <menesis@kabalak.net>
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #include "gtr-application.h"
35 #include "gtr-debug.h"
36 #include "gtr-file-dialogs.h"
37 #include "gtr-po.h"
38 #include "gtr-msg.h"
39 #include "gtr-enum-types.h"
40 #include "gtr-profile.h"
41 #include "gtr-utils.h"
42 #include "gtr-message-container.h"
43 
44 #include <string.h>
45 #include <errno.h>
46 
47 #include <glib.h>
48 #include <glib-object.h>
49 #include <glib/gi18n.h>
50 #include <gtk/gtk.h>
51 #include <gettext-po.h>
52 #include <gio/gio.h>
53 
54 static void gtr_po_message_container_init (GtrMessageContainerInterface *iface);
55 
56 typedef struct
57 {
58   /* The location of the file to open */
59   GFile *location;
60 
61   /* Gettext's file handle */
62   po_file_t gettext_po_file;
63 
64   /* Message iter */
65   po_message_iterator_t iter;
66 
67   /* The message domains in this file */
68   GList *domains;
69 
70   /* Parsed list of GtrMsgs for the current domains' messagelist */
71   GList *messages;
72 
73   /* A pointer to the currently displayed message */
74   GList *current;
75 
76   /* The obsolete messages are stored within this gchar. */
77   gchar *obsolete;
78 
79   /* Is the file write-permitted? (read-only) */
80   gboolean no_write_perms;
81 
82   /* Translated entries count */
83   guint translated;
84 
85   /* Fuzzy entries count */
86   guint fuzzy;
87 
88   /* Autosave timeout timer */
89   guint autosave_timeout;
90 
91   /* Header object */
92   GtrHeader *header;
93 
94   GtrPoState state;
95 
96   /* Damned Lies(DL) teams are stored here */
97   gchar *dl_team;
98 
99   /* DL modules */
100   gchar *dl_module;
101 
102   /*  DL branches */
103   gchar *dl_branch;
104 
105   /*  DL domains */
106   gchar *dl_domain;
107 
108   /* The state of a DL module */
109   gchar *dl_state;
110 
111   /* Marks if the file was changed;  */
112   guint file_changed : 1;
113 } GtrPoPrivate;
114 
115 
116 G_DEFINE_TYPE_WITH_CODE (GtrPo, gtr_po, G_TYPE_OBJECT,
117                          G_ADD_PRIVATE (GtrPo)
118                          G_IMPLEMENT_INTERFACE (GTR_TYPE_MESSAGE_CONTAINER,
119                                                 gtr_po_message_container_init))
120 
121 enum
122 {
123   PROP_0,
124   PROP_LOCATION,
125   PROP_STATE
126 };
127 
128 static gchar *message_error = NULL;
129 
130 static void
gtr_header_set_field(GtrHeader * header,const gchar * field,const gchar * data)131 gtr_header_set_field (GtrHeader * header,
132                       const gchar * field, const gchar * data)
133 {
134   gchar *msgstr;
135 
136   g_return_if_fail (GTR_IS_HEADER (header));
137   g_return_if_fail (data != NULL);
138 
139   msgstr = po_header_set_field (gtr_msg_get_msgstr (GTR_MSG (header)),
140                                 field, data);
141   gtr_msg_set_msgstr (GTR_MSG (header), msgstr);
142 
143   g_free (msgstr);
144 }
145 
146 static void
gtr_po_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)147 gtr_po_set_property (GObject      *object,
148                      guint         prop_id,
149                      const GValue *value,
150                      GParamSpec   *pspec)
151 {
152   GtrPo *po = GTR_PO (object);
153 
154   switch (prop_id)
155     {
156     case PROP_LOCATION:
157       gtr_po_set_location (po, G_FILE (g_value_get_object (value)));
158       break;
159     default:
160       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
161       break;
162     }
163 }
164 
165 static void
gtr_po_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)166 gtr_po_get_property (GObject * object,
167                      guint prop_id, GValue * value, GParamSpec * pspec)
168 {
169   GtrPo *po = GTR_PO (object);
170 
171   switch (prop_id)
172     {
173     case PROP_LOCATION:
174       g_value_take_object (value, gtr_po_get_location (po));
175       break;
176     case PROP_STATE:
177       g_value_set_enum (value, gtr_po_get_state (po));
178       break;
179     default:
180       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
181       break;
182     }
183 }
184 
185 /*
186  * A helper function simply increments the "translated" variable of the
187  *  po-file.
188  */
189 static void
determine_translation_status(GtrMsg * msg,GtrPo * po)190 determine_translation_status (GtrMsg * msg, GtrPo * po)
191 {
192   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
193   if (gtr_msg_is_fuzzy (msg))
194     priv->fuzzy++;
195   else if (gtr_msg_is_translated (msg))
196     priv->translated++;
197 }
198 
199 /*
200  * Update the count of the completed translated entries.
201  */
202 static void
gtr_po_update_translated_count(GtrPo * po)203 gtr_po_update_translated_count (GtrPo * po)
204 {
205   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
206   priv->translated = 0;
207   priv->fuzzy = 0;
208   g_list_foreach (priv->messages,
209                   (GFunc) determine_translation_status, po);
210 }
211 
212 static void
gtr_po_init(GtrPo * po)213 gtr_po_init (GtrPo * po)
214 {
215   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
216 
217   priv->location = NULL;
218   priv->gettext_po_file = NULL;
219   priv->dl_team = NULL;
220   priv->dl_module = NULL;
221   priv->dl_branch = NULL;
222   priv->dl_domain = NULL;
223   priv->dl_state = NULL;
224 }
225 
226 static void
gtr_po_finalize(GObject * object)227 gtr_po_finalize (GObject * object)
228 {
229   GtrPo *po = GTR_PO (object);
230   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
231 
232   g_list_free_full (priv->messages, g_object_unref);
233   g_list_free_full (priv->domains, g_free);
234   g_free (priv->obsolete);
235 
236   if (priv->gettext_po_file)
237     po_file_free (priv->gettext_po_file);
238 
239   if (priv->dl_team)
240     g_free (priv->dl_team);
241   if (priv->dl_module)
242     g_free (priv->dl_module);
243   if (priv->dl_branch)
244     g_free (priv->dl_branch);
245   if (priv->dl_domain)
246     g_free (priv->dl_domain);
247   if (priv->dl_state)
248     g_free (priv->dl_state);
249 
250   G_OBJECT_CLASS (gtr_po_parent_class)->finalize (object);
251 }
252 
253 static void
gtr_po_dispose(GObject * object)254 gtr_po_dispose (GObject * object)
255 {
256   GtrPo *po = GTR_PO (object);
257   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
258 
259   g_clear_object (&priv->location);
260 
261   G_OBJECT_CLASS (gtr_po_parent_class)->dispose (object);
262 }
263 
264 static GtrMsg *
gtr_po_message_container_get_message(GtrMessageContainer * container,gint number)265 gtr_po_message_container_get_message (GtrMessageContainer *container,
266                                       gint number)
267 {
268   GtrPo *po = GTR_PO (container);
269   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
270 
271   return g_list_nth_data (priv->messages, number);
272 }
273 
274 static gint
gtr_po_message_container_get_message_number(GtrMessageContainer * container,GtrMsg * msg)275 gtr_po_message_container_get_message_number (GtrMessageContainer * container,
276                                              GtrMsg * msg)
277 {
278   GtrPo *po = GTR_PO (container);
279   GList *list;
280   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
281 
282   list = g_list_find (priv->messages, msg);
283   return g_list_position (priv->messages, list);
284 }
285 
286 static gint
gtr_po_message_container_get_count(GtrMessageContainer * container)287 gtr_po_message_container_get_count (GtrMessageContainer * container)
288 {
289   GtrPo *po = GTR_PO (container);
290   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
291 
292   return g_list_length (priv->messages);
293 }
294 
295 static void
gtr_po_message_container_init(GtrMessageContainerInterface * iface)296 gtr_po_message_container_init (GtrMessageContainerInterface * iface)
297 {
298   iface->get_message = gtr_po_message_container_get_message;
299   iface->get_message_number = gtr_po_message_container_get_message_number;
300   iface->get_count = gtr_po_message_container_get_count;
301 }
302 
303 static void
gtr_po_class_init(GtrPoClass * klass)304 gtr_po_class_init (GtrPoClass * klass)
305 {
306   GObjectClass *object_class = G_OBJECT_CLASS (klass);
307 
308   object_class->finalize = gtr_po_finalize;
309   object_class->dispose = gtr_po_dispose;
310   object_class->get_property = gtr_po_get_property;
311   object_class->set_property = gtr_po_set_property;
312 
313   g_object_class_install_property (object_class,
314                                    PROP_LOCATION,
315                                    g_param_spec_object ("location",
316                                                         "Location",
317                                                         "The po's location",
318                                                         G_TYPE_FILE,
319                                                         G_PARAM_READWRITE));
320 
321   g_object_class_install_property (object_class,
322                                    PROP_STATE,
323                                    g_param_spec_enum ("state",
324                                                       "State",
325                                                       "The po's state",
326                                                       GTR_TYPE_PO_STATE,
327                                                       GTR_PO_STATE_SAVED,
328                                                       G_PARAM_READABLE));
329 }
330 
331 /*
332  * Functions for errors handling.
333  */
334 GQuark
gtr_po_error_quark(void)335 gtr_po_error_quark (void)
336 {
337   static GQuark quark = 0;
338   if (!quark)
339     quark = g_quark_from_static_string ("gtr_po_parser_error");
340   return quark;
341 }
342 
343 static void
on_gettext_po_xerror(gint severity,po_message_t message,const gchar * filename,size_t lineno,size_t column,gint multiline_p,const gchar * message_text)344 on_gettext_po_xerror (gint severity,
345                       po_message_t message,
346                       const gchar * filename, size_t lineno, size_t column,
347                       gint multiline_p, const gchar * message_text)
348 {
349   message_error = g_strdup (message_text);
350 }
351 
352 static void
on_gettext_po_xerror2(gint severity,po_message_t message1,const gchar * filename1,size_t lineno1,size_t column1,gint multiline_p1,const gchar * message_text1,po_message_t message2,const gchar * filename2,size_t lineno2,size_t column2,gint multiline_p2,const gchar * message_text2)353 on_gettext_po_xerror2 (gint severity,
354                        po_message_t message1,
355                        const gchar * filename1, size_t lineno1,
356                        size_t column1, gint multiline_p1,
357                        const gchar * message_text1, po_message_t message2,
358                        const gchar * filename2, size_t lineno2,
359                        size_t column2, gint multiline_p2,
360                        const gchar * message_text2)
361 {
362   message_error = g_strdup_printf ("%s.\n %s", message_text1, message_text2);
363 }
364 
365 static gboolean
po_file_is_empty(po_file_t file)366 po_file_is_empty (po_file_t file)
367 {
368   const gchar *const *domains = po_file_domains (file);
369 
370   for (; *domains != NULL; domains++)
371     {
372       po_message_iterator_t iter = po_message_iterator (file, *domains);
373       if (po_next_message (iter) != NULL)
374         {
375           po_message_iterator_free (iter);
376           return FALSE;
377         }
378       po_message_iterator_free (iter);
379     }
380   return TRUE;
381 }
382 
383 /**
384  * is_read_only:
385  * @location: a GFile Object that represents the file to check
386  *
387  * This method is copied from gedit, file gedit-commands-file.c
388  *
389  * Returns: False if file is writeable. True if file doesn't exists, is read-only or read-only attribute can't be check
390  */
391 static gboolean
is_read_only(const gchar * filename)392 is_read_only (const gchar * filename)
393 {
394   gboolean ret = TRUE;          /* default to read only */
395   GFileInfo *info;
396   GFile *location;
397 
398   location = g_file_new_for_path (filename);
399 
400   if (!g_file_query_exists (location, NULL))
401     return FALSE;
402 
403   info = g_file_query_info (location,
404                             G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
405                             G_FILE_QUERY_INFO_NONE, NULL, NULL);
406   g_object_unref (location);
407 
408   if (info != NULL)
409     {
410       if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
411         {
412           ret = !g_file_info_get_attribute_boolean (info,
413                                                     G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
414         }
415 
416       g_object_unref (info);
417     }
418 
419   return ret;
420 }
421 
422 /***************************** Public funcs ***********************************/
423 
424 /**
425  * gtr_po_new:
426  *
427  * Creates a new #GtrPo.
428  *
429  * Returns: a new #GtrPo object
430  **/
431 GtrPo *
gtr_po_new(void)432 gtr_po_new (void)
433 {
434   GtrPo *po;
435 
436   po = g_object_new (GTR_TYPE_PO, NULL);
437 
438   return po;
439 }
440 
441 static gboolean
_gtr_po_load(GtrPo * po,GFile * location,GError ** error)442 _gtr_po_load (GtrPo * po, GFile * location, GError ** error)
443 {
444   struct po_xerror_handler handler;
445   po_message_iterator_t iter;
446   po_message_t message;
447   const gchar *msgid;
448   gchar *filename;
449   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
450 
451   /*
452    * Initialize the handler error.
453    */
454   handler.xerror = &on_gettext_po_xerror;
455   handler.xerror2 = &on_gettext_po_xerror2;
456 
457   if (message_error != NULL)
458     {
459       g_free (message_error);
460       message_error = NULL;
461     }
462 
463   filename = g_file_get_path (location);
464 
465   if (priv->gettext_po_file)
466     po_file_free (priv->gettext_po_file);
467 
468   if (priv->header)
469     {
470       g_object_unref (priv->header);
471       priv->header = NULL;
472     }
473 
474   if (priv->iter)
475     {
476       po_message_iterator_free (priv->iter);
477       priv->iter = NULL;
478     }
479 
480   priv->gettext_po_file = po_file_read (filename, &handler);
481   g_free (filename);
482 
483   if (po_file_is_empty (priv->gettext_po_file))
484     {
485       g_set_error (error,
486                    GTR_PO_ERROR,
487                    GTR_PO_ERROR_FILE_EMPTY, _("The file is empty"));
488       return FALSE;
489     }
490 
491   if (!priv->gettext_po_file)
492     {
493       g_set_error (error,
494                    GTR_PO_ERROR,
495                    GTR_PO_ERROR_FILENAME,
496                    _("Failed opening file “%s”: %s"),
497                    filename, g_strerror (errno));
498       g_free (filename);
499       return FALSE;
500     }
501 
502   iter = po_message_iterator (priv->gettext_po_file, NULL);
503   message = po_next_message (iter);
504   msgid = po_message_msgid (message);
505 
506   if (*msgid == '\0')
507     priv->header = gtr_header_new (iter, message);
508   else
509     {
510       po_message_iterator_free (iter);
511       iter = po_message_iterator (priv->gettext_po_file, NULL);
512 
513       message = po_message_create ();
514       po_message_set_msgid (message, "");
515       po_message_set_msgstr (message, "");
516       po_message_insert (iter, message);
517 
518       priv->header = gtr_header_new (iter, message);
519     }
520 
521   priv->iter = iter;
522 
523   return TRUE;
524 }
525 
526 static gboolean
_gtr_po_load_ensure_utf8(GtrPo * po,GError ** error)527 _gtr_po_load_ensure_utf8 (GtrPo * po, GError ** error)
528 {
529   GMappedFile *mapped;
530   const gchar *content;
531   gboolean utf8_valid;
532   gchar *filename;
533   gsize size;
534   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
535 
536   filename = g_file_get_path (priv->location);
537   mapped = g_mapped_file_new (filename, FALSE, error);
538   g_free (filename);
539 
540   if (!mapped)
541     return FALSE;
542 
543   content = g_mapped_file_get_contents (mapped);
544   size = g_mapped_file_get_length (mapped);
545 
546   utf8_valid = g_utf8_validate (content, size, NULL);
547 
548   if (!_gtr_po_load (po, priv->location, error))
549     {
550       g_mapped_file_unref (mapped);
551       return FALSE;
552     }
553 
554   if (!utf8_valid &&
555       priv->header)
556     {
557       gchar *charset = NULL;
558 
559       if (priv->header)
560         charset = gtr_header_get_charset (priv->header);
561 
562       if (charset && *charset && strcmp (charset, "UTF-8") != 0)
563         {
564           GOutputStream *converter_stream, *stream;
565           GCharsetConverter *converter;
566           GIOStream *iostream;
567           GFile *tmp;
568 
569           /* Store UTF-8 converted file in $TMP */
570           converter = g_charset_converter_new ("UTF-8", charset, NULL);
571 
572           if (!converter)
573             {
574               g_set_error (error,
575                            GTR_PO_ERROR,
576                            GTR_PO_ERROR_ENCODING,
577                            _("Could not convert from charset “%s” to UTF-8"),
578                            charset);
579               g_mapped_file_unref (mapped);
580               g_free (charset);
581               return FALSE;
582             }
583 
584           g_free (charset);
585           tmp = g_file_new_tmp ("gtranslator-XXXXXX.po",
586                                 (GFileIOStream **) &iostream,
587                                 NULL);
588 
589           if (!tmp)
590             {
591               g_set_error (error,
592                            GTR_PO_ERROR,
593                            GTR_PO_ERROR_ENCODING,
594                            _("Could not store temporary "
595                              "file for encoding conversion"));
596               g_mapped_file_unref (mapped);
597               g_object_unref (converter);
598               return FALSE;
599             }
600 
601           stream = g_io_stream_get_output_stream (iostream);
602           converter_stream =
603             g_converter_output_stream_new (stream,
604                                            G_CONVERTER (converter));
605 
606 
607           if (!g_output_stream_write_all (converter_stream,
608                                           content, size, NULL,
609                                           NULL, NULL))
610             {
611               g_set_error (error,
612                            GTR_PO_ERROR,
613                            GTR_PO_ERROR_ENCODING,
614                            _("Could not store temporary "
615                              "file for encoding conversion"));
616               g_object_unref (converter_stream);
617               g_object_unref (iostream);
618               g_object_unref (converter);
619               g_mapped_file_unref (mapped);
620               return FALSE;
621             }
622 
623           g_object_unref (converter_stream);
624           g_object_unref (iostream);
625           g_object_unref (converter);
626 
627           /* Now load again the converted file */
628           if (!_gtr_po_load (po, tmp, error))
629             {
630               g_mapped_file_unref (mapped);
631               return FALSE;
632             }
633 
634           /* Ensure Content-Type is set correctly
635            * in the header as per the content
636            */
637           if (priv->header)
638             gtr_header_set_charset (priv->header, "UTF-8");
639 
640           utf8_valid = TRUE;
641         }
642     }
643 
644   g_mapped_file_unref (mapped);
645 
646   if (!utf8_valid)
647     {
648       g_set_error (error,
649                    GTR_PO_ERROR,
650                    GTR_PO_ERROR_ENCODING,
651                    _("All attempt to convert the file to UTF-8 has failed, "
652                      "use the msgconv or iconv command line tools before "
653                      "opening this file with GNOME Translation Editor"));
654       return FALSE;
655     }
656 
657   return TRUE;
658 }
659 
660 /**
661  * gtr_po_parse:
662  * @po: a #GtrPo
663  * @location: the file to open
664  * @error: a variable to store the errors
665  *
666  * Parses all things related to the #GtrPo and initilizes all neccessary
667  * variables.
668  **/
669 gboolean
gtr_po_parse(GtrPo * po,GFile * location,GError ** error)670 gtr_po_parse (GtrPo * po, GFile * location, GError ** error)
671 {
672   GtrMsg *msg;
673   po_message_t message;
674   po_message_iterator_t iter;
675   const gchar *const *domains;
676   gint i = 0;
677   gint pos = 1;
678   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
679 
680   g_return_val_if_fail (GTR_IS_PO (po), FALSE);
681   g_return_val_if_fail (location != NULL, FALSE);
682 
683   if (message_error != NULL)
684     {
685       g_free (message_error);
686       message_error = NULL;
687     }
688 
689   /*
690    * Get filename path.
691    */
692   priv->location = g_file_dup (location);
693 
694   if (!_gtr_po_load_ensure_utf8 (po, error))
695     {
696       g_object_unref (po);
697       return FALSE;
698     }
699 
700   /*
701    * No need to return; this can be corrected by the user
702    */
703   if (message_error != NULL)
704     {
705       g_set_error (error,
706                    GTR_PO_ERROR, GTR_PO_ERROR_RECOVERY, "%s", message_error);
707     }
708 
709   /*
710    * Determine the message domains to track
711    */
712   if (!(domains = po_file_domains (priv->gettext_po_file)))
713     {
714       if (*error != NULL)
715         g_clear_error (error);
716       g_set_error (error,
717                    GTR_PO_ERROR,
718                    GTR_PO_ERROR_GETTEXT,
719                    _("Gettext returned a null message domain list."));
720       g_object_unref (po);
721       return FALSE;
722     }
723   while (domains[i])
724     {
725       priv->domains = g_list_append (priv->domains, g_strdup (domains[i]));
726       i++;
727     }
728 
729   /*
730    * Determine whether first message is the header or not, and
731    * if so, process it seperately. Otherwise, treat as a normal
732    * message.
733    */
734   priv->messages = NULL;
735   iter = priv->iter;
736 
737   /* Post-process these into a linked list of GtrMsgs. */
738   while ((message = po_next_message (iter)))
739     {
740       /*FIXME: We have to change this:
741        * we have to add a gtr_msg_is_obsolete fund msg.c
742        * and detect if we want obsoletes messages in show message
743        */
744       if (!po_message_is_obsolete (message))
745         {
746           /* Unpack into a GtrMsg */
747           msg = _gtr_msg_new (iter, message);
748 
749           /* Set position in PO file */
750           gtr_msg_set_po_position (msg, pos++);
751 
752           /* Build up messages */
753           priv->messages = g_list_prepend (priv->messages, msg);
754         }
755     }
756 
757   if (priv->messages == NULL)
758     {
759       if (*error != NULL)
760         g_clear_error (error);
761       g_set_error (error,
762                    GTR_PO_ERROR,
763                    GTR_PO_ERROR_OTHER,
764                    _("No messages obtained from parser."));
765       g_object_unref (po);
766       return FALSE;
767     }
768 
769   priv->messages = g_list_reverse (priv->messages);
770 
771   /*
772    * Set the current message to the first message.
773    */
774   priv->current = g_list_first (priv->messages);
775 
776   gtr_po_update_translated_count (po);
777 
778   /* Initialize Tab state */
779   priv->state = GTR_PO_STATE_SAVED;
780   return TRUE;
781 }
782 
783 /**
784  * gtr_po_save_file:
785  * @po: a #GtrPo
786  * @error: a GError to manage the exceptions
787  *
788  * It saves the po file and if there are any problem it stores the error
789  * in @error.
790  **/
791 void
gtr_po_save_file(GtrPo * po,GError ** error)792 gtr_po_save_file (GtrPo * po, GError ** error)
793 {
794   struct po_xerror_handler handler;
795   gchar *filename;
796   gchar *msg_error;
797   GtrHeader *header;
798   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
799 
800   /*
801    * Initialice the handler error.
802    */
803   handler.xerror = &on_gettext_po_xerror;
804   handler.xerror2 = &on_gettext_po_xerror2;
805 
806   filename = g_file_get_path (priv->location);
807 
808   if (g_str_has_suffix (filename, ".pot"))
809     {
810       // Remove suffix
811       filename[strlen (filename) - 4] = '\0';
812       g_set_error (error,
813                    GTR_PO_ERROR,
814                    GTR_PO_ERROR_FILENAME,
815                    _("You are saving a file with a .pot extension.\n"
816                      "Pot files are generated by the compilation process.\n"
817                      "Your file should likely be named “%s.po”."), filename);
818       g_free (filename);
819       return;
820     }
821 
822 
823   if (is_read_only (filename))
824     {
825       g_set_error (error,
826                    GTR_PO_ERROR,
827                    GTR_PO_ERROR_READONLY,
828                    _("The file %s is read-only, and can not be overwritten"),
829                    filename);
830       g_free (filename);
831       return;
832     }
833 
834   /* Save header fields into msg */
835   header = gtr_po_get_header (po);
836   gtr_header_update_header (header);
837 
838   /*
839    * Check if the file is right
840    */
841   msg_error = gtr_po_check_po_file (po);
842   if (msg_error != NULL)
843     {
844       g_set_error (error,
845                    GTR_PO_ERROR,
846                    GTR_PO_ERROR_GETTEXT,
847                    _("There is an error in the PO file: %s"),
848                    msg_error);
849       g_free (msg_error);
850       g_free (filename);
851       return;
852     }
853 
854   if (!po_file_write (gtr_po_get_po_file (po), filename, &handler))
855     {
856       g_set_error (error,
857                    GTR_PO_ERROR,
858                    GTR_PO_ERROR_FILENAME,
859                    _("There was an error writing the PO file: %s"),
860                    message_error);
861       g_free (message_error);
862       g_free (filename);
863       return;
864     }
865   g_free (filename);
866 
867   /* If we are here everything is ok and we can set the state as saved */
868   gtr_po_set_state (po, GTR_PO_STATE_SAVED);
869 
870   /*
871    * If the warn if fuzzy option is enabled we have to show an error
872    */
873   /*if (gtr_prefs_manager_get_warn_if_fuzzy () && priv->fuzzy)
874      {
875      g_set_error (error,
876      GTR_PO_ERROR,
877      GTR_PO_ERROR_OTHER,
878      ngettext ("File %s\ncontains %d fuzzy message",
879      "File %s\ncontains %d fuzzy messages",
880      priv->fuzzy),
881      priv->fuzzy);
882      } */
883 }
884 
885 /**
886  * gtr_po_get_location:
887  * @po: a #GtrPo
888  *
889  * Gets the GFile of the po file.
890  *
891  * Returns: (transfer full): the GFile associated with the @po. The returned
892  *          location must be freed with g_object_unref.
893  **/
894 GFile *
gtr_po_get_location(GtrPo * po)895 gtr_po_get_location (GtrPo * po)
896 {
897   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
898   g_return_val_if_fail (GTR_IS_PO (po), NULL);
899 
900   return g_file_dup (priv->location);
901 }
902 
903 /**
904  * gtr_po_set_location:
905  * @po: a #GtrPo
906  * @location: The GFile to set to the #GtrPo
907  *
908  * Sets the GFile location within the #GtrPo object.
909  **/
910 void
gtr_po_set_location(GtrPo * po,GFile * location)911 gtr_po_set_location (GtrPo * po, GFile * location)
912 {
913   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
914   g_return_if_fail (GTR_IS_PO (po));
915 
916   if (priv->location)
917     {
918       if (g_file_equal (priv->location, location))
919         return;
920       g_object_unref (priv->location);
921     }
922 
923   priv->location = g_file_dup (location);
924 
925   g_object_notify (G_OBJECT (po), "location");
926 }
927 
928 /**
929  * gtr_po_get_state:
930  * @po: a #GtrPo
931  *
932  * Return value: the #GtrPoState value of the @po.
933  */
934 GtrPoState
gtr_po_get_state(GtrPo * po)935 gtr_po_get_state (GtrPo * po)
936 {
937   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
938   g_return_val_if_fail (GTR_IS_PO (po), 0);
939 
940   return priv->state;
941 }
942 
943 /**
944  * gtr_po_set_state:
945  * @po: a #GtrPo
946  * @state: a #GtrPoState
947  *
948  * Sets the state for a #GtrPo
949  */
950 void
gtr_po_set_state(GtrPo * po,GtrPoState state)951 gtr_po_set_state (GtrPo * po, GtrPoState state)
952 {
953   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
954   g_return_if_fail (GTR_IS_PO (po));
955 
956   priv->state = state;
957 
958   g_object_notify (G_OBJECT (po), "state");
959 }
960 
gtr_po_set_dl_info(GtrPo * po,gchar * team,gchar * module_name,gchar * branch,gchar * domain,gchar * module_state)961 void gtr_po_set_dl_info (GtrPo * po, gchar * team, gchar * module_name,
962                          gchar * branch, gchar * domain, gchar * module_state)
963 {
964   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
965   priv->dl_team = g_strdup (team);
966   priv->dl_module = g_strdup (module_name);
967   priv->dl_branch = g_strdup (branch);
968   priv->dl_domain = g_strdup (domain);
969   priv->dl_state = g_strdup (module_state);
970 
971   gtr_header_set_field (priv->header, "X-DL-Team", team);
972   gtr_header_set_field (priv->header, "X-DL-Module", module_name);
973   gtr_header_set_field (priv->header, "X-DL-Branch", branch);
974   gtr_header_set_field (priv->header, "X-DL-Domain", domain);
975   gtr_header_set_field (priv->header, "X-DL-State", module_state);
976 }
977 /*
978  * FIXME: We are not using this func.
979  */
980 gboolean
gtr_po_get_write_perms(GtrPo * po)981 gtr_po_get_write_perms (GtrPo * po)
982 {
983   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
984   return priv->no_write_perms;
985 }
986 
987 /**
988  * gtr_po_get_messages:
989  * @po: a #GtrPo
990  *
991  * Return value: (transfer container) (element-type Gtranslator.Msg):
992  *               a pointer to the messages list
993  **/
994 GList *
gtr_po_get_messages(GtrPo * po)995 gtr_po_get_messages (GtrPo * po)
996 {
997   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
998   g_return_val_if_fail (GTR_IS_PO (po), NULL);
999 
1000   return priv->messages;
1001 }
1002 
1003 /* FIXME: this is hack, we should fix it */
1004 /**
1005  * gtr_po_set_messages:
1006  * @po: a #GtrPo
1007  * @messages: (element-type Gtranslator.Msg): a pointer to a new messages list.
1008  *
1009  * Sets an updated list of messages.
1010  **/
1011 void
gtr_po_set_messages(GtrPo * po,GList * messages)1012 gtr_po_set_messages (GtrPo * po, GList * messages)
1013 {
1014   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1015   g_return_if_fail (GTR_IS_PO (po));
1016 
1017   priv->messages = messages;
1018 }
1019 
1020 /**
1021  * gtr_po_get_current_message:
1022  * @po: a #GtrPo
1023  *
1024  * Return value: (transfer none) (element-type Gtranslator.Msg):
1025  *               a pointer to the current message
1026  **/
1027 GList *
gtr_po_get_current_message(GtrPo * po)1028 gtr_po_get_current_message (GtrPo * po)
1029 {
1030   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1031   return priv->current;
1032 }
1033 
1034 /**
1035  * gtr_po_update_current_message:
1036  * @po: a #GtrPo
1037  * @msg: the message where should point the current message.
1038  *
1039  * Sets the new current message to the message that is passed in
1040  * the argument.
1041  **/
1042 void
gtr_po_update_current_message(GtrPo * po,GtrMsg * msg)1043 gtr_po_update_current_message (GtrPo * po, GtrMsg * msg)
1044 {
1045   gint i;
1046   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1047   i = g_list_index (priv->messages, msg);
1048   priv->current = g_list_nth (priv->messages, i);
1049 }
1050 
1051 /**
1052  * gtr_po_get_domains:
1053  * @po: a #GtrPo
1054  *
1055  * Return value: (transfer none) (element-type utf8):
1056  *               a pointer to the domains list
1057  **/
1058 GList *
gtr_po_get_domains(GtrPo * po)1059 gtr_po_get_domains (GtrPo * po)
1060 {
1061   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1062   return priv->domains;
1063 }
1064 
1065 /**
1066  * gtr_po_get_po_file: (skip)
1067  * @po: a #GtrPo
1068  *
1069  * Gets the gettext file.
1070  *
1071  * Return value: the gettext file
1072  **/
1073 po_file_t
gtr_po_get_po_file(GtrPo * po)1074 gtr_po_get_po_file (GtrPo * po)
1075 {
1076   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1077   return priv->gettext_po_file;
1078 }
1079 
1080 /**
1081  * gtr_po_get_next_fuzzy:
1082  * @po: a #GtrPo
1083  *
1084  * Return value: (transfer none) (element-type Gtranslator.Msg):
1085  *               a pointer to the next fuzzy message
1086  **/
1087 GList *
gtr_po_get_next_fuzzy(GtrPo * po)1088 gtr_po_get_next_fuzzy (GtrPo * po)
1089 {
1090   GList *msg;
1091   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1092 
1093   msg = priv->current;
1094   while ((msg = g_list_next (msg)))
1095     {
1096       if (gtr_msg_is_fuzzy (msg->data))
1097         return msg;
1098     }
1099 
1100   return NULL;
1101 }
1102 
1103 
1104 /**
1105  * gtr_po_get_prev_fuzzy:
1106  * @po: a #GtrPo
1107  *
1108  * Return value: (transfer none) (element-type Gtranslator.Msg):
1109  *               a pointer to the previously fuzzy message
1110  **/
1111 GList *
gtr_po_get_prev_fuzzy(GtrPo * po)1112 gtr_po_get_prev_fuzzy (GtrPo * po)
1113 {
1114   GList *msg;
1115   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1116 
1117   msg = priv->current;
1118   while ((msg = g_list_previous (msg)))
1119     {
1120       if (gtr_msg_is_fuzzy (msg->data))
1121         return msg;
1122     }
1123 
1124   return NULL;
1125 }
1126 
1127 
1128 /**
1129  * gtr_po_get_next_untrans:
1130  * @po: a #GtrPo
1131  *
1132  * Return value: (transfer none) (element-type Gtranslator.Msg):
1133  *               a pointer to the next untranslated message
1134  **/
1135 GList *
gtr_po_get_next_untrans(GtrPo * po)1136 gtr_po_get_next_untrans (GtrPo * po)
1137 {
1138   GList *msg;
1139   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1140 
1141   msg = priv->current;
1142   while ((msg = g_list_next (msg)))
1143     {
1144       if (!gtr_msg_is_translated (msg->data))
1145         return msg;
1146     }
1147 
1148   return NULL;
1149 }
1150 
1151 
1152 /**
1153  * gtr_po_get_prev_untrans:
1154  * @po: a #GtrPo
1155  *
1156  * Return value: (transfer none) (element-type Gtranslator.Msg):
1157  *                a pointer to the previously untranslated
1158  *                message or NULL if there are not previously untranslated
1159  *                message.
1160  **/
1161 GList *
gtr_po_get_prev_untrans(GtrPo * po)1162 gtr_po_get_prev_untrans (GtrPo * po)
1163 {
1164   GList *msg;
1165   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1166 
1167   msg = priv->current;
1168   while ((msg = g_list_previous (msg)))
1169     {
1170       if (!gtr_msg_is_translated (msg->data))
1171         return msg;
1172     }
1173 
1174   return NULL;
1175 }
1176 
1177 /**
1178  * gtr_po_get_next_fuzzy_or_untrans:
1179  * @po: a #GtrPo
1180  *
1181  * Return value: (transfer none) (element-type Gtranslator.Msg):
1182  *               a pointer to the next fuzzy or untranslated
1183  *               message or NULL if there is not next fuzzy or untranslated
1184  *               message.
1185  **/
1186 GList *
gtr_po_get_next_fuzzy_or_untrans(GtrPo * po)1187 gtr_po_get_next_fuzzy_or_untrans (GtrPo * po)
1188 {
1189   GList *msg;
1190   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1191 
1192   msg = priv->current;
1193   while ((msg = g_list_next (msg)))
1194     {
1195       if (gtr_msg_is_fuzzy (msg->data) || !gtr_msg_is_translated (msg->data))
1196         return msg;
1197     }
1198 
1199   return NULL;
1200 }
1201 
1202 /**
1203  * gtr_po_get_prev_fuzzy_or_untrans:
1204  * @po: a #GtrPo
1205  *
1206  * Return value: (transfer none) (element-type Gtranslator.Msg):
1207  *               a pointer to the previously fuzzy or
1208  *               untranslated message or NULL if there is not previously
1209  *               fuzzy or untranslated message.
1210  **/
1211 GList *
gtr_po_get_prev_fuzzy_or_untrans(GtrPo * po)1212 gtr_po_get_prev_fuzzy_or_untrans (GtrPo * po)
1213 {
1214   GList *msg;
1215   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1216 
1217   msg = priv->current;
1218   while ((msg = g_list_previous (msg)))
1219     {
1220       if (gtr_msg_is_fuzzy (msg->data) || !gtr_msg_is_translated (msg->data))
1221         return msg;
1222     }
1223 
1224   return NULL;
1225 }
1226 
1227 /**
1228  * gtr_po_get_msg_from_number:
1229  * @po: a #GtrPo
1230  * @number: the message to jump
1231  *
1232  * Gets the message at the given position.
1233  *
1234  * Returns: (transfer none) (element-type Gtranslator.Msg):
1235  *          the message at the given position.
1236  */
1237 GList *
gtr_po_get_msg_from_number(GtrPo * po,gint number)1238 gtr_po_get_msg_from_number (GtrPo * po, gint number)
1239 {
1240   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1241   g_return_val_if_fail (GTR_IS_PO (po), NULL);
1242 
1243   return g_list_nth (priv->messages, number);
1244 }
1245 
1246 /**
1247  * gtr_po_get_header:
1248  * @po: a #GtrPo
1249  *
1250  * Return value: (transfer none): The #GtrHeader of the @po.
1251  **/
1252 GtrHeader *
gtr_po_get_header(GtrPo * po)1253 gtr_po_get_header (GtrPo * po)
1254 {
1255   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1256   g_return_val_if_fail (GTR_IS_PO (po), NULL);
1257 
1258   return priv->header;
1259 }
1260 
1261 /**
1262  * gtr_po_get_translated_count:
1263  * @po: a #GtrPo
1264  *
1265  * Return value: the count of the translated messages.
1266  **/
1267 gint
gtr_po_get_translated_count(GtrPo * po)1268 gtr_po_get_translated_count (GtrPo * po)
1269 {
1270   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1271   g_return_val_if_fail (GTR_IS_PO (po), -1);
1272 
1273   return priv->translated;
1274 }
1275 
1276 /*
1277  * This func decrease or increase the count of translated
1278  * messages in 1.
1279  * This funcs must not be exported.
1280  */
1281 void
_gtr_po_increase_decrease_translated(GtrPo * po,gboolean increase)1282 _gtr_po_increase_decrease_translated (GtrPo * po, gboolean increase)
1283 {
1284   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1285   g_return_if_fail (GTR_IS_PO (po));
1286 
1287   if (increase)
1288     priv->translated++;
1289   else
1290     priv->translated--;
1291 }
1292 
1293 /**
1294  * gtr_po_get_fuzzy_count:
1295  * @po: a #GtrPo
1296  *
1297  * Return value: the count of the fuzzy messages.
1298  **/
1299 gint
gtr_po_get_fuzzy_count(GtrPo * po)1300 gtr_po_get_fuzzy_count (GtrPo * po)
1301 {
1302   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1303   g_return_val_if_fail (GTR_IS_PO (po), -1);
1304 
1305   return priv->fuzzy;
1306 }
1307 
1308 /*
1309  * This func decrease or increase the count of fuzzy
1310  * messages in 1.
1311  * This funcs must not be exported.
1312  */
1313 void
_gtr_po_increase_decrease_fuzzy(GtrPo * po,gboolean increase)1314 _gtr_po_increase_decrease_fuzzy (GtrPo * po, gboolean increase)
1315 {
1316   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1317   g_return_if_fail (GTR_IS_PO (po));
1318 
1319   if (increase)
1320     priv->fuzzy++;
1321   else
1322     priv->fuzzy--;
1323 }
1324 
1325 /**
1326  * gtr_po_get_untranslated_count:
1327  * @po: a #GtrPo
1328  *
1329  * Return value: the count of the untranslated messages.
1330  **/
1331 gint
gtr_po_get_untranslated_count(GtrPo * po)1332 gtr_po_get_untranslated_count (GtrPo * po)
1333 {
1334   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1335   g_return_val_if_fail (GTR_IS_PO (po), -1);
1336 
1337   return (g_list_length (priv->messages) - priv->translated -
1338           priv->fuzzy);
1339 }
1340 
1341 /**
1342  * gtr_po_get_messages_count:
1343  * @po: a #GtrPo
1344  *
1345  * Return value: the number of messages messages.
1346  **/
1347 gint
gtr_po_get_messages_count(GtrPo * po)1348 gtr_po_get_messages_count (GtrPo * po)
1349 {
1350   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1351   g_return_val_if_fail (GTR_IS_PO (po), -1);
1352 
1353   return g_list_length (priv->messages);
1354 }
1355 
1356 /**
1357  * gtr_po_get_message_position:
1358  * @po: a #GtrPo
1359  *
1360  * Return value: the number of the current message.
1361  **/
1362 gint
gtr_po_get_message_position(GtrPo * po)1363 gtr_po_get_message_position (GtrPo * po)
1364 {
1365   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1366   g_return_val_if_fail (GTR_IS_PO (po), -1);
1367 
1368   return gtr_msg_get_po_position (GTR_MSG (priv->current->data));
1369 }
1370 
1371 /**
1372  * gtr_po_check_po_file:
1373  * @po: a #GtrPo
1374  *
1375  * Test whether an entire PO file is valid, like msgfmt does it.
1376  * Returns: If it is invalid, returns the error. The return value must be freed
1377  * with g_free.
1378  **/
1379 gchar *
gtr_po_check_po_file(GtrPo * po)1380 gtr_po_check_po_file (GtrPo * po)
1381 {
1382   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1383   struct po_xerror_handler handler;
1384 
1385   g_return_val_if_fail (po != NULL, NULL);
1386 
1387   handler.xerror = &on_gettext_po_xerror;
1388   handler.xerror2 = &on_gettext_po_xerror2;
1389   message_error = NULL;
1390 
1391   //TODO: handle error and mark wrong msgids
1392   po_file_check_all (priv->gettext_po_file, &handler);
1393 
1394   return message_error;
1395 }
1396 
1397 const gchar *
gtr_po_get_dl_team(GtrPo * po)1398 gtr_po_get_dl_team (GtrPo *po)
1399 {
1400   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1401   return priv->dl_team;
1402 }
1403 
1404 const gchar *
gtr_po_get_dl_module(GtrPo * po)1405 gtr_po_get_dl_module (GtrPo *po)
1406 {
1407   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1408   return priv->dl_module;
1409 }
1410 
1411 const gchar *
gtr_po_get_dl_branch(GtrPo * po)1412 gtr_po_get_dl_branch (GtrPo *po)
1413 {
1414   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1415   return priv->dl_branch;
1416 }
1417 
1418 const gchar *
gtr_po_get_dl_domain(GtrPo * po)1419 gtr_po_get_dl_domain (GtrPo *po)
1420 {
1421   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1422   return priv->dl_domain;
1423 }
1424 
1425 const gchar *
gtr_po_get_dl_module_state(GtrPo * po)1426 gtr_po_get_dl_module_state (GtrPo *po)
1427 {
1428   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1429   return priv->dl_state;
1430 }
1431 
1432 gboolean
gtr_po_can_dl_upload(GtrPo * po)1433 gtr_po_can_dl_upload (GtrPo *po)
1434 {
1435   GtrPoPrivate *priv = gtr_po_get_instance_private (po);
1436   return g_strcmp0 (priv->dl_state, "Translating") == 0;
1437 }
1438