1 /*
2 * Copyright (C) 2011 Murray Cumming <murrayc@murrayc.com>
3 * Copyright (C) 2011 Vivien Malerba <malerba@gnome-db.org>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include <glib/gi18n-lib.h>
21 #include <string.h>
22 #include <gtk/gtk.h>
23 #include "entry-properties.h"
24 #include "marshal.h"
25 #include <time.h>
26 #include <libgda-ui/libgda-ui.h>
27 #include "../text-search.h"
28
29 struct _EntryPropertiesPrivate {
30 BrowserConnection *bcnc;
31
32 GtkTextView *view;
33 GtkTextBuffer *text;
34 gboolean hovering_over_link;
35
36 GtkWidget *text_search;
37
38 /* coordinates of mouse */
39 gint bx;
40 gint by;
41 };
42
43 static void entry_properties_class_init (EntryPropertiesClass *klass);
44 static void entry_properties_init (EntryProperties *eprop, EntryPropertiesClass *klass);
45 static void entry_properties_dispose (GObject *object);
46
47 static GObjectClass *parent_class = NULL;
48
49 /* signals */
50 enum {
51 OPEN_DN,
52 OPEN_CLASS,
53 LAST_SIGNAL
54 };
55
56 gint entry_properties_signals [LAST_SIGNAL] = { 0, 0 };
57
58 /*
59 * EntryProperties class implementation
60 */
61
62 static void
entry_properties_class_init(EntryPropertiesClass * klass)63 entry_properties_class_init (EntryPropertiesClass *klass)
64 {
65 GObjectClass *object_class = G_OBJECT_CLASS (klass);
66 parent_class = g_type_class_peek_parent (klass);
67
68 entry_properties_signals [OPEN_DN] =
69 g_signal_new ("open-dn",
70 G_TYPE_FROM_CLASS (object_class),
71 G_SIGNAL_RUN_FIRST,
72 G_STRUCT_OFFSET (EntryPropertiesClass, open_dn),
73 NULL, NULL,
74 _ldap_marshal_VOID__STRING, G_TYPE_NONE,
75 1, G_TYPE_STRING);
76 entry_properties_signals [OPEN_CLASS] =
77 g_signal_new ("open-class",
78 G_TYPE_FROM_CLASS (object_class),
79 G_SIGNAL_RUN_FIRST,
80 G_STRUCT_OFFSET (EntryPropertiesClass, open_class),
81 NULL, NULL,
82 _ldap_marshal_VOID__STRING, G_TYPE_NONE,
83 1, G_TYPE_STRING);
84 klass->open_dn = NULL;
85 klass->open_class = NULL;
86
87 object_class->dispose = entry_properties_dispose;
88 }
89
90
91 static void
entry_properties_init(EntryProperties * eprop,G_GNUC_UNUSED EntryPropertiesClass * klass)92 entry_properties_init (EntryProperties *eprop, G_GNUC_UNUSED EntryPropertiesClass *klass)
93 {
94 eprop->priv = g_new0 (EntryPropertiesPrivate, 1);
95 eprop->priv->hovering_over_link = FALSE;
96
97 gtk_orientable_set_orientation (GTK_ORIENTABLE (eprop), GTK_ORIENTATION_VERTICAL);
98 }
99
100 static void
entry_properties_dispose(GObject * object)101 entry_properties_dispose (GObject *object)
102 {
103 EntryProperties *eprop = (EntryProperties *) object;
104
105 /* free memory */
106 if (eprop->priv) {
107 if (eprop->priv->bcnc) {
108 g_object_unref (eprop->priv->bcnc);
109 }
110
111 g_free (eprop->priv);
112 eprop->priv = NULL;
113 }
114
115 parent_class->dispose (object);
116 }
117
118 GType
entry_properties_get_type(void)119 entry_properties_get_type (void)
120 {
121 static GType type = 0;
122
123 if (G_UNLIKELY (type == 0)) {
124 static const GTypeInfo columns = {
125 sizeof (EntryPropertiesClass),
126 (GBaseInitFunc) NULL,
127 (GBaseFinalizeFunc) NULL,
128 (GClassInitFunc) entry_properties_class_init,
129 NULL,
130 NULL,
131 sizeof (EntryProperties),
132 0,
133 (GInstanceInitFunc) entry_properties_init,
134 0
135 };
136 type = g_type_register_static (GTK_TYPE_BOX, "EntryProperties", &columns, 0);
137 }
138 return type;
139 }
140
141 static gboolean key_press_event (GtkWidget *text_view, GdkEventKey *event, EntryProperties *eprop);
142 static gboolean event_after (GtkWidget *text_view, GdkEvent *ev, EntryProperties *eprop);
143 static gboolean motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, EntryProperties *eprop);
144 static gboolean visibility_notify_event (GtkWidget *text_view, GdkEventVisibility *event, EntryProperties *eprop);
145 static void populate_popup_cb (GtkWidget *text_view, GtkMenu *menu, EntryProperties *eprop);
146
147 static void show_search_bar (EntryProperties *eprop);
148
149 /**
150 * entry_properties_new:
151 *
152 * Returns: a new #GtkWidget
153 */
154 GtkWidget *
entry_properties_new(BrowserConnection * bcnc)155 entry_properties_new (BrowserConnection *bcnc)
156 {
157 EntryProperties *eprop;
158 g_return_val_if_fail (BROWSER_IS_CONNECTION (bcnc), NULL);
159
160 eprop = ENTRY_PROPERTIES (g_object_new (ENTRY_PROPERTIES_TYPE, NULL));
161 eprop->priv->bcnc = g_object_ref (bcnc);
162
163 GtkWidget *sw;
164 sw = gtk_scrolled_window_new (NULL, NULL);
165 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
166 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
167 GTK_POLICY_AUTOMATIC,
168 GTK_POLICY_AUTOMATIC);
169 gtk_box_pack_start (GTK_BOX (eprop), sw, TRUE, TRUE, 0);
170
171 GtkWidget *textview;
172 textview = gtk_text_view_new ();
173 gtk_container_add (GTK_CONTAINER (sw), textview);
174 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (textview), 5);
175 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (textview), 5);
176 gtk_text_view_set_editable (GTK_TEXT_VIEW (textview), FALSE);
177 gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (textview), FALSE);
178 eprop->priv->text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview));
179 eprop->priv->view = GTK_TEXT_VIEW (textview);
180 gtk_widget_show_all (sw);
181
182 gtk_text_buffer_create_tag (eprop->priv->text, "section",
183 "weight", PANGO_WEIGHT_BOLD,
184 "foreground", "blue", NULL);
185
186 gtk_text_buffer_create_tag (eprop->priv->text, "error",
187 "foreground", "red", NULL);
188
189 gtk_text_buffer_create_tag (eprop->priv->text, "data",
190 "left-margin", 20, NULL);
191
192 gtk_text_buffer_create_tag (eprop->priv->text, "convdata",
193 "style", PANGO_STYLE_ITALIC,
194 "background", "lightgray",
195 "left-margin", 20, NULL);
196
197 gtk_text_buffer_create_tag (eprop->priv->text, "starter",
198 "indent", -10,
199 "left-margin", 20, NULL);
200
201 g_signal_connect (textview, "key-press-event",
202 G_CALLBACK (key_press_event), eprop);
203 g_signal_connect (textview, "event-after",
204 G_CALLBACK (event_after), eprop);
205 g_signal_connect (textview, "motion-notify-event",
206 G_CALLBACK (motion_notify_event), eprop);
207 g_signal_connect (textview, "visibility-notify-event",
208 G_CALLBACK (visibility_notify_event), eprop);
209 g_signal_connect (textview, "populate-popup",
210 G_CALLBACK (populate_popup_cb), eprop);
211
212 entry_properties_set_dn (eprop, NULL);
213
214 return (GtkWidget*) eprop;
215 }
216
217 static void
data_save_cb(GtkWidget * mitem,EntryProperties * eprop)218 data_save_cb (GtkWidget *mitem, EntryProperties *eprop)
219 {
220 GtkWidget *dialog;
221
222 dialog = gtk_file_chooser_dialog_new (_("Select the file to save data to"),
223 (GtkWindow*) gtk_widget_get_toplevel (GTK_WIDGET (eprop)),
224 GTK_FILE_CHOOSER_ACTION_SAVE,
225 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
226 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
227 NULL);
228 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog),
229 gdaui_get_default_path ());
230 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
231 char *filename;
232 GValue *binvalue;
233 GError *lerror = NULL;
234 const GdaBinary *bin = NULL;
235
236 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
237 binvalue = g_object_get_data (G_OBJECT (mitem), "binvalue");
238 if (binvalue)
239 bin = gda_value_get_binary (binvalue);
240 if (!bin || !g_file_set_contents (filename, (gchar*) bin->data,
241 bin->binary_length, &lerror)) {
242 browser_show_error ((GtkWindow*) gtk_widget_get_toplevel (GTK_WIDGET (eprop)),
243 _("Could not save data: %s"),
244 lerror && lerror->message ? lerror->message : _("No detail"));
245 g_clear_error (&lerror);
246 }
247 gdaui_set_default_path (gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dialog)));
248 g_free (filename);
249 }
250 gtk_widget_destroy (dialog);
251 }
252
253 static void
populate_popup_cb(G_GNUC_UNUSED GtkWidget * text_view,GtkMenu * menu,EntryProperties * eprop)254 populate_popup_cb (G_GNUC_UNUSED GtkWidget *text_view, GtkMenu *menu, EntryProperties *eprop)
255 {
256 GtkTextIter iter;
257 gtk_text_view_get_iter_at_position (eprop->priv->view, &iter, NULL,
258 eprop->priv->bx, eprop->priv->by);
259
260 GSList *tags = NULL, *tagp = NULL;
261
262 tags = gtk_text_iter_get_tags (&iter);
263 for (tagp = tags; tagp != NULL; tagp = tagp->next) {
264 GtkTextTag *tag = tagp->data;
265 GValue *binvalue;
266
267 binvalue = g_object_get_data (G_OBJECT (tag), "binvalue");
268 if (binvalue) {
269 GtkWidget *item;
270
271 item = gtk_separator_menu_item_new ();
272 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
273 gtk_widget_show (item);
274
275 item = gtk_menu_item_new_with_label (_("Save"));
276 gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), item);
277 g_signal_connect (G_OBJECT (item), "activate",
278 G_CALLBACK (data_save_cb), eprop);
279 g_object_set_data (G_OBJECT (item), "binvalue", binvalue);
280 gtk_widget_show (item);
281
282 break;
283 }
284 }
285
286 if (tags)
287 g_slist_free (tags);
288 }
289
290 static GdkCursor *hand_cursor = NULL;
291 static GdkCursor *regular_cursor = NULL;
292
293 /* Looks at all tags covering the position (x, y) in the text view,
294 * and if one of them is a link, change the cursor to the "hands" cursor
295 * typically used by web browsers.
296 */
297 static void
set_cursor_if_appropriate(GtkTextView * text_view,gint x,gint y,EntryProperties * eprop)298 set_cursor_if_appropriate (GtkTextView *text_view, gint x, gint y, EntryProperties *eprop)
299 {
300 GSList *tags = NULL, *tagp = NULL;
301 GtkTextIter iter;
302 gboolean hovering = FALSE;
303
304 gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
305
306 tags = gtk_text_iter_get_tags (&iter);
307 for (tagp = tags; tagp != NULL; tagp = tagp->next) {
308 GtkTextTag *tag = tagp->data;
309
310 if (g_object_get_data (G_OBJECT (tag), "dn") ||
311 g_object_get_data (G_OBJECT (tag), "class")) {
312 hovering = TRUE;
313 break;
314 }
315 }
316
317 if (hovering != eprop->priv->hovering_over_link) {
318 eprop->priv->hovering_over_link = hovering;
319
320 if (eprop->priv->hovering_over_link) {
321 if (! hand_cursor)
322 hand_cursor = gdk_cursor_new (GDK_HAND2);
323 gdk_window_set_cursor (gtk_text_view_get_window (text_view,
324 GTK_TEXT_WINDOW_TEXT),
325 hand_cursor);
326 }
327 else {
328 if (!regular_cursor)
329 regular_cursor = gdk_cursor_new (GDK_XTERM);
330 gdk_window_set_cursor (gtk_text_view_get_window (text_view,
331 GTK_TEXT_WINDOW_TEXT),
332 regular_cursor);
333 }
334 }
335
336 if (tags)
337 g_slist_free (tags);
338 }
339
340 /*
341 * Also update the cursor image if the window becomes visible
342 * (e.g. when a window covering it got iconified).
343 */
344 static gboolean
visibility_notify_event(GtkWidget * text_view,G_GNUC_UNUSED GdkEventVisibility * event,EntryProperties * eprop)345 visibility_notify_event (GtkWidget *text_view, G_GNUC_UNUSED GdkEventVisibility *event,
346 EntryProperties *eprop)
347 {
348 gint wx, wy, bx, by;
349 GdkDeviceManager *manager;
350 GdkDevice *pointer;
351
352 manager = gdk_display_get_device_manager (gtk_widget_get_display (text_view));
353 pointer = gdk_device_manager_get_client_pointer (manager);
354 gdk_window_get_device_position (gtk_widget_get_window (text_view), pointer, &wx, &wy, NULL);
355 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
356 GTK_TEXT_WINDOW_WIDGET,
357 wx, wy, &bx, &by);
358
359 set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), bx, by, eprop);
360
361 return FALSE;
362 }
363
364 /*
365 * Update the cursor image if the pointer moved.
366 */
367 static gboolean
motion_notify_event(GtkWidget * text_view,GdkEventMotion * event,EntryProperties * eprop)368 motion_notify_event (GtkWidget *text_view, GdkEventMotion *event, EntryProperties *eprop)
369 {
370 gint x, y;
371
372 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
373 GTK_TEXT_WINDOW_WIDGET,
374 event->x, event->y, &x, &y);
375
376 set_cursor_if_appropriate (GTK_TEXT_VIEW (text_view), x, y, eprop);
377
378 /* store coordinates */
379 eprop->priv->bx = x;
380 eprop->priv->by = y;
381
382 return FALSE;
383 }
384
385 /* Looks at all tags covering the position of iter in the text view,
386 * and if one of them is a link, follow it by showing the page identified
387 * by the data attached to it.
388 */
389 static void
follow_if_link(G_GNUC_UNUSED GtkWidget * text_view,GtkTextIter * iter,EntryProperties * eprop)390 follow_if_link (G_GNUC_UNUSED GtkWidget *text_view, GtkTextIter *iter, EntryProperties *eprop)
391 {
392 GSList *tags = NULL, *tagp = NULL;
393
394 tags = gtk_text_iter_get_tags (iter);
395 for (tagp = tags; tagp != NULL; tagp = tagp->next) {
396 GtkTextTag *tag = tagp->data;
397 const gchar *dn;
398
399 dn = g_object_get_data (G_OBJECT (tag), "dn");
400 if (dn)
401 g_signal_emit (eprop, entry_properties_signals [OPEN_DN], 0, dn);
402 dn = g_object_get_data (G_OBJECT (tag), "class");
403 if (dn)
404 g_signal_emit (eprop, entry_properties_signals [OPEN_CLASS], 0, dn);
405 }
406
407 if (tags)
408 g_slist_free (tags);
409 }
410
411
412 /*
413 * Links can also be activated by clicking.
414 */
415 static gboolean
event_after(GtkWidget * text_view,GdkEvent * ev,EntryProperties * eprop)416 event_after (GtkWidget *text_view, GdkEvent *ev, EntryProperties *eprop)
417 {
418 GtkTextIter start, end, iter;
419 GtkTextBuffer *buffer;
420 GdkEventButton *event;
421 gint x, y;
422
423 if (ev->type != GDK_BUTTON_RELEASE)
424 return FALSE;
425
426 event = (GdkEventButton *)ev;
427
428 if (event->button != 1)
429 return FALSE;
430
431 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
432
433 /* we shouldn't follow a link if the user has selected something */
434 gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
435 if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
436 return FALSE;
437
438 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
439 GTK_TEXT_WINDOW_WIDGET,
440 event->x, event->y, &x, &y);
441
442 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
443
444 follow_if_link (text_view, &iter, eprop);
445
446 return FALSE;
447 }
448
449 /*
450 * Links can be activated by pressing Enter.
451 */
452 static gboolean
key_press_event(GtkWidget * text_view,GdkEventKey * event,EntryProperties * eprop)453 key_press_event (GtkWidget *text_view, GdkEventKey *event, EntryProperties *eprop)
454 {
455 GtkTextIter iter;
456 GtkTextBuffer *buffer;
457
458 switch (event->keyval) {
459 case GDK_KEY_Return:
460 case GDK_KEY_KP_Enter:
461 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
462 gtk_text_buffer_get_iter_at_mark (buffer, &iter,
463 gtk_text_buffer_get_insert (buffer));
464 follow_if_link (text_view, &iter, eprop);
465 break;
466 case GDK_KEY_F:
467 case GDK_KEY_f:
468 if (event->state & GDK_CONTROL_MASK) {
469 show_search_bar (eprop);
470 return TRUE;
471 }
472 break;
473 case GDK_KEY_slash:
474 show_search_bar (eprop);
475 return TRUE;
476 break;
477 default:
478 break;
479 }
480 return FALSE;
481 }
482
483 static GdkPixbuf *
data_to_pixbuf(const GValue * cvalue)484 data_to_pixbuf (const GValue *cvalue)
485 {
486 GdkPixbuf *retpixbuf = NULL;
487
488 if (G_VALUE_TYPE (cvalue) == GDA_TYPE_BINARY) {
489 const GdaBinary *bin;
490 GdkPixbufLoader *loader;
491
492 bin = gda_value_get_binary (cvalue);
493 if (!bin->data)
494 goto out;
495
496 loader = gdk_pixbuf_loader_new ();
497 if (gdk_pixbuf_loader_write (loader, bin->data, bin->binary_length, NULL)) {
498 if (gdk_pixbuf_loader_close (loader, NULL)) {
499 retpixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
500 g_object_ref (retpixbuf);
501 }
502 else
503 gdk_pixbuf_loader_close (loader, NULL);
504 }
505 else
506 gdk_pixbuf_loader_close (loader, NULL);
507 g_object_unref (loader);
508 }
509
510 out:
511 return retpixbuf;
512 }
513
514 static gchar *
unix_shadow_to_string(const gchar * value,const gchar * attname)515 unix_shadow_to_string (const gchar *value, const gchar *attname)
516 {
517 /* value is the number of days since 1970-01-01 */
518 gint64 i64;
519 gchar *endptr [1];
520
521 if (!value || !*value)
522 return NULL;
523
524 i64 = g_ascii_strtoll (value, endptr, 10);
525 if (**endptr != '\0')
526 return NULL;
527
528 if ((i64 == -1) &&
529 (!strcmp (attname, "shadowInactive") ||
530 !strcmp (attname, "shadowMin") ||
531 !strcmp (attname, "shadowExpire")))
532 return g_strdup (_("Non activated"));
533 else if ((i64 == 99999) && !strcmp (attname, "shadowMax"))
534 return g_strdup ("Always valid");
535 if ((i64 >= G_MAXUINT) || (i64 < 0))
536 return NULL;
537
538 if (!strcmp (attname, "shadowMax") ||
539 !strcmp (attname, "shadowMin") ||
540 !strcmp (attname, "shadowInactive"))
541 return NULL;
542
543 GDate *date;
544 date = g_date_new_dmy (1, 1, 1970);
545 g_date_add_days (date, (guint) i64);
546 if (! g_date_valid (date)) {
547 g_date_free (date);
548 return NULL;
549 }
550
551 GdaDataHandler *dh;
552 GValue tvalue;
553 gchar *str;
554
555 memset (&tvalue, 0, sizeof (GValue));
556 g_value_init (&tvalue, G_TYPE_DATE);
557 g_value_take_boxed (&tvalue, date);
558 dh = gda_data_handler_get_default (G_TYPE_DATE);
559 str = gda_data_handler_get_str_from_value (dh, &tvalue);
560 g_value_reset (&tvalue);
561
562 return str;
563 }
564
565 static gchar *
ad_1601_timestamp_to_string(const gchar * value,const gchar * attname)566 ad_1601_timestamp_to_string (const gchar *value, const gchar *attname)
567 {
568 /* value is the number of 100 nanoseconds since 1601-01-01 UTC */
569 gint64 i64;
570 gchar *endptr [1];
571
572 if (!value || !*value)
573 return NULL;
574
575 i64 = g_ascii_strtoll (value, endptr, 10);
576 if (**endptr != '\0')
577 return NULL;
578
579 if (i64 == 0x7FFFFFFFFFFFFFFF)
580 return g_strdup (_("Never"));
581
582 if (i64 == 0 && attname) {
583 if (!strcmp (attname, "accountExpires"))
584 return g_strdup (_("Never"));
585 else
586 return g_strdup (_("Unknown"));
587 }
588
589 i64 = (i64 / (guint64) 10000000);
590 if (i64 < (gint64) 11644473600)
591 return NULL;
592 i64 = i64 - (guint64) 11644473600;
593 if (i64 >= G_MAXINT)
594 return NULL;
595
596 GdaDataHandler *dh;
597 struct tm *stm;
598 GValue tvalue;
599 GdaTimestamp ts;
600 time_t nsec = (time_t) i64;
601 gchar *str;
602 #ifdef HAVE_LOCALTIME_R
603 struct tm tmpstm;
604 stm = localtime_r (&nsec, &tmpstm);
605 #elif HAVE_LOCALTIME_S
606 struct tm tmpstm;
607 if (localtime_s (&tmpstm, &nsec) == 0)
608 stm = &tmpstm;
609 else
610 stm = NULL;
611 #else
612 stm = localtime (&nsec);
613 #endif
614
615 if (!stm)
616 return NULL;
617
618 memset (&ts, 0, sizeof (GdaTimestamp));
619 ts.year = stm->tm_year + 1900;
620 ts.month = stm->tm_mon + 1;
621 ts.day = stm->tm_mday;
622 ts.hour = stm->tm_hour;
623 ts.minute = stm->tm_min;
624 ts.second = stm->tm_sec;
625 ts.timezone = GDA_TIMEZONE_INVALID;
626 memset (&tvalue, 0, sizeof (GValue));
627 gda_value_set_timestamp (&tvalue, &ts);
628 dh = gda_data_handler_get_default (GDA_TYPE_TIMESTAMP);
629 str = gda_data_handler_get_str_from_value (dh, &tvalue);
630 g_value_reset (&tvalue);
631
632 return str;
633 }
634
635 typedef struct {
636 guint mask;
637 gchar *descr;
638 } ADUACData;
639
640 ADUACData uac_data[] = {
641 {0x00000001, "Logon script is executed"},
642 {0x00000002, "Account disabled"},
643 {0x00000008, "Home directory required"},
644 {0x00000010, "Account locked out"},
645 {0x00000020, "No password required"},
646 {0x00000040, "User cannot change password"},
647 {0x00000080, "User can send an encrypted password"},
648 {0x00000100, "Duplicate account (local user account)"},
649 {0x00000200, "Default account type"},
650 {0x00000800, "Permit to trust account for a system domain that trusts other domains"},
651 {0x00001000, "Account for a computer"},
652 {0x00002000, "Account for a system backup domain controller"},
653 {0x00010000, "Account never expires"},
654 {0x00020000, "Majority Node Set (MNS) logon account"},
655 {0x00040000, "User must log on using a smart card"},
656 {0x00080000, "Service account for trusted for Kerberos delegation"},
657 {0x00100000, "Security context not delegated"},
658 {0x00200000, "Only Data Encryption Standard (DES) encryption for keys"},
659 {0x00400000, "Kerberos pre-authentication not required"},
660 {0x00800000, "User password expired"},
661 {0x01000000, "Account enabled for delegation"}
662 };
663
664 static gchar *
ad_1601_uac_to_string(const gchar * value)665 ad_1601_uac_to_string (const gchar *value)
666 {
667 gint64 i64;
668 gchar *endptr [1];
669
670 if (!value || !*value)
671 return NULL;
672
673 i64 = g_ascii_strtoll (value, endptr, 10);
674 if (**endptr != '\0')
675 return NULL;
676 if (i64 < 0)
677 return NULL;
678 if (i64 > G_MAXUINT32)
679 return NULL;
680
681 GString *string = NULL;
682 guint i;
683 guint32 v;
684 v = (guint32) i64;
685 for (i = 0; i < sizeof (uac_data) / sizeof (ADUACData); i++) {
686 ADUACData *d;
687 d = & (uac_data [i]);
688 if (v & d->mask) {
689 if (string)
690 g_string_append (string, " / ");
691 else
692 string = g_string_new ("");
693 g_string_append (string, d->descr);
694 }
695 }
696 if (string)
697 return g_string_free (string, FALSE);
698 else
699 return NULL;
700 }
701
702 static gchar *
ad_sam_account_type_to_string(const gchar * value)703 ad_sam_account_type_to_string (const gchar *value)
704 {
705 gint64 i64;
706 gchar *endptr [1];
707
708 if (!value || !*value)
709 return NULL;
710
711 i64 = g_ascii_strtoll (value, endptr, 10);
712 if (**endptr != '\0')
713 return NULL;
714 if (i64 < 0)
715 return NULL;
716
717 switch (i64) {
718 case 0x0:
719 return g_strdup ("SAM_DOMAIN_OBJECT");
720 case 0x10000000:
721 return g_strdup ("SAM_GROUP_OBJECT");
722 case 0x10000001:
723 return g_strdup ("SAM_NON_SECURITY_GROUP_OBJECT");
724 case 0x20000000:
725 return g_strdup ("SAM_ALIAS_OBJECT");
726 case 0x20000001:
727 return g_strdup ("SAM_NON_SECURITY_ALIAS_OBJECT");
728 case 0x30000000:
729 return g_strdup ("SAM_NORMAL_USER_ACCOUNT");
730 case 0x30000001:
731 return g_strdup ("SAM_MACHINE_ACCOUNT");
732 case 0x30000002:
733 return g_strdup ("SAM_TRUST_ACCOUNT");
734 case 0x40000000:
735 return g_strdup ("SAM_APP_BASIC_GROUP");
736 case 0x40000001:
737 return g_strdup ("SAM_APP_QUERY_GROUP");
738 case 0x7fffffff:
739 return g_strdup ("SAM_ACCOUNT_TYPE_MAX");
740 default:
741 return NULL;
742 }
743 }
744
745 static void
info_fetch_cb(BrowserConnection * bcnc,gpointer out_result,EntryProperties * eprop,G_GNUC_UNUSED GError * error)746 info_fetch_cb (BrowserConnection *bcnc, gpointer out_result, EntryProperties *eprop, G_GNUC_UNUSED GError *error)
747 {
748 if (out_result) {
749 GtkTextBuffer *tbuffer;
750 GtkTextIter start, end;
751
752 tbuffer = eprop->priv->text;
753 gtk_text_buffer_get_start_iter (tbuffer, &start);
754 gtk_text_buffer_get_end_iter (tbuffer, &end);
755 gtk_text_buffer_delete (tbuffer, &start, &end);
756
757 GdaLdapEntry *entry = (GdaLdapEntry*) out_result;
758 guint i;
759 GtkTextIter current;
760
761 gtk_text_buffer_get_start_iter (tbuffer, ¤t);
762
763 /* DN */
764 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t, _("Distinguished Name:"), -1,
765 "section", NULL);
766 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
767 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t, " ", -1, "starter", NULL);
768 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t, entry->dn, -1,
769 "data", NULL);
770 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
771
772 /* other attributes */
773 const gchar *basedn;
774 GdaDataHandler *ts_dh = NULL;
775 basedn = browser_connection_ldap_get_base_dn (bcnc);
776
777 for (i = 0; i < entry->nb_attributes; i++) {
778 GdaLdapAttribute *attr;
779 gchar *tmp;
780 attr = entry->attributes [i];
781 tmp = g_strdup_printf ("%s:", attr->attr_name);
782 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t, tmp, -1, "section", NULL);
783 g_free (tmp);
784 gtk_text_buffer_insert (tbuffer, ¤t, "\n", -1);
785
786 guint j;
787 for (j = 0; j < attr->nb_values; j++) {
788 const GValue *cvalue;
789 cvalue = attr->values [j];
790
791 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t, " ", -1,
792 "starter", NULL);
793
794 if (G_VALUE_TYPE (cvalue) == GDA_TYPE_BINARY) {
795 GValue *copyvalue;
796 GtkTextTagTable *table;
797 GtkTextTag *tag;
798 GtkTextMark *mark;
799
800 copyvalue = gda_value_copy (cvalue);
801 table = gtk_text_buffer_get_tag_table (tbuffer);
802 tag = gtk_text_tag_new (NULL);
803 gtk_text_tag_table_add (table, tag);
804 g_object_set_data_full ((GObject*) tag, "binvalue",
805 copyvalue, (GDestroyNotify) gda_value_free);
806 g_object_unref ((GObject*) tag);
807
808 mark = gtk_text_buffer_create_mark (tbuffer, NULL, ¤t, TRUE);
809
810 GdkPixbuf *pixbuf;
811 pixbuf = data_to_pixbuf (cvalue);
812 if (pixbuf) {
813 gtk_text_buffer_insert_pixbuf (tbuffer, ¤t, pixbuf);
814 g_object_unref (pixbuf);
815 }
816 else {
817 GdaDataHandler *dh;
818 dh = gda_data_handler_get_default (G_VALUE_TYPE (cvalue));
819 if (dh)
820 tmp = gda_data_handler_get_str_from_value (dh, cvalue);
821 else
822 tmp = gda_value_stringify (cvalue);
823 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
824 tmp, -1,
825 "data", NULL);
826 g_free (tmp);
827 }
828 GtkTextIter before;
829 gtk_text_buffer_get_iter_at_mark (tbuffer, &before, mark);
830 gtk_text_buffer_apply_tag (tbuffer, tag, &before, ¤t);
831 gtk_text_buffer_delete_mark (tbuffer, mark);
832
833 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
834 "\n", 1,
835 "data", NULL);
836 }
837 else {
838 GdaDataHandler *dh;
839 dh = gda_data_handler_get_default (G_VALUE_TYPE (cvalue));
840 if (dh)
841 tmp = gda_data_handler_get_str_from_value (dh, cvalue);
842 else
843 tmp = gda_value_stringify (cvalue);
844 if (tmp) {
845 if (*tmp &&
846 ((basedn && g_str_has_suffix (tmp, basedn)) || !basedn) &&
847 gda_ldap_is_dn (tmp)) {
848 /* we have a DN */
849 GtkTextTag *tag;
850 tag = gtk_text_buffer_create_tag (tbuffer, NULL,
851 "foreground", "blue",
852 "weight", PANGO_WEIGHT_NORMAL,
853 "underline", PANGO_UNDERLINE_SINGLE,
854 NULL);
855 g_object_set_data_full (G_OBJECT (tag), "dn",
856 g_strdup (tmp), g_free);
857 gtk_text_buffer_insert_with_tags (tbuffer, ¤t,
858 tmp, -1,
859 tag, NULL);
860 }
861 else if (attr->attr_name &&
862 !g_ascii_strcasecmp (attr->attr_name, "objectClass")) {
863 GtkTextTag *tag;
864 tag = gtk_text_buffer_create_tag (tbuffer, NULL,
865 "foreground", "blue",
866 "weight", PANGO_WEIGHT_NORMAL,
867 "underline", PANGO_UNDERLINE_SINGLE,
868 NULL);
869 g_object_set_data_full (G_OBJECT (tag), "class",
870 g_strdup (tmp), g_free);
871 gtk_text_buffer_insert_with_tags (tbuffer, ¤t,
872 tmp, -1,
873 tag, NULL);
874 }
875 else
876 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t, tmp, -1,
877 "data", NULL);
878
879 gchar *extrainfo = NULL;
880 if (!strncmp (attr->attr_name, "shadow", 6) &&
881 (!strcmp (attr->attr_name, "shadowLastChange") ||
882 !strcmp (attr->attr_name, "shadowMax") ||
883 !strcmp (attr->attr_name, "shadowMin") ||
884 !strcmp (attr->attr_name, "shadowInactive") ||
885 !strcmp (attr->attr_name, "shadowExpire")))
886 extrainfo = unix_shadow_to_string (tmp, attr->attr_name);
887 else if (!strcmp (attr->attr_name, "badPasswordTime") ||
888 !strcmp (attr->attr_name, "lastLogon") ||
889 !strcmp (attr->attr_name, "pwdLastSet") ||
890 !strcmp (attr->attr_name, "accountExpires") ||
891 !strcmp (attr->attr_name, "lockoutTime") ||
892 !strcmp (attr->attr_name, "lastLogonTimestamp"))
893 extrainfo = ad_1601_timestamp_to_string (tmp, attr->attr_name);
894 else if (!strcmp (attr->attr_name, "userAccountControl"))
895 extrainfo = ad_1601_uac_to_string (tmp);
896 else if (!strcmp (attr->attr_name, "sAMAccountType"))
897 extrainfo = ad_sam_account_type_to_string (tmp);
898
899 if (extrainfo) {
900 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
901 " ", 1,
902 "data", NULL);
903 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
904 extrainfo, -1,
905 "convdata", NULL);
906 g_free (extrainfo);
907 }
908 g_free (tmp);
909 }
910 else {
911 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t, _("Can't display attribute value"), -1,
912 "error", NULL);
913 }
914 gtk_text_buffer_insert_with_tags_by_name (tbuffer, ¤t,
915 "\n", 1,
916 "data", NULL);
917 }
918 }
919 }
920 if (ts_dh)
921 g_object_unref (ts_dh);
922 gda_ldap_entry_free (entry);
923 }
924 else
925 browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eprop)),
926 "%s", _("Could not get information about LDAP entry"));
927
928 if (eprop->priv->text_search && gtk_widget_get_visible (eprop->priv->text_search))
929 text_search_rerun (TEXT_SEARCH (eprop->priv->text_search));
930
931 g_object_unref (eprop);
932 }
933
934 /**
935 * entry_properties_set_dn:
936 * @eprop: a #EntryProperties widget
937 * @dn: a DN to display information for
938 *
939 * Adjusts the display to show @dn's properties
940 */
941 void
entry_properties_set_dn(EntryProperties * eprop,const gchar * dn)942 entry_properties_set_dn (EntryProperties *eprop, const gchar *dn)
943 {
944 g_return_if_fail (IS_ENTRY_PROPERTIES (eprop));
945
946 GtkTextBuffer *tbuffer;
947 GtkTextIter start, end;
948
949 tbuffer = eprop->priv->text;
950 gtk_text_buffer_get_start_iter (tbuffer, &start);
951 gtk_text_buffer_get_end_iter (tbuffer, &end);
952 gtk_text_buffer_delete (tbuffer, &start, &end);
953
954 if (dn && *dn) {
955 guint id;
956 id = browser_connection_ldap_describe_entry (eprop->priv->bcnc, dn,
957 (BrowserConnectionJobCallback) info_fetch_cb,
958 g_object_ref (eprop), NULL);
959 if (id == 0)
960 browser_show_message (GTK_WINDOW (gtk_widget_get_toplevel ((GtkWidget*) eprop)),
961 "%s", _("Could not get information about LDAP entry"));
962 }
963 }
964
965 static void
show_search_bar(EntryProperties * eprop)966 show_search_bar (EntryProperties *eprop)
967 {
968 if (! eprop->priv->text_search) {
969 eprop->priv->text_search = text_search_new (eprop->priv->view);
970 gtk_box_pack_start (GTK_BOX (eprop), eprop->priv->text_search, FALSE, FALSE, 0);
971 gtk_widget_show (eprop->priv->text_search);
972 }
973 else {
974 gtk_widget_show (eprop->priv->text_search);
975 text_search_rerun (TEXT_SEARCH (eprop->priv->text_search));
976 }
977
978 gtk_widget_grab_focus (eprop->priv->text_search);
979 }
980