1 /*
2 * dialog-doc-metadata.c: Edit document metadata
3 *
4 * Copyright (C) 2005 Jody Goldberg (jody@gnome.org)
5 * Copyright (C) 2011 Andreas J. Guelzow (aguelzow@pyrshep.ca)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22 #include <gnumeric-config.h>
23 #include <gnumeric.h>
24 #include <dialogs/dialogs.h>
25 #include <dialogs/help.h>
26
27 #include <workbook.h>
28 #include <workbook-control.h>
29 #include <wbc-gtk.h>
30 #include <workbook-view.h>
31 #include <workbook-priv.h>
32 #include <gui-util.h>
33 #include <parse-util.h>
34 #include <value.h>
35 #include <expr.h>
36 #include <commands.h>
37 #include <number-match.h>
38 #include <dead-kittens.h>
39
40 #include <gsf/gsf-doc-meta-data.h>
41 #include <gsf/gsf-meta-names.h>
42 #include <gsf/gsf-timestamp.h>
43 #include <gsf/gsf-docprop-vector.h>
44
45 #include <goffice/goffice.h>
46
47
48 #include <glib-object.h>
49 #include <glib/gi18n-lib.h>
50 #include <glib/gprintf.h>
51
52 #include <string.h>
53
54
55 #define DOC_METADATA_KEY "dialog-doc-metadata"
56
57 enum {
58 ITEM_ICON,
59 ITEM_NAME,
60 PAGE_NUMBER,
61 NUM_COLUMNS
62 };
63
64 typedef struct {
65 GtkBuilder *gui;
66 GtkWidget *dialog;
67
68 /*pointer to the document metadata*/
69 GsfDocMetaData *metadata;
70
71 gboolean permissions_changed;
72 GOFilePermissions *file_permissions;
73
74 WBCGtk *wbcg;
75 Workbook *wb;
76 GODoc *doc;
77
78 GtkTreeStore *store;
79 GtkTreeView *view;
80
81 /* Dialog Widgets */
82 GtkNotebook *notebook;
83 GtkButton *help_button;
84 GtkButton *close_button;
85
86 /* File Information Page */
87 GtkLabel *file_name;
88 GtkLabel *location;
89 GtkLabel *created;
90 GtkLabel *modified;
91 GtkLabel *accessed;
92 GtkLabel *owner;
93 GtkLabel *group;
94
95 GtkCheckButton *owner_read;
96 GtkCheckButton *owner_write;
97
98 GtkCheckButton *group_read;
99 GtkCheckButton *group_write;
100
101 GtkCheckButton *others_read;
102 GtkCheckButton *others_write;
103
104 /* Description Page */
105 GtkEntry *title;
106 GtkEntry *subject;
107 GtkEntry *author;
108 GtkEntry *manager;
109 GtkEntry *company;
110 GtkEntry *category;
111
112 GtkTextView *comments;
113
114 /* Properties Page */
115 GtkTreeView *properties;
116 GtkTreeStore *properties_store;
117
118 GtkEntry *ppt_name;
119 GtkEntry *ppt_value;
120 GtkComboBox *ppt_type;
121 GtkListStore *type_store;
122 GtkTreeModelFilter *type_store_filter;
123
124 GtkButton *add_button;
125 GtkButton *remove_button;
126
127 GtkLabel *instruction;
128 GtkLabel *warning;
129
130 /* Keyword Page */
131 GtkTreeView *key_tree_view;
132 GtkListStore *key_store;
133 GtkButton *key_add_button;
134 GtkButton *key_remove_button;
135
136 /* Statistics Page */
137 GtkLabel *sheets;
138 GtkLabel *cells;
139 GtkLabel *pages;
140
141 /* Calculation Page */
142 GtkCheckButton *recalc_auto;
143 GtkCheckButton *recalc_manual;
144 GtkCheckButton *recalc_iteration;
145 GtkEntry *recalc_max;
146 GtkEntry *recalc_tolerance;
147 GtkWidget *recalc_iteration_grid;
148
149 } DialogDocMetaData;
150
151 #define trim_string(s_) g_strstrip (g_strdup ((s_)))
152
153 static gchar *dialog_doc_metadata_get_prop_val (DialogDocMetaData *state, char const *prop_name,
154 GValue *prop_value);
155
156 static gboolean cb_dialog_doc_metadata_ppt_changed (G_GNUC_UNUSED GtkEntry *entry,
157 G_GNUC_UNUSED GdkEventFocus *event,
158 DialogDocMetaData *state);
159
160
161 static GType
dialog_doc_metadata_get_value_type_from_name(gchar const * name,GType def)162 dialog_doc_metadata_get_value_type_from_name (gchar const *name, GType def)
163 {
164 /* shared by all instances and never freed */
165 static GHashTable *dialog_doc_metadata_name_to_type = NULL;
166 gpointer res;
167
168 if (NULL == dialog_doc_metadata_name_to_type) {
169 static struct {
170 char const *name;
171 GType type;
172 } const map [] = {
173 {GSF_META_NAME_GENERATOR, G_TYPE_STRING},
174 {GSF_META_NAME_INITIAL_CREATOR, G_TYPE_STRING},
175 {GSF_META_NAME_CREATOR, G_TYPE_STRING},
176 {GSF_META_NAME_TITLE, G_TYPE_STRING},
177 {GSF_META_NAME_SUBJECT, G_TYPE_STRING},
178 {GSF_META_NAME_MANAGER, G_TYPE_STRING},
179 {GSF_META_NAME_COMPANY, G_TYPE_STRING},
180 {GSF_META_NAME_CATEGORY, G_TYPE_STRING},
181 {GSF_META_NAME_DESCRIPTION, G_TYPE_STRING},
182 {GSF_META_NAME_LAST_SAVED_BY, G_TYPE_STRING},
183 {GSF_META_NAME_TEMPLATE, G_TYPE_STRING},
184 {GSF_META_NAME_EDITING_DURATION, G_TYPE_STRING}, /* special */
185 {GSF_META_NAME_SPREADSHEET_COUNT, G_TYPE_INT},
186 {GSF_META_NAME_TABLE_COUNT, G_TYPE_INT},
187 {GSF_META_NAME_CELL_COUNT, G_TYPE_INT},
188 {GSF_META_NAME_CHARACTER_COUNT, G_TYPE_INT},
189 {GSF_META_NAME_BYTE_COUNT, G_TYPE_INT},
190 {GSF_META_NAME_SECURITY, G_TYPE_INT},
191 {GSF_META_NAME_HIDDEN_SLIDE_COUNT, G_TYPE_INT},
192 {GSF_META_NAME_LINE_COUNT, G_TYPE_INT},
193 {GSF_META_NAME_SLIDE_COUNT, G_TYPE_INT},
194 {GSF_META_NAME_WORD_COUNT, G_TYPE_INT},
195 {GSF_META_NAME_MM_CLIP_COUNT, G_TYPE_INT},
196 {GSF_META_NAME_NOTE_COUNT, G_TYPE_INT},
197 {GSF_META_NAME_PARAGRAPH_COUNT, G_TYPE_INT},
198 {GSF_META_NAME_PAGE_COUNT, G_TYPE_INT},
199 {GSF_META_NAME_CODEPAGE, G_TYPE_INT},
200 {GSF_META_NAME_LOCALE_SYSTEM_DEFAULT,G_TYPE_INT},
201 {GSF_META_NAME_OBJECT_COUNT, G_TYPE_INT},
202 {"xlsx:HyperlinksChanged", G_TYPE_BOOLEAN},
203 {GSF_META_NAME_LINKS_DIRTY, G_TYPE_BOOLEAN},
204 {"xlsx:SharedDoc", G_TYPE_BOOLEAN},
205 {GSF_META_NAME_SCALE, G_TYPE_BOOLEAN}
206 };
207 static char const *map_vector[] =
208 {GSF_META_NAME_KEYWORDS,
209 GSF_META_NAME_DOCUMENT_PARTS,
210 GSF_META_NAME_HEADING_PAIRS};
211 static char const *map_timestamps[] =
212 {GSF_META_NAME_DATE_CREATED,
213 GSF_META_NAME_DATE_MODIFIED};
214
215 /*Missing:GSF_META_NAME_THUMBNAIL */
216
217 int i = G_N_ELEMENTS (map);
218 dialog_doc_metadata_name_to_type = g_hash_table_new (g_str_hash, g_str_equal);
219 while (i-- > 0)
220 g_hash_table_insert (dialog_doc_metadata_name_to_type,
221 (gpointer)map[i].name,
222 GINT_TO_POINTER (map[i].type));
223
224 i = G_N_ELEMENTS (map_vector);
225 while (i-- > 0)
226 g_hash_table_insert (dialog_doc_metadata_name_to_type,
227 (gpointer)map_vector[i],
228 GINT_TO_POINTER (GSF_DOCPROP_VECTOR_TYPE));
229
230 i = G_N_ELEMENTS (map_timestamps);
231 while (i-- > 0)
232 g_hash_table_insert (dialog_doc_metadata_name_to_type,
233 (gpointer)map_timestamps[i],
234 GINT_TO_POINTER (GSF_TIMESTAMP_TYPE));
235
236 }
237
238 res = g_hash_table_lookup (dialog_doc_metadata_name_to_type, name);
239
240 if (res != NULL)
241 return GPOINTER_TO_INT (res);
242 else
243 return def;
244 }
245
246 static GType
dialog_doc_metadata_get_value_type(GValue * value)247 dialog_doc_metadata_get_value_type (GValue *value)
248 {
249 GType val_type = G_VALUE_TYPE (value);
250
251 switch (val_type) {
252 case G_TYPE_INT:
253 case G_TYPE_UINT:
254 case G_TYPE_FLOAT:
255 case G_TYPE_DOUBLE:
256 case G_TYPE_STRING:
257 case G_TYPE_BOOLEAN:
258 /* Just leave it as is */
259 break;
260
261 default:
262 /* Check if it is a GsfDocPropVector or GsfTimeStamp */
263 if (VAL_IS_GSF_TIMESTAMP (value))
264 val_type = GSF_TIMESTAMP_TYPE;
265 else if (VAL_IS_GSF_DOCPROP_VECTOR (value))
266 val_type = GSF_DOCPROP_VECTOR_TYPE;
267 else {
268 g_printerr ("GType %s (%i) not handled in metadata dialog.\n",
269 g_type_name (val_type), (int) val_type);
270 val_type = G_TYPE_INVALID;
271 }
272
273 break;
274 }
275 return val_type;
276 }
277
278
279 /******************************************************************************
280 * G_VALUE TRANSFORM FUNCTIONS
281 ******************************************************************************/
282
283 /*
284 * G_TYPE_STRING TO OTHER
285 */
286
287 static void
dialog_doc_metadata_transform_str_to_timestamp(const char * str,GValue * timestamp_value)288 dialog_doc_metadata_transform_str_to_timestamp (const char *str,
289 GValue *timestamp_value)
290 {
291 time_t s;
292 gnm_float serial;
293 gint int_serial;
294 GsfTimestamp *gt;
295 GnmValue *conversion;
296 GOFormat *fmt;
297
298 g_return_if_fail (VAL_IS_GSF_TIMESTAMP (timestamp_value));
299
300 fmt = go_format_new_from_XL ("yyyy-mm-dd hh:mm:ss");
301 conversion = format_match_number (str, fmt, NULL);
302 go_format_unref (fmt);
303 if (conversion) {
304 serial = value_get_as_float (conversion);
305 value_release (conversion);
306
307 /* Convert from Gnumeric time to unix time */
308 int_serial = (int)serial;
309 s = go_date_serial_to_timet (int_serial, NULL);
310
311 if (gnm_abs (serial - int_serial) >= 1.0 || s == (time_t)-1) {
312 s = time (NULL);
313 } else
314 s += (gnm_fake_round (3600 * 24 * (serial - int_serial)));
315
316 } else
317 s = time (NULL);
318
319 gt = gsf_timestamp_new ();
320 gsf_timestamp_set_time (gt, s);
321 gsf_timestamp_to_value (gt, timestamp_value);
322 }
323
324 static void
dialog_doc_metadata_transform_str_to_docprop_vect(const char * str,GValue * docprop_value)325 dialog_doc_metadata_transform_str_to_docprop_vect (const char *str,
326 GValue *docprop_value)
327 {
328 char const *s;
329 GsfDocPropVector *gdpv;
330 GValue *value;
331
332 g_return_if_fail (VAL_IS_GSF_DOCPROP_VECTOR (docprop_value));
333
334 gdpv = gsf_docprop_vector_new ();
335
336 while (*str == ' ') {str++;}
337
338 while (*str == '"') {
339 char *key;
340 s = str += 1;
341 while (*s != '"' && *s != '\0') {
342 if (*(s++) == '\\') {
343 if (*s == '\0')
344 goto str_done;
345 s++;
346 }
347 }
348 if (*s == '\0')
349 goto str_done;
350 /* s == '"' */
351 key = g_strndup (str, s - str);
352 value = g_new0 (GValue, 1);
353 g_value_take_string (g_value_init (value, G_TYPE_STRING),
354 g_strcompress (key));
355 gsf_docprop_vector_append (gdpv, value);
356 g_free (key);
357 str = s + 1;
358 while (*str == ' ') {str++;}
359 if (str[0] != ',')
360 goto str_done;
361 str++;
362 while (*str == ' ') {str++;}
363 }
364 str_done:
365 g_value_set_object (docprop_value, gdpv);
366 g_object_unref (gdpv);
367 }
368
369 static char *
time2str(time_t t)370 time2str (time_t t)
371 {
372 char buffer[4000];
373 gsize len;
374 char const *format = "%c";
375
376 if (t == -1)
377 return NULL;
378
379 len = strftime (buffer, sizeof (buffer), format, localtime (&t));
380 if (len == 0)
381 return NULL;
382
383 return g_locale_to_utf8 (buffer, len, NULL, NULL, NULL);
384 }
385
386 static char *
time2str_go(time_t t)387 time2str_go (time_t t)
388 {
389 /* We need to create a format that is also parsable */
390 char *str;
391 GOFormat *fmt;
392 gnm_float t_gnm;
393
394 if (t == -1)
395 return NULL;
396
397 t_gnm = go_date_timet_to_serial_raw (t, NULL);
398
399 fmt = go_format_new_from_XL ("yyyy-mm-dd hh:mm:ss");
400 str = go_format_value (fmt, t_gnm);
401 go_format_unref (fmt);
402 return str;
403 }
404
405 /*
406 * OTHER TO G_TYPE_STRING
407 */
408 static void
dialog_doc_metadata_transform_timestamp_to_str(const GValue * timestamp_value,GValue * string_value)409 dialog_doc_metadata_transform_timestamp_to_str (const GValue *timestamp_value,
410 GValue *string_value)
411 {
412 GsfTimestamp const *timestamp = NULL;
413
414 g_return_if_fail (VAL_IS_GSF_TIMESTAMP (timestamp_value));
415 g_return_if_fail (G_VALUE_HOLDS_STRING (string_value));
416
417 timestamp = g_value_get_boxed (timestamp_value);
418 if (timestamp != NULL)
419 g_value_take_string (string_value,
420 time2str_go (timestamp->timet));
421 }
422
423 static gchar*
gnm_docprop_vector_as_string(GsfDocPropVector * vector)424 gnm_docprop_vector_as_string (GsfDocPropVector *vector)
425 {
426 GString *rstring;
427 guint i;
428 guint num_values;
429 GValueArray *gva;
430 GValue vl = G_VALUE_INIT;
431
432 g_return_val_if_fail (vector != NULL, NULL);
433
434 g_value_set_object (g_value_init (&vl, GSF_DOCPROP_VECTOR_TYPE), vector);
435 gva = gsf_value_get_docprop_varray (&vl);
436
437 g_return_val_if_fail (gva != NULL, NULL);
438
439 num_values = gva->n_values;
440 rstring = g_string_sized_new (num_values * 8);
441
442 for (i = 0; i < num_values; i++) {
443 char *str;
444 GValue *v;
445
446 v = g_value_array_get_nth (gva, i);
447
448 if (G_VALUE_TYPE(v) == G_TYPE_STRING)
449 str = g_strescape (g_value_get_string (v), "");
450 else {
451 char *b_str = g_strdup_value_contents (v);
452 str = g_strescape (b_str, "");
453 g_free (b_str);
454 }
455 g_string_append_c (rstring, '"');
456 g_string_append (rstring, str);
457 g_string_append (rstring, "\", ");
458 g_free (str);
459 }
460 if (rstring->len > 0)
461 g_string_truncate (rstring, rstring->len - 2);
462
463 g_value_unset (&vl);
464
465 return g_string_free (rstring, FALSE);
466 }
467
468 static void
dialog_doc_metadata_transform_docprop_vect_to_str(const GValue * docprop_value,GValue * string_value)469 dialog_doc_metadata_transform_docprop_vect_to_str (const GValue *docprop_value,
470 GValue *string_value)
471 {
472 GsfDocPropVector * docprop_vector = NULL;
473
474 g_return_if_fail (VAL_IS_GSF_DOCPROP_VECTOR (docprop_value));
475 g_return_if_fail (G_VALUE_HOLDS_STRING (string_value));
476
477 docprop_vector = gsf_value_get_docprop_vector (docprop_value);
478
479 if (docprop_vector != NULL)
480 g_value_set_string (string_value,
481 gnm_docprop_vector_as_string (docprop_vector));
482 }
483
484 /******************************************************************************
485 * FUNCTIONS RELATED TO 'FILE' PAGE
486 ******************************************************************************/
487
488 static void
cb_dialog_doc_metadata_change_permission(GtkCheckButton * bt,DialogDocMetaData * state)489 cb_dialog_doc_metadata_change_permission (GtkCheckButton *bt,
490 DialogDocMetaData *state)
491 {
492 g_return_if_fail (state->file_permissions != NULL);
493
494 /* Check which button was toggled */
495 if (bt == state->owner_read)
496 state->file_permissions->owner_read = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (bt));
497 else if (bt == state->owner_write)
498 state->file_permissions->owner_write = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (bt));
499 else if (bt == state->group_read)
500 state->file_permissions->group_read = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (bt));
501 else if (bt == state->group_write)
502 state->file_permissions->group_write = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (bt));
503 else if (bt == state->others_read)
504 state->file_permissions->others_read = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (bt));
505 else if (bt == state->others_write)
506 state->file_permissions->others_write = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (bt));
507 else
508 return;
509
510 state->permissions_changed = TRUE;
511 }
512
513 static void
dialog_doc_metadata_set_up_permissions(DialogDocMetaData * state)514 dialog_doc_metadata_set_up_permissions (DialogDocMetaData *state)
515 {
516 g_return_if_fail (state->metadata != NULL);
517
518 state->file_permissions = go_get_file_permissions (
519 go_doc_get_uri (state->doc));
520
521 if (state->file_permissions != NULL) {
522 /* Set Check buttons */
523
524 /* Owner */
525 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->owner_read),
526 state->file_permissions->owner_read);
527
528 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->owner_write),
529 state->file_permissions->owner_write);
530
531 /* Group */
532 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->group_read),
533 state->file_permissions->group_read);
534
535 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->group_write),
536 state->file_permissions->group_write);
537
538 /* Others */
539 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->others_read),
540 state->file_permissions->others_read);
541
542 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->others_write),
543 state->file_permissions->others_write);
544 }
545
546 /* At this moment we don't let user change file permissions */
547 gtk_widget_set_sensitive (GTK_WIDGET (state->owner_read), FALSE);
548 gtk_widget_set_sensitive (GTK_WIDGET (state->owner_write), FALSE);
549 gtk_widget_set_sensitive (GTK_WIDGET (state->group_read), FALSE);
550 gtk_widget_set_sensitive (GTK_WIDGET (state->group_write), FALSE);
551 gtk_widget_set_sensitive (GTK_WIDGET (state->others_read), FALSE);
552 gtk_widget_set_sensitive (GTK_WIDGET (state->others_write), FALSE);
553 }
554
555 /* @auto_fill : if TRUE and the text is NULL, try to set the label text with an automatic value. */
556 static void
dialog_doc_metadata_set_label(DialogDocMetaData * state,GtkLabel * label,char const * text,gboolean auto_fill)557 dialog_doc_metadata_set_label (DialogDocMetaData *state,
558 GtkLabel *label,
559 char const *text,
560 gboolean auto_fill)
561 {
562 Workbook *wb = state->wb;
563 gchar *str_value = NULL;
564
565 g_return_if_fail (label != NULL);
566
567 if (text != NULL)
568 str_value = g_strdup (text);
569
570 if (str_value == NULL && auto_fill == TRUE) {
571 /* File Name */
572 if (label == state->file_name) {
573 str_value = go_basename_from_uri (go_doc_get_uri (state->doc));
574 }
575
576 /* File Location */
577 else if (label == state->location) {
578 str_value = go_dirname_from_uri (go_doc_get_uri (state->doc), TRUE);
579 }
580
581 /* Date Created */
582 else if (label == state->created) {
583 /* Nothing to do ATM */
584 }
585
586 /* Date Modified */
587 else if (label == state->modified) {
588 str_value = time2str (go_file_get_date_modified (go_doc_get_uri (state->doc)));
589 }
590
591 /* Date Accessed */
592 else if (label == state->accessed) {
593 str_value = time2str (go_file_get_date_accessed (go_doc_get_uri (state->doc)));
594 }
595
596 /* Owner */
597 else if (label == state->owner) {
598 str_value = go_file_get_owner_name (go_doc_get_uri (state->doc));
599 }
600
601 /* Group */
602 else if (label == state->group) {
603 str_value = go_file_get_group_name (go_doc_get_uri (state->doc));
604 }
605
606 /* Number of Sheets */
607 else if (label == state->sheets) {
608 str_value = g_strdup_printf ("%d", workbook_sheet_count (wb));
609 }
610
611 /* Number of cells */
612 else if (label == state->cells) {
613 /* Nothing to do ATM */
614 }
615
616 /* Number of pages */
617 else if (label == state->pages) {
618 /* Nothing to do ATM */
619 }
620 }
621
622 if (str_value != NULL) {
623 gtk_label_set_text (label, str_value);
624 g_free (str_value);
625 } else
626 gtk_label_set_text (label, _("Unknown"));
627 }
628
629 static void
dialog_doc_metadata_init_file_page(DialogDocMetaData * state)630 dialog_doc_metadata_init_file_page (DialogDocMetaData *state)
631 {
632 g_return_if_fail (state->metadata != NULL);
633
634 /* Set up the labels */
635 dialog_doc_metadata_set_label (state, state->file_name, NULL, TRUE);
636 dialog_doc_metadata_set_label (state, state->location, NULL, TRUE);
637 dialog_doc_metadata_set_label (state, state->created, NULL, TRUE);
638 dialog_doc_metadata_set_label (state, state->modified, NULL, TRUE);
639 dialog_doc_metadata_set_label (state, state->accessed, NULL, TRUE);
640 dialog_doc_metadata_set_label (state, state->owner, NULL, TRUE);
641 dialog_doc_metadata_set_label (state, state->group, NULL, TRUE);
642
643 /* Set up check buttons */
644 state->permissions_changed = FALSE;
645 dialog_doc_metadata_set_up_permissions (state);
646
647 /* Signals */
648
649 /* Owner */
650 g_signal_connect (G_OBJECT (state->owner_read),
651 "toggled",
652 G_CALLBACK (cb_dialog_doc_metadata_change_permission),
653 state);
654
655 g_signal_connect (G_OBJECT (state->owner_write),
656 "toggled",
657 G_CALLBACK (cb_dialog_doc_metadata_change_permission),
658 state);
659
660 /* Group */
661 g_signal_connect (G_OBJECT (state->group_read),
662 "toggled",
663 G_CALLBACK (cb_dialog_doc_metadata_change_permission),
664 state);
665
666 g_signal_connect (G_OBJECT (state->group_write),
667 "toggled",
668 G_CALLBACK (cb_dialog_doc_metadata_change_permission),
669 state);
670
671 /* Others */
672 g_signal_connect (G_OBJECT (state->others_read),
673 "toggled",
674 G_CALLBACK (cb_dialog_doc_metadata_change_permission),
675 state);
676
677 g_signal_connect (G_OBJECT (state->others_write),
678 "toggled",
679 G_CALLBACK (cb_dialog_doc_metadata_change_permission),
680 state);
681
682 }
683
684 /******************************************************************************
685 * FUNCTIONS RELATED TO 'DESCRIPTION' PAGE
686 ******************************************************************************/
687
688 /* @activate_property : if TRUE, sets the tree view row which was added active. */
689 static void
dialog_doc_metadata_add_prop(DialogDocMetaData * state,const gchar * name,const gchar * value,const gchar * lnk,GType val_type)690 dialog_doc_metadata_add_prop (DialogDocMetaData *state,
691 const gchar *name,
692 const gchar *value,
693 const gchar *lnk,
694 GType val_type)
695 {
696 gboolean editable = (val_type != G_TYPE_INVALID)
697 && (val_type != GSF_DOCPROP_VECTOR_TYPE);
698 if (value == NULL)
699 value = "";
700
701 if (lnk == NULL)
702 lnk = "";
703
704 /* Append new values in tree view */
705 gtk_tree_store_insert_with_values (state->properties_store, NULL, NULL, G_MAXINT,
706 0, name,
707 1, value,
708 2, lnk,
709 3, editable,
710 4, val_type,
711 -1);
712 }
713
714 static GType
dialog_doc_metadata_get_gsf_prop_val_type(DialogDocMetaData * state,const gchar * name)715 dialog_doc_metadata_get_gsf_prop_val_type (DialogDocMetaData *state,
716 const gchar *name)
717 {
718 GsfDocProp *prop = NULL;
719 GValue *value = NULL;
720
721 g_return_val_if_fail (state->metadata != NULL, G_TYPE_INVALID);
722
723 /* First we try the clean way */
724 prop = gsf_doc_meta_data_lookup (state->metadata, name);
725
726 if (prop != NULL)
727 value = (GValue *) gsf_doc_prop_get_val (prop);
728
729 if (value != NULL)
730 return dialog_doc_metadata_get_value_type (value);
731 else
732 return dialog_doc_metadata_get_value_type_from_name (name, G_TYPE_STRING);
733 }
734
735 static void
dialog_doc_metadata_set_gsf_prop_val(DialogDocMetaData * state,GValue * prop_value,const gchar * str_val)736 dialog_doc_metadata_set_gsf_prop_val (DialogDocMetaData *state,
737 GValue *prop_value,
738 const gchar *str_val)
739 {
740 GType t = G_VALUE_TYPE (prop_value);
741
742 /* The preinstalled transform functions do not handle simple transformations */
743 /* such as from string to double, so we do that ourselves */
744 switch (t) {
745 case G_TYPE_STRING:
746 g_value_set_string (prop_value, str_val);
747 return;
748 case G_TYPE_DOUBLE:
749 case G_TYPE_FLOAT: {
750 GnmValue *val = format_match_number (str_val, NULL, workbook_date_conv (state->wb));
751 if (val != NULL) {
752 gnm_float fl = value_get_as_float (val);
753 value_release (val);
754 if (t == G_TYPE_DOUBLE)
755 g_value_set_double (prop_value, fl);
756 else
757 g_value_set_float (prop_value, fl);
758 }
759 return;
760 }
761 case G_TYPE_INT:
762 g_value_set_int (prop_value, strtol (str_val, NULL, 10));
763 return;
764 case G_TYPE_UINT:
765 g_value_set_uint (prop_value, strtoul (str_val, NULL, 10));
766 return;
767 case G_TYPE_BOOLEAN: {
768 GnmValue *val = format_match_number (str_val, NULL, workbook_date_conv (state->wb));
769 gboolean b = FALSE;
770 if (val != NULL) {
771 gboolean err;
772 b = value_get_as_bool (val, &err);
773 value_release (val);
774 }
775 g_value_set_boolean (prop_value, b);
776 return;
777 }
778 }
779
780 if (t == GSF_TIMESTAMP_TYPE) {
781 dialog_doc_metadata_transform_str_to_timestamp (str_val, prop_value);
782 } else if (t == GSF_DOCPROP_VECTOR_TYPE) {
783 dialog_doc_metadata_transform_str_to_docprop_vect (str_val, prop_value);
784 } else {
785 g_printerr (_("Transform function of G_TYPE_STRING to %s is required!\n"),
786 g_type_name (t));
787 }
788 }
789
790 /**
791 * dialog_doc_metadata_set_gsf_prop
792 * @state: dialog main struct
793 * @name: property name
794 * @value: property value
795 * @lnk: property linked to
796 *
797 * Sets a new value to the property in the GsfDocMetaData struct
798 *
799 **/
800 static GType
dialog_doc_metadata_set_gsf_prop(DialogDocMetaData * state,const gchar * name,const gchar * value,const gchar * lnk,GType type)801 dialog_doc_metadata_set_gsf_prop (DialogDocMetaData *state,
802 const gchar *name,
803 const gchar *value,
804 const gchar *lnk,
805 GType type)
806 {
807 GsfDocProp *existing_prop = NULL;
808 GsfDocProp *doc_prop;
809 GValue *existing_value = NULL;
810 char const *existing_link = NULL;
811 GType val_type;
812
813 existing_prop = gsf_doc_meta_data_lookup (state->metadata, name);
814 if (existing_prop != NULL) {
815 existing_value = (GValue *) gsf_doc_prop_get_val (existing_prop);
816 existing_link = gsf_doc_prop_get_link (existing_prop);
817 }
818
819 if (lnk != NULL && *lnk == 0)
820 lnk = NULL;
821 if (value != NULL && *value == 0)
822 value = NULL;
823 if ((value == NULL) && (lnk == NULL)) {
824 if ((existing_prop == NULL) ||
825 ((existing_value == NULL) && (existing_link == NULL)))
826 return G_TYPE_INVALID;
827 else {
828 cmd_change_meta_data (GNM_WBC (state->wbcg), NULL,
829 g_slist_prepend (NULL, g_strdup (name)));
830 return G_TYPE_INVALID;
831 }
832 }
833
834 if (existing_prop != NULL) {
835 gboolean link_changed;
836 gboolean value_changed = TRUE;
837
838 if (existing_link!= NULL && *existing_link == 0)
839 existing_link = NULL;
840 if (lnk == existing_link)
841 link_changed = FALSE;
842 else if (lnk == NULL || existing_link == NULL)
843 link_changed = TRUE;
844 else
845 link_changed = (0 != strcmp (lnk, existing_link));
846
847 if (existing_value == NULL)
848 value_changed = (value != NULL);
849 else if (G_VALUE_HOLDS_STRING (existing_value) && (type == 0 || type == G_TYPE_STRING)) {
850 char const * existing_val_str = g_value_get_string (existing_value);
851 if (existing_val_str != NULL && *existing_val_str == 0)
852 existing_val_str = NULL;
853 if (value == existing_val_str)
854 value_changed = FALSE;
855 else if (value == NULL || existing_val_str == NULL)
856 value_changed = TRUE;
857 else
858 value_changed = (0 != strcmp (value, existing_val_str));
859 if (!link_changed && !value_changed)
860 return G_TYPE_STRING;
861 }
862 }
863
864
865 /* Create a new GsfDocProp */
866 doc_prop = gsf_doc_prop_new (g_strdup (name));
867
868 if (type == 0)
869 val_type = dialog_doc_metadata_get_gsf_prop_val_type (state, name);
870 else
871 val_type = type;
872
873 if (val_type != G_TYPE_INVALID) {
874 GValue *doc_prop_value;
875
876 /* Create a new Value */
877 doc_prop_value = g_new0 (GValue, 1);
878
879 g_value_init (doc_prop_value, val_type);
880 dialog_doc_metadata_set_gsf_prop_val (state, doc_prop_value, value);
881 gsf_doc_prop_set_val (doc_prop, doc_prop_value);
882 }
883
884 if (lnk != NULL)
885 gsf_doc_prop_set_link (doc_prop, g_strdup (lnk));
886
887 cmd_change_meta_data (GNM_WBC (state->wbcg),
888 g_slist_prepend (NULL, doc_prop), NULL);
889
890 return val_type;
891 }
892
893 /**
894 * dialog_doc_metadata_set_prop
895 * @state: dialog main struct
896 * @prop_name: property name
897 * @prop_value: property value
898 *
899 * Tries to update the property value in all the dialog and in the GsfDocMetaData
900 * struct. If the property was not found, creates a new one.
901 *
902 **/
903 static void
dialog_doc_metadata_set_prop(DialogDocMetaData * state,const gchar * prop_name,const gchar * prop_value,const gchar * link_value,GType type)904 dialog_doc_metadata_set_prop (DialogDocMetaData *state,
905 const gchar *prop_name,
906 const gchar *prop_value,
907 const gchar *link_value,
908 GType type)
909 {
910 GtkTreeIter tree_iter;
911 GValue *value;
912 gboolean ret;
913 gboolean found;
914 GType val_type;
915 GsfDocProp *updated_prop;
916 gchar *new_prop_value = NULL;
917
918 g_return_if_fail (state->metadata != NULL);
919
920 val_type = dialog_doc_metadata_set_gsf_prop (state, prop_name, prop_value,
921 link_value, type);
922
923 /* Due to changes in type, prop_value may have changed */
924 updated_prop = gsf_doc_meta_data_lookup (state->metadata, prop_name);
925 if (updated_prop != NULL) {
926 GValue *new_value = (GValue *) gsf_doc_prop_get_val (updated_prop);
927 if (new_value != NULL)
928 new_prop_value = dialog_doc_metadata_get_prop_val (state, prop_name, new_value);
929 if (new_prop_value == NULL)
930 new_prop_value = g_strdup ("");
931 }
932
933 found = FALSE;
934
935 /* Search tree view for property */
936 value = g_new0 (GValue, 1);
937
938 ret = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (state->properties_store),
939 &tree_iter);
940
941 while (ret == TRUE) {
942
943 gtk_tree_model_get_value (GTK_TREE_MODEL (state->properties_store),
944 &tree_iter,
945 0,
946 value);
947
948 if (strcmp (prop_name, g_value_get_string (value)) == 0) {
949 if (updated_prop != NULL) {
950 /* Set new value */
951 gtk_tree_store_set (state->properties_store,
952 &tree_iter,
953 1, new_prop_value,
954 -1);
955
956 if (link_value != NULL)
957 gtk_tree_store_set (state->properties_store,
958 &tree_iter,
959 2, link_value,
960 -1);
961 } else
962 /* Clear value */
963 gtk_tree_store_remove (state->properties_store,
964 &tree_iter);
965
966 g_value_unset (value);
967
968 found = TRUE;
969 break;
970 }
971
972 g_value_unset (value);
973 ret = gtk_tree_model_iter_next (GTK_TREE_MODEL (state->properties_store),
974 &tree_iter);
975 }
976
977 if (val_type != G_TYPE_INVALID && found == FALSE)
978 dialog_doc_metadata_add_prop (state, prop_name, new_prop_value, "", val_type);
979
980 /* Free all data */
981 g_free (value);
982 g_free (new_prop_value );
983 }
984
985 /*
986 * CALLBACKS for 'Description' page entries
987 */
988 static gboolean
cb_dialog_doc_metadata_title_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)989 cb_dialog_doc_metadata_title_changed (GtkEntry *entry,
990 G_GNUC_UNUSED GdkEventFocus *event,
991 DialogDocMetaData *state)
992 {
993 dialog_doc_metadata_set_prop (state,
994 GSF_META_NAME_TITLE,
995 gtk_entry_get_text (entry),
996 NULL, G_TYPE_STRING);
997 return FALSE;
998 }
999
1000 static gboolean
cb_dialog_doc_metadata_subject_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1001 cb_dialog_doc_metadata_subject_changed (GtkEntry *entry,
1002 G_GNUC_UNUSED GdkEventFocus *event,
1003 DialogDocMetaData *state)
1004 {
1005 dialog_doc_metadata_set_prop (state,
1006 GSF_META_NAME_SUBJECT,
1007 gtk_entry_get_text (entry),
1008 NULL, G_TYPE_STRING);
1009 return FALSE;
1010 }
1011
1012 static gboolean
cb_dialog_doc_metadata_author_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1013 cb_dialog_doc_metadata_author_changed (GtkEntry *entry,
1014 G_GNUC_UNUSED GdkEventFocus *event,
1015 DialogDocMetaData *state)
1016 {
1017 dialog_doc_metadata_set_prop (state,
1018 GSF_META_NAME_INITIAL_CREATOR,
1019 gtk_entry_get_text (entry),
1020 NULL, G_TYPE_STRING);
1021 return FALSE;
1022 }
1023
1024 static gboolean
cb_dialog_doc_metadata_manager_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1025 cb_dialog_doc_metadata_manager_changed (GtkEntry *entry,
1026 G_GNUC_UNUSED GdkEventFocus *event,
1027 DialogDocMetaData *state)
1028 {
1029 dialog_doc_metadata_set_prop (state,
1030 GSF_META_NAME_MANAGER,
1031 gtk_entry_get_text (entry),
1032 NULL, G_TYPE_STRING);
1033 return FALSE;
1034 }
1035
1036 static gboolean
cb_dialog_doc_metadata_company_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1037 cb_dialog_doc_metadata_company_changed (GtkEntry *entry,
1038 G_GNUC_UNUSED GdkEventFocus *event,
1039 DialogDocMetaData *state)
1040 {
1041 dialog_doc_metadata_set_prop (state,
1042 GSF_META_NAME_COMPANY,
1043 gtk_entry_get_text (entry),
1044 NULL, G_TYPE_STRING);
1045 return FALSE;
1046 }
1047
1048 static gboolean
cb_dialog_doc_metadata_category_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1049 cb_dialog_doc_metadata_category_changed (GtkEntry *entry,
1050 G_GNUC_UNUSED GdkEventFocus *event,
1051 DialogDocMetaData *state)
1052 {
1053 dialog_doc_metadata_set_prop (state,
1054 GSF_META_NAME_CATEGORY,
1055 gtk_entry_get_text (entry),
1056 NULL, G_TYPE_STRING );
1057 return FALSE;
1058 }
1059
1060 static gboolean
cb_dialog_doc_metadata_comments_changed(GtkTextView * view,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1061 cb_dialog_doc_metadata_comments_changed (GtkTextView *view,
1062 G_GNUC_UNUSED GdkEventFocus *event,
1063 DialogDocMetaData *state)
1064 {
1065 GtkTextIter start_iter;
1066 GtkTextIter end_iter;
1067 gchar *text;
1068 GtkTextBuffer *buffer = gtk_text_view_get_buffer (view);
1069
1070 gtk_text_buffer_get_start_iter (buffer, &start_iter);
1071 gtk_text_buffer_get_end_iter (buffer, &end_iter);
1072
1073 text = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, TRUE);
1074
1075 dialog_doc_metadata_set_prop (state,
1076 GSF_META_NAME_DESCRIPTION,
1077 text,
1078 NULL, G_TYPE_STRING);
1079 return FALSE;
1080 }
1081
1082 /**
1083 * dialog_doc_metadata_init_description_page
1084 * @state: dialog main struct
1085 *
1086 * Initializes the widgets and signals for the 'Description' page.
1087 *
1088 **/
1089 static void
dialog_doc_metadata_init_description_page(DialogDocMetaData * state)1090 dialog_doc_metadata_init_description_page (DialogDocMetaData *state)
1091 {
1092 g_return_if_fail (state->metadata != NULL);
1093
1094 /* At this point, the entry values were already filled */
1095
1096 /* Set up the signals */
1097 g_signal_connect (G_OBJECT (state->title),
1098 "focus-out-event",
1099 G_CALLBACK (cb_dialog_doc_metadata_title_changed),
1100 state);
1101
1102 g_signal_connect (G_OBJECT (state->subject),
1103 "focus-out-event",
1104 G_CALLBACK (cb_dialog_doc_metadata_subject_changed),
1105 state);
1106
1107 g_signal_connect (G_OBJECT (state->author),
1108 "focus-out-event",
1109 G_CALLBACK (cb_dialog_doc_metadata_author_changed),
1110 state);
1111
1112 g_signal_connect (G_OBJECT (state->manager),
1113 "focus-out-event",
1114 G_CALLBACK (cb_dialog_doc_metadata_manager_changed),
1115 state);
1116
1117 g_signal_connect (G_OBJECT (state->company),
1118 "focus-out-event",
1119 G_CALLBACK (cb_dialog_doc_metadata_company_changed),
1120 state);
1121
1122 g_signal_connect (G_OBJECT (state->category),
1123 "focus-out-event",
1124 G_CALLBACK (cb_dialog_doc_metadata_category_changed),
1125 state);
1126
1127 g_signal_connect (G_OBJECT (state->comments),
1128 "focus-out-event",
1129 G_CALLBACK (cb_dialog_doc_metadata_comments_changed),
1130 state);
1131 }
1132
1133 /******************************************************************************
1134 * FUNCTIONS RELATED TO 'KEYWORDS' PAGE
1135 ******************************************************************************/
1136
1137 static void
dialog_doc_metadata_update_keywords_changed(DialogDocMetaData * state)1138 dialog_doc_metadata_update_keywords_changed (DialogDocMetaData *state)
1139 {
1140 GValue val = G_VALUE_INIT;
1141 GtkTreeIter iter;
1142 GsfDocPropVector *vector = gsf_docprop_vector_new ();
1143
1144 g_value_init (&val, GSF_DOCPROP_VECTOR_TYPE);
1145
1146 if (gtk_tree_model_get_iter_first
1147 (GTK_TREE_MODEL (state->key_store), &iter)) {
1148 do {
1149 GValue *value = g_new0 (GValue, 1);
1150 gtk_tree_model_get_value
1151 (GTK_TREE_MODEL (state->key_store), &iter,
1152 0, value);
1153 gsf_docprop_vector_append (vector, value);
1154 g_value_unset (value);
1155 g_free (value);
1156 } while (gtk_tree_model_iter_next
1157 (GTK_TREE_MODEL (state->key_store), &iter));
1158 }
1159 g_value_set_object (&val, vector);
1160 g_object_unref (vector);
1161
1162 dialog_doc_metadata_set_prop
1163 (state, GSF_META_NAME_KEYWORDS,
1164 dialog_doc_metadata_get_prop_val (state, GSF_META_NAME_KEYWORDS, &val),
1165 NULL, GSF_DOCPROP_VECTOR_TYPE);
1166
1167 g_value_unset (&val);
1168 }
1169
1170 static void
cb_dialog_doc_metadata_keywords_sel_changed(GtkTreeSelection * treeselection,DialogDocMetaData * state)1171 cb_dialog_doc_metadata_keywords_sel_changed (GtkTreeSelection *treeselection,
1172 DialogDocMetaData *state)
1173 {
1174 gtk_widget_set_sensitive
1175 (GTK_WIDGET (state->key_remove_button),
1176 gtk_tree_selection_get_selected (treeselection, NULL, NULL));
1177 }
1178
1179 static void
dialog_doc_metadata_update_keyword_list(DialogDocMetaData * state,GsfDocProp * prop)1180 dialog_doc_metadata_update_keyword_list (DialogDocMetaData *state, GsfDocProp *prop)
1181 {
1182 guint i;
1183 GtkTreeSelection *sel;
1184
1185 gtk_list_store_clear (state->key_store);
1186
1187 if (prop != NULL) {
1188 GValueArray *array;
1189 array = gsf_value_get_docprop_varray (gsf_doc_prop_get_val (prop));
1190 if (array != NULL) {
1191 for (i = 0; i < array->n_values; i++) {
1192 GValue *val = g_value_array_get_nth (array, i);
1193 gtk_list_store_insert_with_values
1194 (state->key_store, NULL, G_MAXINT,
1195 0, g_value_get_string (val), -1);
1196 }
1197 }
1198 }
1199
1200 sel = gtk_tree_view_get_selection (state->key_tree_view);
1201 cb_dialog_doc_metadata_keywords_sel_changed (sel, state);
1202 }
1203
1204 static void
cb_dialog_doc_metadata_keywords_add_clicked(G_GNUC_UNUSED GtkWidget * w,DialogDocMetaData * state)1205 cb_dialog_doc_metadata_keywords_add_clicked (G_GNUC_UNUSED GtkWidget *w,
1206 DialogDocMetaData *state)
1207 {
1208 gtk_list_store_insert_with_values (state->key_store, NULL, G_MAXINT,
1209 0, "<?>", -1);
1210 dialog_doc_metadata_update_keywords_changed (state);
1211 }
1212
1213 static void
cb_dialog_doc_metadata_keywords_remove_clicked(G_GNUC_UNUSED GtkWidget * w,DialogDocMetaData * state)1214 cb_dialog_doc_metadata_keywords_remove_clicked (G_GNUC_UNUSED GtkWidget *w,
1215 DialogDocMetaData *state)
1216 {
1217 GtkTreeIter iter;
1218 GtkTreeSelection *sel = gtk_tree_view_get_selection (state->key_tree_view);
1219
1220 if (gtk_tree_selection_get_selected (sel, NULL, &iter)) {
1221 gtk_list_store_remove (state->key_store, &iter);
1222 dialog_doc_metadata_update_keywords_changed (state);
1223 }
1224 }
1225
1226 static void
cb_dialog_doc_metadata_keyword_edited(G_GNUC_UNUSED GtkCellRendererText * renderer,gchar * path,gchar * new_text,DialogDocMetaData * state)1227 cb_dialog_doc_metadata_keyword_edited (G_GNUC_UNUSED GtkCellRendererText *renderer,
1228 gchar *path,
1229 gchar *new_text,
1230 DialogDocMetaData *state)
1231 {
1232 GtkTreeIter iter;
1233 if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (state->key_store), &iter, path)) {
1234 gtk_list_store_set (state->key_store, &iter, 0, new_text, -1);
1235 dialog_doc_metadata_update_keywords_changed (state);
1236 }
1237 }
1238
1239
1240 /**
1241 * dialog_doc_metadata_init_keywords_page
1242 * @state: dialog main struct
1243 *
1244 * Initializes the widgets and signals for the 'Description' page.
1245 *
1246 **/
1247 static void
dialog_doc_metadata_init_keywords_page(DialogDocMetaData * state)1248 dialog_doc_metadata_init_keywords_page (DialogDocMetaData *state)
1249 {
1250 GtkTreeViewColumn *column;
1251 GtkCellRenderer *renderer;
1252 GtkTreeSelection *sel;
1253
1254 g_return_if_fail (state->metadata != NULL);
1255
1256 renderer = gtk_cell_renderer_text_new ();
1257 g_object_set (G_OBJECT (renderer), "editable", TRUE, NULL);
1258 column = gtk_tree_view_column_new_with_attributes (_("Keywords"),
1259 renderer,
1260 "text", 0,
1261 NULL);
1262 gtk_tree_view_insert_column (state->key_tree_view, column, -1);
1263
1264 gtk_widget_set_sensitive (GTK_WIDGET (state->key_add_button), TRUE);
1265 gtk_widget_set_sensitive (GTK_WIDGET (state->key_remove_button), FALSE);
1266
1267 sel = gtk_tree_view_get_selection (state->key_tree_view);
1268 g_signal_connect (G_OBJECT (sel),
1269 "changed",
1270 G_CALLBACK (cb_dialog_doc_metadata_keywords_sel_changed),
1271 state);
1272
1273 g_signal_connect (G_OBJECT (state->key_add_button),
1274 "clicked",
1275 G_CALLBACK (cb_dialog_doc_metadata_keywords_add_clicked),
1276 state);
1277 g_signal_connect (G_OBJECT (state->key_remove_button),
1278 "clicked",
1279 G_CALLBACK (cb_dialog_doc_metadata_keywords_remove_clicked),
1280 state);
1281 g_signal_connect (G_OBJECT (renderer),
1282 "edited",
1283 G_CALLBACK (cb_dialog_doc_metadata_keyword_edited),
1284 state);
1285
1286 cb_dialog_doc_metadata_keywords_sel_changed (sel, state);
1287 }
1288
1289 /******************************************************************************
1290 * FUNCTIONS RELATED TO 'PROPERTIES' PAGE
1291 ******************************************************************************/
1292
1293 static void
cb_dialog_doc_metadata_value_edited(G_GNUC_UNUSED GtkCellRendererText * renderer,gchar * path,gchar * new_text,DialogDocMetaData * state)1294 cb_dialog_doc_metadata_value_edited (G_GNUC_UNUSED GtkCellRendererText *renderer,
1295 gchar *path,
1296 gchar *new_text,
1297 DialogDocMetaData *state)
1298 {
1299 GtkTreeIter iter;
1300 if (gtk_tree_model_get_iter_from_string
1301 (GTK_TREE_MODEL (state->properties_store), &iter, path)) {
1302 gchar *prop_name;
1303 gchar *link_value;
1304 GType type;
1305
1306 gtk_tree_model_get (GTK_TREE_MODEL (state->properties_store),
1307 &iter,
1308 0, &prop_name,
1309 2, &link_value,
1310 4, &type,
1311 -1);
1312 dialog_doc_metadata_set_prop (state, prop_name, new_text, link_value, type);
1313 g_free (prop_name);
1314 g_free (link_value);
1315 }
1316 }
1317
1318 /**
1319 * cb_dialog_doc_metadata_add_clicked
1320 * @w: widget
1321 * @state: dialog main struct
1322 *
1323 * Adds a new "empty" property to the tree view.
1324 *
1325 **/
1326 static void
cb_dialog_doc_metadata_add_clicked(G_GNUC_UNUSED GtkWidget * w,DialogDocMetaData * state)1327 cb_dialog_doc_metadata_add_clicked (G_GNUC_UNUSED GtkWidget *w,
1328 DialogDocMetaData *state)
1329 {
1330 const gchar *name = gtk_entry_get_text (state->ppt_name);
1331 const gchar *value = gtk_entry_get_text (state->ppt_value);
1332 gchar *name_trimmed = trim_string (name);
1333 GType t = G_TYPE_INVALID; /* we need to initialize that one since
1334 gtk_tree_model_get() will only set the low bytes */
1335 GtkTreeIter filter_iter;
1336
1337 if (gtk_combo_box_get_active_iter (state->ppt_type, &filter_iter)) {
1338 GtkTreeIter child_iter;
1339 gtk_tree_model_filter_convert_iter_to_child_iter
1340 (state->type_store_filter, &child_iter, &filter_iter);
1341 gtk_tree_model_get (GTK_TREE_MODEL (state->type_store), &child_iter,
1342 1, &t, -1);
1343 } else
1344 t = dialog_doc_metadata_get_value_type_from_name (name_trimmed, G_TYPE_STRING);
1345 dialog_doc_metadata_set_prop (state, name_trimmed, value, NULL, t);
1346
1347 g_free (name_trimmed);
1348
1349 cb_dialog_doc_metadata_ppt_changed (NULL, NULL, state);
1350 gtk_label_set_text (state->warning, "");
1351 }
1352
1353 /**
1354 * dialog_doc_metadata_update_prop
1355 * @state: dialog main struct
1356 * @prop_name: property name
1357 * @prop_value: property value
1358 *
1359 * Updates a label or a entry text with the new value.
1360 *
1361 **/
1362
1363 static void
dialog_doc_metadata_update_prop(DialogDocMetaData * state,const gchar * prop_name,const gchar * prop_value,GsfDocProp * prop)1364 dialog_doc_metadata_update_prop (DialogDocMetaData *state,
1365 const gchar *prop_name,
1366 const gchar *prop_value,
1367 GsfDocProp *prop)
1368 {
1369 /* Labels */
1370 if (strcmp (prop_name, GSF_META_NAME_DATE_CREATED) == 0) {
1371 dialog_doc_metadata_set_label (state,
1372 state->created,
1373 prop_value,
1374 TRUE);
1375 }
1376 else if (strcmp (prop_name, GSF_META_NAME_DATE_MODIFIED) == 0) {
1377 dialog_doc_metadata_set_label (state,
1378 state->modified,
1379 prop_value,
1380 TRUE);
1381 }
1382 else if (strcmp (prop_name, GSF_META_NAME_SPREADSHEET_COUNT) == 0) {
1383 dialog_doc_metadata_set_label (state,
1384 state->sheets,
1385 prop_value,
1386 TRUE);
1387 }
1388 else if (strcmp (prop_name, GSF_META_NAME_CELL_COUNT) == 0) {
1389 dialog_doc_metadata_set_label (state,
1390 state->cells,
1391 prop_value,
1392 TRUE);
1393 }
1394 else if (strcmp (prop_name, GSF_META_NAME_PAGE_COUNT) == 0) {
1395 dialog_doc_metadata_set_label (state,
1396 state->pages,
1397 prop_value,
1398 TRUE);
1399 }
1400
1401 /* Entries */
1402 if (prop_value == NULL)
1403 prop_value = "";
1404
1405 if (strcmp (prop_name, GSF_META_NAME_TITLE) == 0) {
1406 gtk_entry_set_text (state->title, prop_value);
1407 }
1408
1409 else if (strcmp (prop_name, GSF_META_NAME_SUBJECT) == 0) {
1410 gtk_entry_set_text (state->subject, prop_value);
1411 }
1412
1413 else if (strcmp (prop_name, GSF_META_NAME_INITIAL_CREATOR) == 0) {
1414 gtk_entry_set_text (state->author, prop_value);
1415 }
1416
1417 else if (strcmp (prop_name, GSF_META_NAME_MANAGER) == 0) {
1418 gtk_entry_set_text (state->manager, prop_value);
1419 }
1420
1421 else if (strcmp (prop_name, GSF_META_NAME_COMPANY) == 0) {
1422 gtk_entry_set_text (state->company, prop_value);
1423 }
1424
1425 else if (strcmp (prop_name, GSF_META_NAME_CATEGORY) == 0) {
1426 gtk_entry_set_text (state->category, prop_value);
1427 }
1428
1429 else if (strcmp (prop_name, GSF_META_NAME_KEYWORDS) == 0) {
1430 dialog_doc_metadata_update_keyword_list (state, prop);
1431 }
1432
1433 else if (strcmp (prop_name, GSF_META_NAME_DESCRIPTION) == 0) {
1434 gtk_text_buffer_set_text (gtk_text_view_get_buffer (state->comments),
1435 prop_value,
1436 -1);
1437 }
1438 }
1439
1440 /**
1441 * cb_dialog_doc_metadata_remove_clicked
1442 * @remove_bt: widget
1443 * @state: dialog main struct
1444 *
1445 * Removes a property from the tree view and updates all the dialog and
1446 * the GsfDocMetaData struct.
1447 *
1448 **/
1449 static void
cb_dialog_doc_metadata_remove_clicked(GtkWidget * remove_bt,DialogDocMetaData * state)1450 cb_dialog_doc_metadata_remove_clicked (GtkWidget *remove_bt,
1451 DialogDocMetaData *state)
1452 {
1453 GtkTreeIter tree_iter;
1454 GValue *prop_name;
1455 GtkTreeSelection *sel = gtk_tree_view_get_selection (state->properties);
1456
1457 g_return_if_fail (state->metadata != NULL);
1458
1459 if (gtk_tree_selection_get_selected (sel, NULL, &tree_iter)) {
1460
1461 /* Get the property name */
1462 prop_name = g_new0 (GValue, 1);
1463 gtk_tree_model_get_value (GTK_TREE_MODEL (state->properties_store),
1464 &tree_iter,
1465 0,
1466 prop_name);
1467
1468 /* Update other pages */
1469 dialog_doc_metadata_update_prop (state,
1470 g_value_get_string (prop_name),
1471 NULL, NULL);
1472
1473 /* Remove property from GsfMetadata */
1474 cmd_change_meta_data (GNM_WBC (state->wbcg), NULL,
1475 g_slist_prepend (NULL, g_value_dup_string (prop_name)));
1476
1477 /* Remove from Tree View */
1478 gtk_tree_store_remove (state->properties_store,
1479 &tree_iter);
1480
1481 /* Free all data */
1482 g_value_unset (prop_name);
1483 g_free (prop_name);
1484 }
1485
1486 /* Set remove button insensitive */
1487 gtk_widget_set_sensitive (remove_bt, FALSE);
1488 }
1489
1490
1491 /**
1492 * cb_dialog_doc_metadata_tree_prop_selected
1493 * @combo_box: widget
1494 * @state: dialog main struct
1495 *
1496 * Update the highlited item in the 'Properties' page combo box.
1497 *
1498 **/
1499 static void
cb_dialog_doc_metadata_tree_prop_selected(GtkTreeSelection * selection,DialogDocMetaData * state)1500 cb_dialog_doc_metadata_tree_prop_selected (GtkTreeSelection *selection,
1501 DialogDocMetaData *state)
1502 {
1503 GtkTreeIter iter;
1504 gboolean selected;
1505 gchar const *text = "";
1506
1507 g_return_if_fail (state->metadata != NULL);
1508
1509 selected = gtk_tree_selection_get_selected (selection, NULL, &iter);
1510
1511 /* Set remove button sensitive */
1512 gtk_widget_set_sensitive (GTK_WIDGET (state->remove_button), selected);
1513
1514 if (selected) {
1515 GType val_type = G_TYPE_INVALID;
1516 gchar *prop_name = NULL;
1517 gtk_tree_model_get (GTK_TREE_MODEL (state->properties_store),
1518 &iter,
1519 0, &prop_name,
1520 4, &val_type,
1521 -1);
1522 switch (val_type) {
1523 case G_TYPE_STRING:
1524 text = _("Edit string value directly in above listing.");
1525 break;
1526 case G_TYPE_UINT:
1527 text = _("Edit positive integer value directly in above listing.");
1528 break;
1529 case G_TYPE_INT:
1530 text = _("Edit integer value directly in above listing.");
1531 break;
1532 case G_TYPE_FLOAT:
1533 case G_TYPE_DOUBLE:
1534 text = _("Edit decimal number value directly in above listing.");
1535 break;
1536 case G_TYPE_BOOLEAN:
1537 text = _("Edit TRUE/FALSE value directly in above listing.");
1538 break;
1539 default:
1540 if (val_type == GSF_DOCPROP_VECTOR_TYPE) {
1541 if (0 == strcmp (prop_name, "dc:keywords"))
1542 text = _("To edit, use the keywords tab.");
1543 else
1544 text = _("This property value cannot be edited.");
1545 } else if (val_type == GSF_TIMESTAMP_TYPE)
1546 text= _("Edit timestamp directly in above listing.");
1547 break;
1548 }
1549 g_free (prop_name);
1550 }
1551
1552 gtk_label_set_text (state->instruction, text);
1553 }
1554
1555 /**
1556 * dialog_doc_metadata_get_prop_val:
1557 * @state:
1558 * @prop_name: property name
1559 * @prop_value: property value
1560 *
1561 * Retrieves an arbitrary property value always as string.
1562 *
1563 **/
1564 static gchar *
dialog_doc_metadata_get_prop_val(G_GNUC_UNUSED DialogDocMetaData * state,char const * prop_name,GValue * prop_value)1565 dialog_doc_metadata_get_prop_val (G_GNUC_UNUSED DialogDocMetaData *state,
1566 char const *prop_name,
1567 GValue *prop_value)
1568 {
1569 GValue str_value = G_VALUE_INIT;
1570 gboolean ret = FALSE;
1571 GType t;
1572 char *s;
1573
1574 g_return_val_if_fail (prop_value != NULL, NULL);
1575
1576 g_value_init (&str_value, G_TYPE_STRING);
1577 t = G_VALUE_TYPE (prop_value);
1578 switch (t) {
1579 case G_TYPE_INT:
1580 case G_TYPE_UINT:
1581 case G_TYPE_STRING:
1582 ret = g_value_transform (prop_value, &str_value);
1583 break;
1584
1585 case G_TYPE_BOOLEAN: {
1586 gboolean b = g_value_get_boolean (prop_value);
1587 g_value_set_string (&str_value, go_locale_boolean_name (b));
1588 ret = TRUE;
1589 break;
1590 }
1591
1592 case G_TYPE_FLOAT:
1593 case G_TYPE_DOUBLE: {
1594 double d = (t == G_TYPE_FLOAT)
1595 ? g_value_get_float (prop_value)
1596 : g_value_get_double (prop_value);
1597 GString *res = g_string_new (NULL);
1598 go_dtoa (res, "!g", d);
1599 g_value_set_string (&str_value, res->str);
1600 g_string_free (res, TRUE);
1601 ret = TRUE;
1602 break;
1603 }
1604 }
1605
1606 if (t == GSF_TIMESTAMP_TYPE) {
1607 dialog_doc_metadata_transform_timestamp_to_str (prop_value, &str_value);
1608 ret = TRUE;
1609 } else if (t == GSF_DOCPROP_VECTOR_TYPE) {
1610 dialog_doc_metadata_transform_docprop_vect_to_str (prop_value, &str_value);
1611 ret = TRUE;
1612 }
1613
1614 if (ret == FALSE) {
1615 g_warning ("Metadata tag '%s' holds unrecognized value type.", prop_name);
1616 return NULL;
1617 }
1618
1619 s = g_value_dup_string (&str_value);
1620 g_value_unset (&str_value);
1621 return s;
1622 }
1623
1624 /**
1625 * dialog_doc_metadata_populate_tree_view
1626 * @name: property name
1627 * @prop: property stored in GsfDocMetaData
1628 * @state: dialog main struct
1629 *
1630 * Populates the tree view in 'Properties' page.
1631 *
1632 **/
1633 static void
dialog_doc_metadata_populate_tree_view(gchar * name,GsfDocProp * prop,DialogDocMetaData * state)1634 dialog_doc_metadata_populate_tree_view (gchar *name,
1635 GsfDocProp *prop,
1636 DialogDocMetaData *state)
1637 {
1638 gchar *str_value;
1639 char *link_value;
1640 GValue *value;
1641
1642 g_return_if_fail (state->metadata != NULL);
1643
1644 value = (GValue *) gsf_doc_prop_get_val (prop);
1645 /* Get the prop value as string */
1646 str_value = dialog_doc_metadata_get_prop_val (state, name, value);
1647
1648 link_value = (char *) gsf_doc_prop_get_link (prop);
1649
1650 dialog_doc_metadata_add_prop
1651 (state,
1652 gsf_doc_prop_get_name (prop),
1653 str_value == NULL ? "" : str_value,
1654 link_value == NULL ? "" : link_value,
1655 dialog_doc_metadata_get_value_type (value));
1656
1657 dialog_doc_metadata_update_prop (state, gsf_doc_prop_get_name (prop), str_value, prop);
1658
1659 g_free (str_value);
1660 }
1661
1662 static gboolean
dialog_doc_metadata_show_all_types(GtkTreeModel * model,G_GNUC_UNUSED GtkTreePath * path,GtkTreeIter * iter,G_GNUC_UNUSED gpointer data)1663 dialog_doc_metadata_show_all_types (GtkTreeModel *model,
1664 G_GNUC_UNUSED GtkTreePath *path,
1665 GtkTreeIter *iter,
1666 G_GNUC_UNUSED gpointer data)
1667 {
1668 gtk_list_store_set (GTK_LIST_STORE (model), iter, 2, TRUE, -1);
1669 return FALSE;
1670 }
1671
1672 static gboolean
dialog_doc_metadata_show_this_type(GtkTreeModel * model,G_GNUC_UNUSED GtkTreePath * path,GtkTreeIter * iter,gpointer data)1673 dialog_doc_metadata_show_this_type (GtkTreeModel *model,
1674 G_GNUC_UNUSED GtkTreePath *path,
1675 GtkTreeIter *iter,
1676 gpointer data)
1677 {
1678 GType t, type = GPOINTER_TO_INT (data);
1679
1680 gtk_tree_model_get (model, iter, 1, &t, -1);
1681 gtk_list_store_set (GTK_LIST_STORE (model), iter, 2, t == type, -1);
1682 return FALSE;
1683 }
1684
1685 static gboolean
cb_dialog_doc_metadata_ppt_changed(G_GNUC_UNUSED GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1686 cb_dialog_doc_metadata_ppt_changed (G_GNUC_UNUSED GtkEntry *entry,
1687 G_GNUC_UNUSED GdkEventFocus *event,
1688 DialogDocMetaData *state)
1689 {
1690 const gchar *name;
1691 const gchar *value;
1692 gchar *name_trimmed;
1693 gboolean enable = FALSE;
1694 gchar *str = NULL;
1695 GsfDocProp *prop = NULL;
1696 GtkTreeIter iter;
1697
1698 name = gtk_entry_get_text (state->ppt_name);
1699 value = gtk_entry_get_text (state->ppt_value);
1700 name_trimmed = trim_string (name);
1701
1702 enable = strlen (name_trimmed) > 0 && strlen (value) > 0;
1703
1704 if (enable)
1705 enable = gtk_combo_box_get_active_iter (state->ppt_type, &iter);
1706
1707 if (enable) {
1708 prop = gsf_doc_meta_data_lookup (state->metadata, name_trimmed);
1709 if (prop != NULL) {
1710 str = g_strdup_printf
1711 (_("A document property with the name \'%s\' already exists."),
1712 name_trimmed);
1713 enable = FALSE;
1714 }
1715 }
1716 g_free (name_trimmed);
1717 gtk_widget_set_sensitive (GTK_WIDGET (state->add_button), enable);
1718 gtk_label_set_text (state->warning, str ? str : "");
1719 g_free (str);
1720 return FALSE;
1721 }
1722
1723 static void
cb_dialog_doc_metadata_ppt_type_changed(G_GNUC_UNUSED GtkComboBox * widget,DialogDocMetaData * state)1724 cb_dialog_doc_metadata_ppt_type_changed (G_GNUC_UNUSED GtkComboBox *widget,
1725 DialogDocMetaData *state)
1726 {
1727 cb_dialog_doc_metadata_ppt_changed (NULL, NULL, state);
1728 }
1729
1730 static gboolean
cb_dialog_doc_metadata_ppt_name_changed(G_GNUC_UNUSED GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1731 cb_dialog_doc_metadata_ppt_name_changed (G_GNUC_UNUSED GtkEntry *entry,
1732 G_GNUC_UNUSED GdkEventFocus *event,
1733 DialogDocMetaData *state)
1734 {
1735 const gchar *name;
1736 gchar *name_trimmed;
1737 gboolean enable = FALSE;
1738 GtkTreeIter iter;
1739 gchar *str = NULL;
1740
1741 name = gtk_entry_get_text (state->ppt_name);
1742 name_trimmed = trim_string (name);
1743
1744 enable = strlen (name_trimmed) > 0;
1745
1746 if (enable) {
1747 GType t = dialog_doc_metadata_get_value_type_from_name (name_trimmed, G_TYPE_INVALID);
1748
1749 if (t == GSF_DOCPROP_VECTOR_TYPE) {
1750 str = g_strdup_printf
1751 (_("Use the keywords tab to create this property."));
1752 enable = FALSE;
1753 }
1754
1755 if (t == G_TYPE_INVALID) {
1756 g_signal_handlers_block_by_func(G_OBJECT (state->ppt_type),
1757 cb_dialog_doc_metadata_ppt_type_changed,
1758 state);
1759 gtk_tree_model_foreach (GTK_TREE_MODEL (state->type_store),
1760 dialog_doc_metadata_show_all_types, NULL);
1761 gtk_tree_model_filter_refilter (state->type_store_filter);
1762 g_signal_handlers_unblock_by_func(G_OBJECT (state->ppt_type),
1763 cb_dialog_doc_metadata_ppt_type_changed,
1764 state);
1765 } else {
1766 gtk_combo_box_set_active_iter (state->ppt_type, NULL);
1767 g_signal_handlers_block_by_func(G_OBJECT (state->ppt_type),
1768 cb_dialog_doc_metadata_ppt_type_changed,
1769 state);
1770 gtk_tree_model_foreach (GTK_TREE_MODEL (state->type_store),
1771 dialog_doc_metadata_show_this_type,
1772 GINT_TO_POINTER (t));
1773 gtk_tree_model_filter_refilter (state->type_store_filter);
1774 g_signal_handlers_unblock_by_func(G_OBJECT (state->ppt_type),
1775 cb_dialog_doc_metadata_ppt_type_changed,
1776 state);
1777 if (gtk_tree_model_get_iter_first
1778 (GTK_TREE_MODEL (state->type_store_filter), &iter))
1779 gtk_combo_box_set_active_iter (state->ppt_type, &iter);
1780 }
1781 }
1782 g_free (name_trimmed);
1783
1784 if (enable)
1785 return cb_dialog_doc_metadata_ppt_changed (NULL, NULL, state);
1786 else {
1787 gtk_widget_set_sensitive (GTK_WIDGET (state->add_button), FALSE);
1788 gtk_label_set_text (state->warning, str ? str : "");
1789 g_free (str);
1790 }
1791
1792 return FALSE;
1793 }
1794
1795
1796
1797 /**
1798 * dialog_doc_metadata_init_properties_page
1799 * @state: dialog main struct
1800 *
1801 * Initializes the widgets and signals for the 'Properties' page.
1802 *
1803 **/
1804 static void
dialog_doc_metadata_init_properties_page(DialogDocMetaData * state)1805 dialog_doc_metadata_init_properties_page (DialogDocMetaData *state)
1806 {
1807 GtkTreeSelection *sel;
1808 GtkCellRenderer *cell;
1809 guint i;
1810
1811 struct types {
1812 char const *type_name;
1813 GType type;
1814 } ppt_types[] = {
1815 {N_("String"), G_TYPE_STRING},
1816 {N_("Integer"), G_TYPE_INT},
1817 {N_("Decimal Number"), G_TYPE_FLOAT},
1818 {N_("TRUE/FALSE"), G_TYPE_BOOLEAN}
1819 };
1820
1821 g_return_if_fail (state->metadata != NULL);
1822 g_return_if_fail (state->properties != NULL);
1823
1824 /* Set Remove and Apply buttons insensitive */
1825 gtk_widget_set_sensitive (GTK_WIDGET (state->add_button), FALSE);
1826 gtk_widget_set_sensitive (GTK_WIDGET (state->remove_button), FALSE);
1827
1828 /* Intialize Combo Box */
1829 /* gtk_combo_box_set_id_column (state->ppt_type, 0); */
1830 cell = gtk_cell_renderer_text_new();
1831 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(state->ppt_type), cell, TRUE);
1832 gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(state->ppt_type), cell, "text", 0, NULL);
1833
1834 for (i = 0; i < G_N_ELEMENTS (ppt_types); i++)
1835 gtk_list_store_insert_with_values (state->type_store, NULL, G_MAXINT,
1836 0, _(ppt_types[i].type_name),
1837 1, ppt_types[i].type,
1838 2, TRUE,
1839 -1);
1840 gtk_list_store_insert_with_values (state->type_store, NULL, G_MAXINT,
1841 0, _("Date & Time"),
1842 1, GSF_TIMESTAMP_TYPE,
1843 2, TRUE,
1844 -1);
1845 gtk_tree_model_filter_set_visible_column (state->type_store_filter, 2);
1846 gtk_tree_model_filter_refilter (state->type_store_filter);
1847
1848 /* Populate Treeview */
1849 state->properties_store = gtk_tree_store_new (5,
1850 G_TYPE_STRING,
1851 G_TYPE_STRING,
1852 G_TYPE_STRING,
1853 G_TYPE_BOOLEAN,
1854 G_TYPE_GTYPE);
1855
1856 gtk_tree_view_set_model (state->properties,
1857 GTK_TREE_MODEL (state->properties_store));
1858 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (state->properties_store),
1859 0, GTK_SORT_ASCENDING);
1860 g_object_unref (state->properties_store);
1861
1862 /* Append Columns */
1863 gtk_tree_view_insert_column_with_attributes (state->properties,
1864 0, _("Name"),
1865 gtk_cell_renderer_text_new(),
1866 "text", 0,
1867 NULL);
1868
1869 cell = gtk_cell_renderer_text_new();
1870 gtk_tree_view_insert_column_with_attributes (state->properties,
1871 1, _("Value"),
1872 cell,
1873 "text", 1,
1874 "editable", 3,
1875 NULL);
1876 g_signal_connect (G_OBJECT (cell),
1877 "edited",
1878 G_CALLBACK (cb_dialog_doc_metadata_value_edited),
1879 state);
1880
1881 gtk_tree_view_insert_column_with_attributes (state->properties,
1882 2, _("Linked To"),
1883 gtk_cell_renderer_text_new(),
1884 "text", 2,
1885 NULL);
1886
1887 /* Read all metadata */
1888 gsf_doc_meta_data_foreach (state->metadata,
1889 (GHFunc) dialog_doc_metadata_populate_tree_view,
1890 state);
1891
1892 /* Set up signals */
1893 /* Tree View */
1894 sel = gtk_tree_view_get_selection (state->properties);
1895 g_signal_connect (G_OBJECT (sel),
1896 "changed",
1897 G_CALLBACK (cb_dialog_doc_metadata_tree_prop_selected),
1898 state);
1899
1900 /* Entries */
1901 g_signal_connect (G_OBJECT (state->ppt_name),
1902 "focus-out-event",
1903 G_CALLBACK (cb_dialog_doc_metadata_ppt_name_changed),
1904 state);
1905 g_signal_connect (G_OBJECT (state->ppt_value),
1906 "focus-out-event",
1907 G_CALLBACK (cb_dialog_doc_metadata_ppt_changed),
1908 state);
1909 /* ComboBox */
1910 g_signal_connect (G_OBJECT (state->ppt_type),
1911 "changed",
1912 G_CALLBACK (cb_dialog_doc_metadata_ppt_type_changed),
1913 state);
1914
1915 /* 'Add', 'Remove' and 'Apply' Button Signals */
1916 g_signal_connect (G_OBJECT (state->add_button),
1917 "clicked",
1918 G_CALLBACK (cb_dialog_doc_metadata_add_clicked),
1919 state);
1920
1921 g_signal_connect (G_OBJECT (state->remove_button),
1922 "clicked",
1923 G_CALLBACK (cb_dialog_doc_metadata_remove_clicked),
1924 state);
1925
1926 cb_dialog_doc_metadata_tree_prop_selected (sel, state);
1927 gtk_combo_box_set_active (state->ppt_type, 0);
1928 }
1929
1930 /******************************************************************************
1931 * FUNCTIONS RELATED TO 'STATISTICS' PAGE
1932 ******************************************************************************/
1933
1934 /**
1935 * dialog_doc_metadata_init_statistics_page
1936 * @state: dialog main struct
1937 *
1938 * Initializes the widgets and signals for the 'Statistics' page.
1939 *
1940 **/
1941 static void
dialog_doc_metadata_init_statistics_page(DialogDocMetaData * state)1942 dialog_doc_metadata_init_statistics_page (DialogDocMetaData *state)
1943 {
1944 g_return_if_fail (state->metadata != NULL);
1945
1946 /* Set up the labels */
1947 dialog_doc_metadata_set_label (state, state->sheets, NULL, TRUE);
1948 dialog_doc_metadata_set_label (state, state->cells, NULL, TRUE);
1949 dialog_doc_metadata_set_label (state, state->pages, NULL, TRUE);
1950 }
1951
1952 /******************************************************************************
1953 * FUNCTIONS RELATED TO 'CALCULATIONS' PAGE
1954 ******************************************************************************/
1955
1956 static gboolean
cb_dialog_doc_metadata_recalc_max_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1957 cb_dialog_doc_metadata_recalc_max_changed (GtkEntry *entry,
1958 G_GNUC_UNUSED GdkEventFocus *event,
1959 DialogDocMetaData *state)
1960 {
1961 int val;
1962 if (!entry_to_int (entry, &val, TRUE))
1963 /* FIXME: make undoable */
1964 workbook_iteration_max_number (state->wb, val);
1965 return FALSE;
1966 }
1967
1968 static gboolean
cb_dialog_doc_metadata_recalc_tolerance_changed(GtkEntry * entry,G_GNUC_UNUSED GdkEventFocus * event,DialogDocMetaData * state)1969 cb_dialog_doc_metadata_recalc_tolerance_changed (GtkEntry *entry,
1970 G_GNUC_UNUSED GdkEventFocus *event,
1971 DialogDocMetaData *state)
1972 {
1973 gnm_float val;
1974 if (!entry_to_float (entry, &val, TRUE))
1975 /* FIXME: make undoable */
1976 workbook_iteration_tolerance (state->wb, val);
1977 return FALSE;
1978 }
1979
1980 static void
cb_dialog_doc_metadata_recalc_auto_changed(GtkWidget * widget,DialogDocMetaData * state)1981 cb_dialog_doc_metadata_recalc_auto_changed (GtkWidget *widget, DialogDocMetaData *state)
1982 {
1983 /* FIXME: make undoable */
1984 workbook_set_recalcmode (state->wb, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
1985 }
1986
1987 static void
cb_dialog_doc_metadata_recalc_iteration_changed(G_GNUC_UNUSED GtkWidget * widget,DialogDocMetaData * state)1988 cb_dialog_doc_metadata_recalc_iteration_changed (G_GNUC_UNUSED GtkWidget *widget, DialogDocMetaData *state)
1989 {
1990 /* FIXME: make undoable */
1991 workbook_iteration_enabled (state->wb, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)));
1992 gtk_widget_set_sensitive (state->recalc_iteration_grid, state->wb->iteration.enabled);
1993 }
1994
1995 /**
1996 * dialog_doc_metadata_init_calculations_page
1997 * @state: dialog main struct
1998 *
1999 * Initializes the widgets and signals for the 'Calculations' page.
2000 *
2001 **/
2002 static void
dialog_doc_metadata_init_calculations_page(DialogDocMetaData * state)2003 dialog_doc_metadata_init_calculations_page (DialogDocMetaData *state)
2004 {
2005 char *buf;
2006
2007 gtk_toggle_button_set_active
2008 (GTK_TOGGLE_BUTTON ( workbook_get_recalcmode (state->wb) ? state->recalc_auto : state->recalc_manual),
2009 TRUE);
2010 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->recalc_iteration),
2011 state->wb->iteration.enabled);
2012 gtk_widget_set_sensitive (state->recalc_iteration_grid, state->wb->iteration.enabled);
2013
2014 buf = g_strdup_printf ("%d", state->wb->iteration.max_number);
2015 gtk_entry_set_text (state->recalc_max, buf);
2016 g_free (buf);
2017 buf = g_strdup_printf ("%g", state->wb->iteration.tolerance);
2018 gtk_entry_set_text (state->recalc_tolerance, buf);
2019 g_free (buf);
2020
2021 g_signal_connect (G_OBJECT (state->recalc_auto),
2022 "toggled",
2023 G_CALLBACK (cb_dialog_doc_metadata_recalc_auto_changed), state);
2024 g_signal_connect (G_OBJECT (state->recalc_iteration),
2025 "toggled",
2026 G_CALLBACK (cb_dialog_doc_metadata_recalc_iteration_changed), state);
2027 g_signal_connect (G_OBJECT (state->recalc_max),
2028 "focus-out-event",
2029 G_CALLBACK (cb_dialog_doc_metadata_recalc_max_changed),
2030 state);
2031 g_signal_connect (G_OBJECT (state->recalc_tolerance),
2032 "focus-out-event",
2033 G_CALLBACK (cb_dialog_doc_metadata_recalc_tolerance_changed),
2034 state);
2035
2036 }
2037
2038 /******************************************************************************
2039 * DIALOG INITIALIZE/FINALIZE FUNCTIONS
2040 ******************************************************************************/
2041
2042 /**
2043 * dialog_doc_metadata_set_file_permissions
2044 * @state: dialog main struct
2045 *
2046 * Writes the new file permissions if there were any changes.
2047 *
2048 **/
2049 static void
dialog_doc_metadata_set_file_permissions(DialogDocMetaData * state)2050 dialog_doc_metadata_set_file_permissions (DialogDocMetaData *state)
2051 {
2052 if (state->file_permissions != NULL &&
2053 state->permissions_changed == TRUE)
2054 go_set_file_permissions (go_doc_get_uri (state->doc),
2055 state->file_permissions);
2056 }
2057
2058 static void
dialog_doc_metadata_free(DialogDocMetaData * state)2059 dialog_doc_metadata_free (DialogDocMetaData *state)
2060 {
2061 WorkbookControl *wbc = GNM_WBC (state->wbcg);
2062
2063 wb_view_selection_desc (wb_control_view (wbc), TRUE, wbc);
2064
2065 if (state->gui != NULL) {
2066 dialog_doc_metadata_set_file_permissions (state);
2067
2068 g_object_unref (state->gui);
2069 state->gui = NULL;
2070 }
2071
2072 g_free (state->file_permissions);
2073 state->file_permissions = NULL;
2074
2075 wbcg_edit_finish (state->wbcg, WBC_EDIT_REJECT, NULL);
2076
2077 state->dialog = NULL;
2078
2079 g_free (state);
2080 }
2081
2082 static void
dialog_doc_metadata_init_widgets(DialogDocMetaData * state)2083 dialog_doc_metadata_init_widgets (DialogDocMetaData *state)
2084 {
2085 state->dialog = go_gtk_builder_get_widget (state->gui, "GOMetadataDialog");
2086
2087 state->notebook = GTK_NOTEBOOK (go_gtk_builder_get_widget (state->gui, "notebook"));
2088 state->help_button = GTK_BUTTON (go_gtk_builder_get_widget (state->gui, "help_button"));
2089 state->close_button = GTK_BUTTON (go_gtk_builder_get_widget (state->gui, "close_button"));
2090
2091 /* File Information Page */
2092 state->file_name = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "file_name"));
2093 state->location = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "location"));
2094 state->created = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "created"));
2095 state->modified = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "modified"));
2096 state->accessed = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "accessed"));
2097 state->owner = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "owner"));
2098 state->group = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "group"));
2099
2100 state->owner_read = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "owner_read"));
2101 state->owner_write = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "owner_write"));
2102
2103 state->group_read = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "group_read"));
2104 state->group_write = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "group_write"));
2105
2106 state->others_read = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "others_read"));
2107 state->others_write = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "others_write"));
2108
2109 /* Description Page */
2110 state->title = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "title"));
2111 state->subject = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "subject"));
2112 state->author = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "author"));
2113 state->manager = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "manager"));
2114 state->company = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "company"));
2115 state->category = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "category"));
2116
2117 state->comments = GTK_TEXT_VIEW (go_gtk_builder_get_widget (state->gui, "comments"));
2118
2119 /* Properties Page */
2120 state->properties = GTK_TREE_VIEW (go_gtk_builder_get_widget (state->gui, "properties"));
2121
2122 state->ppt_name = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "property-name"));
2123 state->ppt_value = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "property-value"));
2124 state->ppt_type = GTK_COMBO_BOX (go_gtk_builder_get_widget (state->gui, "type-combo"));
2125 state->type_store = GTK_LIST_STORE (gtk_builder_get_object (state->gui, "typestore"));
2126 state->type_store_filter = GTK_TREE_MODEL_FILTER (gtk_combo_box_get_model (state->ppt_type));
2127
2128 state->add_button = GTK_BUTTON (go_gtk_builder_get_widget (state->gui, "add_button"));
2129 state->remove_button = GTK_BUTTON (go_gtk_builder_get_widget (state->gui, "remove_button"));
2130 state->instruction = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "instruction-label"));
2131 state->warning = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "warning"));
2132
2133 /* Keyword Page */
2134 state->key_tree_view = GTK_TREE_VIEW (go_gtk_builder_get_widget (state->gui, "keyview"));
2135 state->key_store = GTK_LIST_STORE (gtk_tree_view_get_model (state->key_tree_view));
2136 state->key_add_button = GTK_BUTTON (go_gtk_builder_get_widget (state->gui, "key-add-button"));
2137 state->key_remove_button = GTK_BUTTON (go_gtk_builder_get_widget (state->gui, "key-remove-button"));
2138
2139 /* Statistics Page */
2140 state->sheets = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "sheets"));
2141 state->cells = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "cells"));
2142 state->pages = GTK_LABEL (go_gtk_builder_get_widget (state->gui, "pages"));
2143
2144 /* Calculations Page */
2145 state->recalc_auto = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "recalc_auto"));
2146 state->recalc_manual = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "recalc_manual"));
2147 state->recalc_iteration = GTK_CHECK_BUTTON (go_gtk_builder_get_widget (state->gui, "iteration_enabled"));
2148 state->recalc_max = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "max_iterations"));
2149 state->recalc_tolerance = GTK_ENTRY (go_gtk_builder_get_widget (state->gui, "iteration_tolerance"));
2150 state->recalc_iteration_grid = go_gtk_builder_get_widget (state->gui, "iteration-grid");
2151 }
2152
2153 static void
dialog_doc_meta_data_add_item(DialogDocMetaData * state,char const * page_name,char const * icon_name,int page,char const * parent_path)2154 dialog_doc_meta_data_add_item (DialogDocMetaData *state, char const *page_name,
2155 char const *icon_name,
2156 int page, char const* parent_path)
2157 {
2158 GtkTreeIter iter, parent;
2159 GdkPixbuf * icon = NULL;
2160
2161 if (icon_name != NULL)
2162 icon = gtk_widget_render_icon_pixbuf (state->dialog, icon_name,
2163 GTK_ICON_SIZE_MENU);
2164 if ((parent_path != NULL) && gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (state->store),
2165 &parent, parent_path))
2166 gtk_tree_store_append (state->store, &iter, &parent);
2167 else
2168 gtk_tree_store_append (state->store, &iter, NULL);
2169
2170 gtk_tree_store_set (state->store, &iter,
2171 ITEM_ICON, icon,
2172 ITEM_NAME, _(page_name),
2173 PAGE_NUMBER, page,
2174 -1);
2175 if (icon != NULL)
2176 g_object_unref (icon);
2177 }
2178
2179 typedef struct {
2180 char const *page_name;
2181 char const *icon_name;
2182 char const *parent_path;
2183 int const page;
2184 void (*page_initializer) (DialogDocMetaData *state);
2185 } page_info_t;
2186
2187 static page_info_t const page_info[] = {
2188 /* IMPORTANT: OBEY THE ORDER 0 - 3 - 2 - 1 */
2189 {N_("File"), GTK_STOCK_FILE, NULL, 0, &dialog_doc_metadata_init_file_page },
2190 {N_("Statistics"), "gnumeric-graphguru", NULL, 3 ,&dialog_doc_metadata_init_statistics_page },
2191 {N_("Properties"), GTK_STOCK_PROPERTIES, NULL, 2, &dialog_doc_metadata_init_properties_page },
2192 {N_("Description"), GTK_STOCK_ABOUT, NULL, 1, &dialog_doc_metadata_init_description_page },
2193 {N_("Keywords"), GTK_STOCK_INDEX, NULL, 5, &dialog_doc_metadata_init_keywords_page },
2194 {N_("Calculation"), GTK_STOCK_EXECUTE, NULL, 4, &dialog_doc_metadata_init_calculations_page },
2195 {NULL, NULL, NULL, -1, NULL},
2196 };
2197
2198 typedef struct {
2199 int const page;
2200 GtkTreePath *path;
2201 } page_search_t;
2202
2203 static gboolean
dialog_doc_metadata_select_page_search(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,page_search_t * pst)2204 dialog_doc_metadata_select_page_search (GtkTreeModel *model,
2205 GtkTreePath *path,
2206 GtkTreeIter *iter,
2207 page_search_t *pst)
2208 {
2209 int page;
2210 gtk_tree_model_get (model, iter, PAGE_NUMBER, &page, -1);
2211 if (page == pst->page) {
2212 pst->path = gtk_tree_path_copy (path);
2213 return TRUE;
2214 } else
2215 return FALSE;
2216 }
2217
2218 static void
dialog_doc_metadata_select_page(DialogDocMetaData * state,int page)2219 dialog_doc_metadata_select_page (DialogDocMetaData *state, int page)
2220 {
2221 page_search_t pst = {page, NULL};
2222
2223 if (page >= 0)
2224 gtk_tree_model_foreach (GTK_TREE_MODEL (state->store),
2225 (GtkTreeModelForeachFunc) dialog_doc_metadata_select_page_search,
2226 &pst);
2227
2228 if (pst.path == NULL)
2229 pst.path = gtk_tree_path_new_from_string ("0");
2230
2231 if (pst.path != NULL) {
2232 gtk_tree_view_set_cursor (state->view, pst.path, NULL, FALSE);
2233 gtk_tree_view_expand_row (state->view, pst.path, TRUE);
2234 gtk_tree_path_free (pst.path);
2235 }
2236 }
2237
2238 static void
cb_dialog_doc_metadata_selection_changed(GtkTreeSelection * selection,DialogDocMetaData * state)2239 cb_dialog_doc_metadata_selection_changed (GtkTreeSelection *selection,
2240 DialogDocMetaData *state)
2241 {
2242 GtkTreeIter iter;
2243 int page;
2244
2245 if (gtk_tree_selection_get_selected (selection, NULL, &iter)) {
2246 gtk_tree_model_get (GTK_TREE_MODEL (state->store), &iter,
2247 PAGE_NUMBER, &page,
2248 -1);
2249 gtk_notebook_set_current_page (state->notebook, page);
2250 } else {
2251 dialog_doc_metadata_select_page (state, 0);
2252 }
2253 }
2254
2255 static gboolean
dialog_doc_metadata_init(DialogDocMetaData * state,WBCGtk * wbcg)2256 dialog_doc_metadata_init (DialogDocMetaData *state,
2257 WBCGtk *wbcg)
2258 {
2259 GtkTreeViewColumn *column;
2260 GtkTreeSelection *selection;
2261 int i;
2262
2263 state->wbcg = wbcg;
2264 state->wb = wb_control_get_workbook (GNM_WBC(wbcg));
2265 state->doc = GO_DOC (state->wb);
2266 state->metadata = go_doc_get_meta_data (wb_control_get_doc (GNM_WBC (state->wbcg)));
2267
2268 g_return_val_if_fail (state->metadata != NULL, TRUE);
2269
2270 state->gui = gnm_gtk_builder_load ("res:ui/doc-meta-data.ui", NULL,
2271 GO_CMD_CONTEXT (wbcg));
2272
2273 if (state->gui == NULL)
2274 return TRUE;
2275
2276 dialog_doc_metadata_init_widgets (state);
2277
2278 state->view = GTK_TREE_VIEW(go_gtk_builder_get_widget (state->gui, "itemlist"));
2279 state->store = gtk_tree_store_new (NUM_COLUMNS,
2280 GDK_TYPE_PIXBUF,
2281 G_TYPE_STRING,
2282 G_TYPE_INT);
2283 gtk_tree_view_set_model (state->view, GTK_TREE_MODEL(state->store));
2284 g_object_unref (state->store);
2285 selection = gtk_tree_view_get_selection (state->view);
2286 gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
2287 column = gtk_tree_view_column_new_with_attributes ("",
2288 gtk_cell_renderer_pixbuf_new (),
2289 "pixbuf", ITEM_ICON,
2290 NULL);
2291 gtk_tree_view_append_column (state->view, column);
2292 column = gtk_tree_view_column_new_with_attributes ("",
2293 gtk_cell_renderer_text_new (),
2294 "text", ITEM_NAME,
2295 NULL);
2296 gtk_tree_view_append_column (state->view, column);
2297 gtk_tree_view_set_expander_column (state->view, column);
2298
2299 g_signal_connect (selection,
2300 "changed",
2301 G_CALLBACK (cb_dialog_doc_metadata_selection_changed), state);
2302
2303 for (i = 0; page_info[i].page > -1; i++) {
2304 const page_info_t *this_page = &page_info[i];
2305 this_page->page_initializer (state);
2306 dialog_doc_meta_data_add_item (state, this_page->page_name, this_page->icon_name,
2307 this_page->page, this_page->parent_path);
2308 }
2309
2310 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (state->store), ITEM_NAME, GTK_SORT_ASCENDING);
2311
2312 /* A candidate for merging into attach guru */
2313 gnm_keyed_dialog (state->wbcg,
2314 GTK_WINDOW (state->dialog),
2315 DOC_METADATA_KEY);
2316
2317
2318 go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
2319 GTK_WINDOW (state->dialog));
2320
2321 wbc_gtk_attach_guru (state->wbcg, state->dialog);
2322 g_object_set_data_full (G_OBJECT (state->dialog), "state",
2323 state, (GDestroyNotify) dialog_doc_metadata_free);
2324
2325 /* Help and Close buttons */
2326 gnm_init_help_button (GTK_WIDGET (state->help_button),
2327 GNUMERIC_HELP_LINK_METADATA);
2328
2329 g_signal_connect_swapped (G_OBJECT (state->close_button),
2330 "clicked",
2331 G_CALLBACK (gtk_widget_destroy),
2332 state->dialog);
2333
2334 gtk_widget_show_all (GTK_WIDGET (state->dialog));
2335
2336 return FALSE;
2337 }
2338
2339 /******************************************************************************
2340 * EXPORTED FUNCTIONS
2341 ******************************************************************************/
2342
2343 /**
2344 * dialog_doc_metadata_new:
2345 * @wbcg: WBCGtk
2346 *
2347 * Creates a new instance of the dialog.
2348 **/
2349 void
dialog_doc_metadata_new(WBCGtk * wbcg,int page)2350 dialog_doc_metadata_new (WBCGtk *wbcg, int page)
2351 {
2352 DialogDocMetaData *state;
2353
2354 g_return_if_fail (wbcg != NULL);
2355
2356 /* Only one guru per workbook. */
2357 if (wbc_gtk_get_guru (wbcg))
2358 return;
2359
2360 /* Only pop up one copy per workbook */
2361 if (gnm_dialog_raise_if_exists (wbcg, DOC_METADATA_KEY))
2362 return;
2363
2364 state = g_new0 (DialogDocMetaData, 1);
2365
2366 if (dialog_doc_metadata_init (state, wbcg)) {
2367 go_gtk_notice_dialog (wbcg_toplevel (wbcg),
2368 GTK_MESSAGE_ERROR,
2369 _("Could not create the Properties dialog."));
2370
2371 g_free (state);
2372 return;
2373 }
2374
2375 dialog_doc_metadata_select_page (state, page);
2376 }
2377