1
/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
3
* Copyright (C) 2011 Alexander Larsson <alexl@redhat.com>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
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.
15
* You should have received a copy of the GNU General Public License
16
* along with this program. If not, see <http://www.gnu.org/licenses/>.
23
public class Contacts.FieldRow : Contacts.Row {
29
/* show_as_editable means we prelight, can_focus, show selected, etc.
30
It doesn't mean we can't edit the row. For instance the
31
Card row is editing when we're editing the full name, but
32
thats not represented in the UI as editing the row. */
33
protected bool show_as_editable;
34
protected bool is_editing;
36
public FieldRow(RowGroup group, ContactPane pane) {
39
set_redraw_on_allocate (true); // Since we draw the focus rect
41
this.button_press_event.connect ( (ev) => {
43
this.pane.exit_edit_mode (true);
47
clickable = new Clickable (this);
48
clickable.set_focus_on_click (true);
49
clickable.clicked.connect ( () => { this.clicked (); } );
52
/* This should really be in class construct, but that doesn't seem to work... */
53
activate_signal = GLib.Signal.lookup ("activate-row", typeof (FieldRow));
56
public void set_editing (bool val) {
60
public void reset () {
64
public signal void clicked ();
66
public override bool focus (DirectionType direction) {
67
var row_can_focus = get_can_focus ();
69
/* Non-focusable rows get the standard behvaiour */
71
return base.focus (direction);
73
/* Focusable rows have to also support focusable children,
74
which is not supported by Container.focus(), so we
79
bool recurse_into = false;
82
case DirectionType.RIGHT:
83
case DirectionType.TAB_FORWARD:
87
} else if (this.get_focus_child () != null) {
91
case DirectionType.LEFT:
92
case DirectionType.TAB_BACKWARD:
99
set_can_focus (false);
100
res = base.focus (direction);
101
set_can_focus (true);
103
if (!res && !has_focus) {
105
case DirectionType.LEFT:
106
case DirectionType.TAB_BACKWARD:
122
[CCode (action_signal = true)]
123
public virtual signal void activate_row () {
124
clickable.activate ();
127
public override void realize () {
129
clickable.realize_for (event_window);
132
public override void unrealize () {
134
clickable.unrealize ();
137
public override bool draw (Cairo.Context cr) {
138
Allocation allocation;
139
this.get_allocation (out allocation);
141
var context = this.get_style_context ();
144
StateFlags state = 0;
145
if (show_as_editable) {
146
state = clickable.state & (StateFlags.ACTIVE | StateFlags.PRELIGHT);
148
state |= StateFlags.SELECTED;
150
context.set_state (state);
152
context.render_background (cr,
153
0, 0, allocation.width, allocation.height);
155
if (this.has_visible_focus ())
156
context.render_focus (cr, 0, 0, allocation.width, allocation.height);
165
public override void parent_set (Widget? old_parent) {
166
if (old_parent != null) {
167
var old_parent_container = (old_parent as Container);
168
old_parent_container.set_focus_child.disconnect (parent_set_focus_child);
171
var parent_container = (this.get_parent () as Container);
172
has_child_focus = parent_container != null && parent_container.get_focus_child () == this;
173
if (parent_container != null)
174
parent_container.set_focus_child.connect (parent_set_focus_child);
177
public virtual signal void lost_child_focus () {
180
public void parent_set_focus_child (Container container, Widget? widget) {
181
var old_has_child_focus = has_child_focus;
182
has_child_focus = widget == this;
184
if (old_has_child_focus && !has_child_focus) {
186
if (!has_child_focus)
193
public void pack (Widget w) {
194
this.attach (w, 1, start++);
197
public void pack_label (string s) {
198
var l = new Label (s);
199
l.set_halign (Align.START);
200
l.get_style_context ().add_class ("dim-label");
204
public void pack_header (string s) {
205
var l = new Label (s);
207
Markup.printf_escaped ("<span font='24px'>%s</span>", s));
208
l.set_halign (Align.START);
212
public Grid pack_header_in_grid (string s, out Label label) {
213
var grid = new Grid ();
214
grid.set_column_spacing (4);
215
var l = new Label (s);
218
Markup.printf_escaped ("<span font='24px'>%s</span>", s));
219
l.set_halign (Align.START);
220
l.set_hexpand (true);
222
grid.set_halign (Align.FILL);
230
public Label pack_text (bool wrap = false) {
231
var l = new Label ("");
233
l.set_line_wrap (true);
234
l.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
236
l.set_ellipsize (Pango.EllipsizeMode.END);
238
l.set_halign (Align.START);
243
public void pack_text_detail (out Label text_label, out Label detail_label, bool wrap = false) {
244
var grid = new Grid ();
246
var l = new Label ("");
247
l.set_hexpand (true);
248
l.set_halign (Align.START);
250
l.set_line_wrap (true);
251
l.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
253
l.set_ellipsize (Pango.EllipsizeMode.END);
260
l.set_halign (Align.END);
261
l.get_style_context ().add_class ("dim-label");
264
grid.set_halign (Align.FILL);
270
public void pack_widget_detail_combo (Widget w, AbstractFieldDetails detail, TypeSet type_set, out TypeCombo combo) {
271
var grid = new Grid ();
272
grid.set_column_spacing (16);
276
combo = new TypeCombo (type_set);
277
combo.set_hexpand (false);
278
combo.set_halign (Align.END);
279
combo.set_active (detail);
281
grid.set_halign (Align.FILL);
288
public void pack_entry_detail_combo (string text, AbstractFieldDetails detail, TypeSet type_set, out Entry entry, out TypeCombo combo) {
289
entry = new Entry ();
290
entry.get_style_context ().add_class ("contacts-entry");
291
entry.set_text (text);
292
entry.set_hexpand (true);
293
entry.set_halign (Align.FILL);
295
pack_widget_detail_combo (entry, detail, type_set, out combo);
298
public Entry pack_entry (string s) {
299
var e = new Entry ();
300
e.get_style_context ().add_class ("contacts-entry");
302
e.set_halign (Align.FILL);
307
public void left_add (Widget widget) {
308
this.attach (widget, 0, 0);
309
widget.set_halign (Align.END);
312
public void right_add (Widget widget) {
313
this.attach (widget, 2, 0);
314
widget.set_halign (Align.START);
317
public Button pack_delete_button () {
318
var image = new Image.from_icon_name ("user-trash-symbolic", IconSize.MENU);
319
var b = new Button();
322
b.set_halign (Align.CENTER);
327
public virtual signal bool enter_edit_mode () {
331
public virtual signal void exit_edit_mode (bool save) {
335
public abstract class Contacts.FieldSet : Grid {
336
public class string label_name;
337
public class string detail_name;
338
public class string property_name;
339
public class bool is_single_value;
341
public PersonaSheet sheet { get; construct; }
342
public int row_nr { get; construct; }
346
protected ArrayList<DataFieldRow> data_rows = new ArrayList<DataFieldRow>();
348
public abstract void populate ();
349
public abstract DataFieldRow new_field ();
352
this.set_orientation (Orientation.VERTICAL);
354
label_row = new FieldRow (sheet.pane.row_group, sheet.pane);
355
this.add (label_row);
356
label_row.pack_label (label_name);
359
public void add_to_sheet () {
361
sheet.attach (this, 0, row_nr, 1, 1);
366
public void remove_from_sheet () {
373
public bool reads_param (string param) {
374
return param == property_name;
377
public bool is_empty () {
378
return get_children ().length () == 1;
381
public void clear () {
382
foreach (var row in data_rows) {
388
public void add_row (DataFieldRow row) {
392
row.clicked.connect( () => {
393
sheet.pane.enter_edit_mode (row);
399
public void remove_row (DataFieldRow row) {
401
data_rows.remove (row);
404
public virtual Value? get_value () {
408
public void save () {
409
var value = get_value ();
411
warning ("Unimplemented get_value()");
414
sheet.pane.contact.set_persona_property.begin (sheet.persona, property_name, value,
417
var contact = obj as Contact;
419
contact.set_persona_property.end (result);
421
App.app.show_message (e2.message);
422
refresh_from_persona ();
428
public void refresh_from_persona () {
432
if (this.is_empty ())
433
this.remove_from_sheet ();
436
this.add_to_sheet ();
441
public abstract class Contacts.DataFieldRow : FieldRow {
442
public FieldSet field_set;
443
protected Button? delete_button;
445
public DataFieldRow (FieldSet field_set) {
446
base (field_set.sheet.pane.row_group, field_set.sheet.pane);
448
Contact.persona_has_writable_property (field_set.sheet.persona,
449
field_set.property_name);
450
set_editable (editable);
451
this.field_set = field_set;
454
public void set_editable (bool editable) {
455
this.show_as_editable = editable;
456
set_can_focus (editable);
459
public abstract void update ();
460
public virtual void pack_edit_widgets () {
462
public virtual bool finish_edit_widgets (bool save) {
466
public override bool enter_edit_mode () {
467
if (!show_as_editable)
470
this.set_can_focus (false);
471
foreach (var w in this.get_children ()) {
473
w.set_data ("original-widget", true);
477
delete_button = this.pack_delete_button ();
478
delete_button.clicked.connect ( () => {
479
field_set.remove_row (this);
483
this.pack_edit_widgets ();
485
foreach (var w in this.get_children ()) {
486
if (!w.get_data<bool> ("original-widget"))
493
public override void lost_child_focus () {
494
if (field_set.sheet.pane.editing_row == this)
495
field_set.sheet.pane.exit_edit_mode (true);
498
public override void exit_edit_mode (bool save) {
499
if (!show_as_editable)
502
var had_child_focus = this.get_focus_child () != null;
504
var changed = finish_edit_widgets (save);
506
delete_button = null;
507
foreach (var w in this.get_children ()) {
508
if (!w.get_data<bool> ("original-widget"))
514
this.set_can_focus (true);
522
public void setup_entry_for_edit (Entry entry, bool grab_focus = true) {
524
Utils.grab_widget_later (entry);
526
entry.activate.connect_after ( () => {
527
field_set.sheet.pane.exit_edit_mode (true);
529
entry.key_press_event.connect ( (key_event) => {
530
if (key_event.keyval == Gdk.Key.Escape) {
531
field_set.sheet.pane.exit_edit_mode (false);
537
public void setup_text_view_for_edit (TextView text, bool grab_focus = true) {
539
Utils.grab_widget_later (text);
541
text.key_press_event.connect ( (key_event) => {
542
if (key_event.keyval == Gdk.Key.Escape) {
543
field_set.sheet.pane.exit_edit_mode (false);
550
class Contacts.LinkFieldRow : DataFieldRow {
551
public UrlFieldDetails details;
553
LinkButton uri_button;
556
public LinkFieldRow (FieldSet field_set, UrlFieldDetails details) {
558
this.details = details;
560
text_label = this.pack_text ();
561
var image = new Image.from_icon_name ("web-browser-symbolic", IconSize.MENU);
562
image.get_style_context ().add_class ("dim-label");
563
uri_button = new LinkButton("");
564
uri_button.remove (uri_button.get_child ());
565
uri_button.set_relief (ReliefStyle.NONE);
566
uri_button.add (image);
567
this.right_add (uri_button);
570
public override void update () {
571
text_label.set_text (Contact.format_uri_link_text (details));
572
uri_button.set_uri (details.value);
575
public override void pack_edit_widgets () {
576
entry = this.pack_entry (details.value);
577
setup_entry_for_edit (entry);
580
public override bool finish_edit_widgets (bool save) {
581
var old_details = details;
582
var changed = entry.get_text () != details.value;
584
details = new UrlFieldDetails (entry.get_text (), old_details.parameters);
590
class Contacts.LinkFieldSet : FieldSet {
592
label_name = C_("Addresses on the Web", "Links");
593
detail_name = C_("Web address", "Link");
594
property_name = "urls";
597
public override void populate () {
598
var details = sheet.persona as UrlDetails;
602
var urls = details.urls;
603
foreach (var url_details in urls) {
604
var row = new LinkFieldRow (this, url_details);
609
public override DataFieldRow new_field () {
610
var row = new LinkFieldRow (this, new UrlFieldDetails (""));
615
public override Value? get_value () {
616
var details = sheet.persona as UrlDetails;
620
var new_details = new HashSet<UrlFieldDetails>();
621
foreach (var row in data_rows) {
622
var link_row = row as LinkFieldRow;
623
new_details.add (link_row.details);
626
var value = Value(new_details.get_type ());
627
value.set_object (new_details);
633
class Contacts.DetailedFieldRow<T> : DataFieldRow {
634
public AbstractFieldDetails<string> _details;
641
public delegate AbstractFieldDetails<string> DataCreate(string s);
642
DataCreate data_create;
644
public T details { get { return (T)_details; } }
646
public DetailedFieldRow (FieldSet field_set, AbstractFieldDetails<string> details, TypeSet type_set, owned DataCreate data_create) {
648
this._details = details;
649
this.type_set = type_set;
650
this.data_create = (owned) data_create;
651
this.pack_text_detail (out text_label, out detail_label);
654
public override void update () {
655
text_label.set_text (_details.value);
656
detail_label.set_text (type_set.format_type (_details));
659
public override void pack_edit_widgets () {
660
this.pack_entry_detail_combo (_details.value, _details, type_set, out entry, out combo);
661
setup_entry_for_edit (entry);
664
public override bool finish_edit_widgets (bool save) {
665
var old_details = _details;
666
bool changed = _details.value != entry.get_text () || combo.modified;
667
if (save && changed) {
668
_details = data_create (entry.get_text ());
669
_details.parameters = old_details.parameters;
670
combo.update_details (_details);
678
class Contacts.EmailFieldSet : FieldSet {
680
label_name = _("Email");
681
detail_name = _("Email");
682
property_name = "email-addresses";
685
public override void populate () {
686
var details = sheet.persona as EmailDetails;
689
var emails = Contact.sort_fields<EmailFieldDetails>(details.email_addresses);
690
foreach (var email in emails) {
691
var row = new DetailedFieldRow<EmailFieldDetails> (this, email,TypeSet.general, (s) => { return new EmailFieldDetails (s); } );
696
public override DataFieldRow new_field () {
697
var row = new DetailedFieldRow<EmailFieldDetails> (this, new EmailFieldDetails("") ,TypeSet.general, (s) => { return new EmailFieldDetails (s); } );
702
public override Value? get_value () {
703
var details = sheet.persona as EmailDetails;
707
var new_details = new HashSet<EmailFieldDetails>();
708
foreach (var row in data_rows) {
709
var email_row = row as DetailedFieldRow<EmailFieldDetails>;
710
new_details.add (email_row.details);
713
var value = Value(new_details.get_type ());
714
value.set_object (new_details);
720
class Contacts.PhoneFieldSet : FieldSet {
722
label_name = _("Phone");
723
detail_name = _("Phone number");
724
property_name = "phone-numbers";
726
public override void populate () {
727
var details = sheet.persona as PhoneDetails;
730
var phone_numbers = Contact.sort_fields<PhoneFieldDetails>(details.phone_numbers);
731
foreach (var phone in phone_numbers) {
732
var row = new DetailedFieldRow<PhoneFieldDetails> (this, phone,TypeSet.phone, (s) => { return new PhoneFieldDetails (s);} );
737
public override DataFieldRow new_field () {
738
var row = new DetailedFieldRow<PhoneFieldDetails> (this, new PhoneFieldDetails("") ,TypeSet.phone, (s) => { return new EmailFieldDetails (s); } );
743
public override Value? get_value () {
744
var details = sheet.persona as PhoneDetails;
748
var new_details = new HashSet<PhoneFieldDetails>();
749
foreach (var row in data_rows) {
750
var phone_row = row as DetailedFieldRow<PhoneFieldDetails>;
751
new_details.add (phone_row.details);
754
var value = Value(new_details.get_type ());
755
value.set_object (new_details);
761
class Contacts.ChatFieldRow : DataFieldRow {
763
ImFieldDetails details;
767
public ChatFieldRow (FieldSet field_set, string protocol, ImFieldDetails details) {
769
this.protocol = protocol;
770
this.details = details;
771
text_label = this.pack_text ();
772
this.set_editable (false);
775
public override void update () {
776
var im_persona = field_set.sheet.persona as Tpf.Persona;
777
text_label.set_text (Contact.format_im_name (im_persona, protocol, details.value));
781
class Contacts.ChatFieldSet : FieldSet {
783
label_name = _("Chat");
784
detail_name = _("Chat");
785
property_name = "im-addresses";
787
public override void populate () {
788
var details = sheet.persona as ImDetails;
791
foreach (var protocol in details.im_addresses.get_keys ()) {
792
foreach (var id in details.im_addresses[protocol]) {
793
if (sheet.persona is Tpf.Persona) {
794
var row = new ChatFieldRow (this, protocol, id);
801
public override DataFieldRow new_field () {
802
var row = new ChatFieldRow (this, "", new ImFieldDetails (""));
808
class Contacts.BirthdayFieldRow : DataFieldRow {
809
public DateTime details;
811
SpinButton? day_spin;
812
SpinButton? year_spin;
815
public BirthdayFieldRow (FieldSet field_set, DateTime details) {
817
this.details = details;
819
text_label = this.pack_text ();
820
var image = new Image.from_icon_name ("preferences-system-date-and-time-symbolic", IconSize.MENU);
821
image.get_style_context ().add_class ("dim-label");
822
var button = new Button();
823
button.set_relief (ReliefStyle.NONE);
825
this.right_add (button);
826
button.clicked.connect ( () => {
827
Utils.show_calendar (details);
831
public override void update () {
832
text_label.set_text (details.to_local ().format ("%x"));
835
public override void pack_edit_widgets () {
836
var bday = details.to_local ();
837
var grid = new Grid ();
838
grid.set_column_spacing (16);
840
day_spin = new SpinButton.with_range (0, 31, 1);
841
day_spin.set_digits (0);
842
day_spin.numeric = true;
843
day_spin.set_value ((double)bday.get_day_of_month ());
846
setup_entry_for_edit (day_spin);
848
combo = new ComboBoxText ();
849
combo.append_text (_("January"));
850
combo.append_text (_("February"));
851
combo.append_text (_("March"));
852
combo.append_text (_("April"));
853
combo.append_text (_("May"));
854
combo.append_text (_("June"));
855
combo.append_text (_("July"));
856
combo.append_text (_("August"));
857
combo.append_text (_("September"));
858
combo.append_text (_("October"));
859
combo.append_text (_("November"));
860
combo.append_text (_("December"));
861
combo.set_active (bday.get_month () - 1);
862
combo.get_style_context ().add_class ("contacts-combo");
865
year_spin = new SpinButton.with_range (1800, 3000, 1);
866
year_spin.set_digits (0);
867
year_spin.numeric = true;
868
year_spin.set_value ((double)bday.get_year ());
869
grid.add (year_spin);
871
setup_entry_for_edit (year_spin, false);
876
public override bool finish_edit_widgets (bool save) {
877
var old_details = details;
879
var bday = new DateTime.local ((int)year_spin.get_value (),
880
combo.get_active () + 1,
881
(int)day_spin.get_value (),
883
bday = bday.to_utc ();
885
var changed = !bday.equal (old_details);
896
class Contacts.BirthdayFieldSet : FieldSet {
898
label_name = _("Birthday");
899
detail_name = _("Birthday");
900
property_name = "birthday";
901
is_single_value = true;
903
public override void populate () {
904
var details = sheet.persona as BirthdayDetails;
908
DateTime? bday = details.birthday;
910
var row = new BirthdayFieldRow (this, bday);
915
public override DataFieldRow new_field () {
916
var row = new BirthdayFieldRow (this, new DateTime.now_utc ());
921
public override Value? get_value () {
922
var details = sheet.persona as BirthdayDetails;
926
DateTime? new_details = null;
927
foreach (var row in data_rows) {
928
var bday_row = row as BirthdayFieldRow;
929
new_details = bday_row.details;
932
var value = Value(typeof (DateTime));
933
value.set_boxed (new_details);
939
class Contacts.StringFieldRow : DataFieldRow {
944
public StringFieldRow (FieldSet field_set, string value) {
948
text_label = this.pack_text ();
951
public override void update () {
952
text_label.set_text (value);
955
public override void pack_edit_widgets () {
956
entry = this.pack_entry (value);
957
setup_entry_for_edit (entry);
960
public override bool finish_edit_widgets (bool save) {
961
var changed = entry.get_text () != value;
963
value = entry.get_text ();
969
class Contacts.NicknameFieldSet : FieldSet {
971
label_name = _("Nickname");
972
detail_name = _("Nickname");
973
property_name = "nickname";
974
is_single_value = true;
976
public override void populate () {
977
var details = sheet.persona as NameDetails;
981
if (is_set (details.nickname)) {
982
var row = new StringFieldRow (this, details.nickname);
987
public override DataFieldRow new_field () {
988
var row = new StringFieldRow (this, "");
993
public override Value? get_value () {
994
var details = sheet.persona as NameDetails;
998
var value = Value(typeof (string));
999
value.set_string ("");
1000
foreach (var row in data_rows) {
1001
var string_row = row as StringFieldRow;
1002
value.set_string (string_row.value);
1009
class Contacts.NoteFieldRow : DataFieldRow {
1010
public NoteFieldDetails details;
1014
public NoteFieldRow (FieldSet field_set, NoteFieldDetails details) {
1016
this.details = details;
1018
text_label = this.pack_text (true);
1021
public override void update () {
1022
text_label.set_text (details.value);
1025
public override void pack_edit_widgets () {
1026
text = new TextView ();
1027
text.get_style_context ().add_class ("contacts-entry");
1028
text.set_hexpand (true);
1029
text.set_vexpand (true);
1030
var scrolled = new ScrolledWindow (null, null);
1031
scrolled.set_shadow_type (ShadowType.OUT);
1032
scrolled.add_with_viewport (text);
1036
delete_button.set_valign (Align.START);
1038
text.get_buffer ().set_text (details.value);
1039
text.get_buffer ().set_modified (false);
1041
setup_text_view_for_edit (text);
1044
public override bool finish_edit_widgets (bool save) {
1045
var old_details = details;
1046
var changed = text.get_buffer (). get_modified ();
1047
if (save && changed) {
1048
TextIter start, end;
1049
text.get_buffer ().get_start_iter (out start);
1050
text.get_buffer ().get_end_iter (out end);
1051
var value = text.get_buffer ().get_text (start, end, true);
1052
details = new NoteFieldDetails (value, old_details.parameters);
1060
class Contacts.NoteFieldSet : FieldSet {
1062
label_name = _("Note");
1063
detail_name = _("Note");
1064
property_name = "notes";
1065
is_single_value = true;
1067
public override void populate () {
1068
var details = sheet.persona as NoteDetails;
1069
if (details == null)
1072
foreach (var note in details.notes) {
1073
var row = new NoteFieldRow (this, note);
1078
public override DataFieldRow new_field () {
1079
var row = new NoteFieldRow (this, new NoteFieldDetails (""));
1084
public override Value? get_value () {
1085
var details = sheet.persona as NoteDetails;
1086
if (details == null)
1089
var new_details = new HashSet<NoteFieldDetails>();
1090
foreach (var row in data_rows) {
1091
var note_row = row as NoteFieldRow;
1092
new_details.add (note_row.details);
1095
var value = Value(new_details.get_type ());
1096
value.set_object (new_details);
1102
class Contacts.AddressFieldRow : DataFieldRow {
1103
public PostalAddressFieldDetails details;
1104
Label? text_label[8];
1109
public AddressFieldRow (FieldSet field_set, PostalAddressFieldDetails details) {
1111
this.details = details;
1112
this.pack_text_detail (out text_label[0], out detail_label);
1113
for (int i = 1; i < text_label.length; i++) {
1114
text_label[i] = this.pack_text (true);
1118
public override void update () {
1119
detail_label.set_text (TypeSet.general.format_type (details));
1121
string[] strs = Contact.format_address (details.value);
1122
for (int i = 0; i < text_label.length; i++) {
1123
if (i < strs.length && strs[i] != null) {
1124
text_label[i].set_text (strs[i]);
1125
text_label[i].show ();
1126
text_label[i].set_no_show_all (false);
1128
text_label[i].hide ();
1129
text_label[i].set_no_show_all (true);
1134
public override void pack_edit_widgets () {
1136
var grid = new Box (Orientation.VERTICAL, 0);
1137
grid.set_hexpand (true);
1138
grid.set_halign (Align.FILL);
1140
for (int i = 0; i < entry.length; i++) {
1142
details.value.get (Contact.postal_element_props[i], out postal_part);
1143
entry[i] = new Entry ();
1144
entry[i].set_hexpand (true);
1145
if (postal_part != null)
1146
entry[i].set_text (postal_part);
1147
entry[i].set ("placeholder-text", Contact.postal_element_names[i]);
1148
entry[i].get_style_context ().add_class ("contacts-entry");
1149
entry[i].get_style_context ().add_class ("contacts-postal-entry");
1150
grid.add (entry[i]);
1152
setup_entry_for_edit (entry[i], i == 0);
1155
this.pack_widget_detail_combo (grid, details, TypeSet.general, out combo);
1156
delete_button.set_valign (Align.START);
1157
var size_group = new SizeGroup (SizeGroupMode.VERTICAL);
1158
size_group.add_widget (delete_button);
1159
size_group.add_widget (combo);
1163
public override bool finish_edit_widgets (bool save) {
1164
var old_details = details;
1166
bool changed = combo.modified;
1167
for (int i = 0; i < entry.length; i++) {
1169
details.value.get (Contact.postal_element_props[i], out postal_part);
1170
if (entry[i].get_text () != postal_part) {
1176
if (save && changed) {
1177
var new_value = new PostalAddress (details.value.po_box,
1178
details.value.extension,
1179
details.value.street,
1180
details.value.locality,
1181
details.value.region,
1182
details.value.postal_code,
1183
details.value.country,
1184
details.value.address_format,
1186
for (int i = 0; i < entry.length; i++)
1187
new_value.set (Contact.postal_element_props[i], entry[i].get_text ());
1188
details = new PostalAddressFieldDetails(new_value, old_details.parameters);
1189
combo.update_details (details);
1192
for (int i = 0; i < entry.length; i++)
1200
class Contacts.AddressFieldSet : FieldSet {
1202
label_name = _("Addresses");
1203
detail_name = _("Address");
1204
property_name = "postal-addresses";
1206
public override void populate () {
1207
var details = sheet.persona as PostalAddressDetails;
1208
if (details == null)
1211
foreach (var addr in details.postal_addresses) {
1212
var row = new AddressFieldRow (this, addr);
1217
public override DataFieldRow new_field () {
1218
var row = new AddressFieldRow (this,
1219
new PostalAddressFieldDetails (
1220
new PostalAddress (null,
1233
public override Value? get_value () {
1234
var details = sheet.persona as PostalAddressDetails;
1235
if (details == null)
1238
var new_details = new HashSet<PostalAddressFieldDetails>();
1239
foreach (var row in data_rows) {
1240
var addr_row = row as AddressFieldRow;
1241
new_details.add (addr_row.details);
1244
var value = Value(new_details.get_type ());
1245
value.set_object (new_details);
1251
public class Contacts.PersonaSheet : Grid {
1252
public ContactPane pane;
1253
public Persona persona;
1257
static Type[] field_set_types = {
1258
typeof(LinkFieldSet),
1259
typeof(EmailFieldSet),
1260
typeof(PhoneFieldSet),
1261
typeof(ChatFieldSet),
1262
typeof(BirthdayFieldSet),
1263
typeof(NicknameFieldSet),
1264
typeof(AddressFieldSet),
1265
typeof(NoteFieldSet)
1267
company/department/profession/title/manager/assistant
1270
FieldSet? field_sets[8]; // This is really the size of field_set_types
1272
public PersonaSheet(ContactPane pane, Persona persona, int sheet_nr) {
1273
assert (field_sets.length == field_set_types.length);
1276
this.persona = persona;
1278
this.set_orientation (Orientation.VERTICAL);
1279
this.set_row_spacing (16);
1283
bool editable = Contact.persona_has_writable_property (persona, "email-addresses") &&
1284
Contact.persona_has_writable_property (persona, "phone-numbers") &&
1285
Contact.persona_has_writable_property (persona, "postal-addresses");
1287
if (!Contact.persona_is_main (persona) || sheet_nr > 0) {
1288
header = new FieldRow (pane.row_group, pane);
1291
var grid = header.pack_header_in_grid (Contact.format_persona_store_name_for_contact (persona), out label);
1294
var image = new Image.from_icon_name ("changes-prevent-symbolic", IconSize.MENU);
1296
label.set_hexpand (false);
1297
image.get_style_context ().add_class ("dim-label");
1298
image.set_hexpand (true);
1299
image.set_halign (Align.START);
1300
image.set_valign (Align.CENTER);
1304
if (sheet_nr == 0) {
1305
var b = new Button.with_label(_("Add to My Contacts"));
1308
b.clicked.connect ( () => {
1309
link_contacts.begin (pane.contact, null, (obj, result) => {
1310
link_contacts.end (result);
1311
/* TODO: Support undo */
1314
} else if (pane.contact.individual.personas.size > 1) {
1315
var b = new Button.with_label(_("Unlink"));
1318
b.clicked.connect ( () => {
1319
unlink_persona.begin (pane.contact, persona, (obj, result) => {
1320
unlink_persona.end (result);
1321
/* TODO: Support undo */
1322
/* TODO: Ensure we don't get suggestion for this linkage again */
1327
this.attach (header, 0, row_nr++, 1, 1);
1329
header.clicked.connect ( () => {
1330
this.pane.enter_edit_mode (header);
1334
for (int i = 0; i < field_set_types.length; i++) {
1335
var field_set = (FieldSet) Object.new(field_set_types[i], sheet: this, row_nr: row_nr++);
1336
field_sets[i] = field_set;
1338
field_set.populate ();
1339
if (!field_set.is_empty ())
1340
field_set.add_to_sheet ();
1344
footer = new FieldRow (pane.row_group, pane);
1345
this.attach (footer, 0, row_nr++, 1, 1);
1347
var b = new Button.with_label (_("Add detail..."));
1348
b.set_halign (Align.START);
1349
b.clicked.connect (add_detail);
1353
persona.notify.connect(persona_notify_cb);
1357
persona.notify.disconnect(persona_notify_cb);
1360
private void add_detail () {
1361
pane.exit_edit_mode (true);
1362
var title = _("Select detail to add to %s").printf (pane.contact.display_name);
1363
var dialog = new Dialog.with_buttons ("",
1364
(Window) pane.get_toplevel (),
1365
DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT,
1366
Stock.CANCEL, ResponseType.CANCEL,
1367
Stock.OK, ResponseType.OK);
1369
dialog.set_resizable (false);
1370
dialog.set_default_response (ResponseType.OK);
1372
var tree_view = new TreeView ();
1373
var store = new ListStore (2, typeof (string), typeof (FieldSet));
1374
tree_view.set_model (store);
1375
tree_view.set_headers_visible (false);
1376
tree_view.get_selection ().set_mode (SelectionMode.BROWSE);
1378
var column = new Gtk.TreeViewColumn ();
1379
tree_view.append_column (column);
1381
var renderer = new Gtk.CellRendererText ();
1382
column.pack_start (renderer, false);
1383
column.add_attribute (renderer, "text", 0);
1385
var scrolled = new ScrolledWindow(null, null);
1386
scrolled.set_size_request (340, 300);
1387
scrolled.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
1388
scrolled.set_vexpand (true);
1389
scrolled.set_hexpand (true);
1390
scrolled.set_shadow_type (ShadowType.IN);
1391
scrolled.add (tree_view);
1393
var grid = new Grid ();
1394
grid.set_orientation (Orientation.VERTICAL);
1395
grid.set_row_spacing (28);
1397
var l = new Label (title);
1398
l.set_halign (Align.START);
1401
grid.add (scrolled);
1403
var box = dialog.get_content_area () as Box;
1404
box.pack_start (grid, true, true, 0);
1405
grid.set_border_width (6);
1409
for (int i = 0; i < field_set_types.length; i++) {
1410
var field_set = field_sets[i];
1411
if (!(field_set is ChatFieldSet) &&
1412
Contact.persona_has_writable_property (persona, field_set.property_name) &&
1413
(field_set.is_empty () || !field_set.is_single_value)) {
1414
store.append (out iter);
1415
store.set (iter, 0, field_set.detail_name, 1, field_set);
1420
dialog.response.connect ( (response) => {
1421
if (response == ResponseType.OK) {
1425
if (tree_view.get_selection() .get_selected (null, out iter2)) {
1426
store.get (iter2, 1, out field_set);
1428
var row = field_set.new_field ();
1429
field_set.show_all ();
1430
field_set.add_to_sheet ();
1431
pane.enter_edit_mode (row);
1438
private void persona_notify_cb (ParamSpec pspec) {
1439
var name = pspec.get_name ();
1440
foreach (var field_set in field_sets) {
1441
if (field_set.reads_param (name) && !field_set.saving) {
1442
field_set.refresh_from_persona ();
1449
public class Contacts.ContactPane : ScrolledWindow {
1450
private Store contacts_store;
1451
private Grid top_grid;
1452
private FieldRow card_row;
1453
private Grid card_grid;
1454
private Grid personas_grid;
1455
public RowGroup row_group;
1456
public RowGroup card_row_group;
1457
public FieldRow? editing_row;
1459
public Button email_button;
1460
public Button chat_button;
1461
public Button call_button;
1462
public Gtk.Menu context_menu;
1463
private Gtk.MenuItem link_menu_item;
1464
private Gtk.MenuItem delete_menu_item;
1466
public Contact? contact;
1468
const int PROFILE_SIZE = 128;
1470
private async Persona? set_persona_property (Persona persona,
1471
string property_name,
1472
Value value) throws GLib.Error, PropertyError {
1473
if (persona is FakePersona) {
1474
var fake = persona as FakePersona;
1475
return yield fake.make_real_and_set (property_name, value);
1477
persona.set_data ("contacts-unedited", true);
1478
yield Contact.set_persona_property (persona, property_name, value);
1483
/* Tries to set the property on all persons that have it writeable, and
1484
* if none, creates a new persona and writes to it, returning the new
1487
private async Persona? set_individual_property (Contact contact,
1488
string property_name,
1489
Value value) throws GLib.Error, PropertyError {
1490
bool did_set = false;
1491
// Need to make a copy here as it could change during the yields
1492
var personas_copy = contact.individual.personas.to_array ();
1493
foreach (var p in personas_copy) {
1494
if (property_name in p.writeable_properties) {
1496
yield Contact.set_persona_property (p, property_name, value);
1501
var fake = new FakePersona (contact);
1502
return yield fake.make_real_and_set (property_name, value);
1507
private void change_avatar (ContactFrame image_frame) {
1508
this.exit_edit_mode (true);
1509
var dialog = new AvatarDialog (contact);
1511
dialog.set_avatar.connect ( (icon) => {
1512
Value v = Value (icon.get_type ());
1513
v.set_object (icon);
1514
set_individual_property.begin (contact,
1518
set_individual_property.end (result);
1520
App.app.show_message (e.message);
1521
image_frame.set_image (contact.individual, contact);
1527
public void update_card () {
1528
foreach (var w in card_grid.get_children ()) {
1532
if (contact == null)
1535
var image_frame = new ContactFrame (PROFILE_SIZE, true);
1536
image_frame.clicked.connect ( () => {
1537
change_avatar (image_frame);
1539
contact.keep_widget_uptodate (image_frame, (w) => {
1540
(w as ContactFrame).set_image (contact.individual, contact);
1543
card_grid.attach (image_frame, 0, 0, 1, 3);
1544
card_grid.set_column_spacing (16);
1546
var l = new Label (null);
1547
l.set_hexpand (true);
1548
l.set_halign (Align.START);
1549
l.set_valign (Align.START);
1550
l.set_margin_top (4);
1551
l.set_ellipsize (Pango.EllipsizeMode.END);
1554
contact.keep_widget_uptodate (l, (w) => {
1555
(w as Label).set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", contact.display_name));
1558
var event_box = new EventBox ();
1559
event_box.set_margin_top (4);
1560
event_box.set_margin_bottom (8);
1561
event_box.set_visible_window (false);
1563
var clickable = new Clickable (event_box);
1564
event_box.realize.connect_after ( (event) => {
1565
Gdk.Window window = null;
1566
foreach (var win in event_box.get_window ().get_children ()) {
1568
win.get_user_data (out w);
1569
if (w == event_box) {
1573
clickable.realize_for (window);
1575
event_box.unrealize.connect_after ( (event) => {
1576
clickable.unrealize ();
1578
clickable.clicked.connect ( () => {
1579
this.enter_edit_mode (card_row);
1582
var id1 = card_row.enter_edit_mode.connect_after ( () => {
1583
event_box.remove (l);
1584
var entry = new Entry ();
1585
entry.set_text (contact.display_name);
1586
entry.set_hexpand (true);
1588
entry.override_font (Pango.FontDescription.from_string ("16px"));
1589
event_box.add (entry);
1590
Utils.grab_widget_later (entry);
1592
entry.activate.connect_after ( () => {
1593
exit_edit_mode (true);
1595
entry.key_press_event.connect ( (key_event) => {
1596
if (key_event.keyval == Gdk.Key.Escape) {
1597
exit_edit_mode (false);
1605
var id2 = card_row.exit_edit_mode.connect ( (save) => {
1606
Entry entry = event_box.get_child () as Entry;
1607
bool changed = entry.get_text () != contact.display_name;
1609
if (save && changed) {
1610
// Things look better if we update immediately, rather than after the setting has
1612
l.set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", entry.get_text ()));
1614
Value v = Value (typeof (string));
1615
v.set_string (entry.get_text ());
1616
set_individual_property.begin (contact,
1620
set_individual_property.end (result);
1622
App.app.show_message (e.message);
1623
l.set_markup (Markup.printf_escaped ("<span font='16'>%s</span>", contact.display_name));
1628
event_box.remove (entry);
1632
var id3 = card_row.lost_child_focus.connect ( () => {
1633
if (editing_row == card_row)
1634
exit_edit_mode (true);
1637
event_box.destroy.connect ( () => {
1638
card_row.disconnect (id1);
1639
card_row.disconnect (id2);
1640
card_row.disconnect (id3);
1644
card_grid.attach (event_box, 1, 0, 1, 1);
1646
var merged_presence = contact.create_merged_presence_widget ();
1647
merged_presence.set_halign (Align.START);
1648
merged_presence.set_valign (Align.START);
1649
merged_presence.set_vexpand (true);
1650
card_grid.attach (merged_presence, 1, 1, 1, 1);
1652
var box = new Box (Orientation.HORIZONTAL, 0);
1653
box.set_margin_bottom (4 + 8);
1654
box.set_halign (Align.START);
1656
box.get_style_context ().add_class ("linked");
1657
box.set_homogeneous (true);
1658
box.set_halign (Align.FILL);
1659
var image = new Image.from_icon_name ("mail-unread-symbolic", IconSize.MENU);
1660
var b = new Button ();
1662
box.pack_start (b, true, true, 0);
1664
email_button.clicked.connect (send_email);
1666
image = new Image.from_icon_name ("user-available-symbolic", IconSize.MENU);
1669
box.pack_start (b, true, true, 0);
1671
chat_button.clicked.connect (start_chat);
1673
image = new Image.from_icon_name ("call-start-symbolic", IconSize.MENU);
1676
box.pack_start (b, true, true, 0);
1678
call_button.clicked.connect (start_call);
1680
card_grid.attach (box, 1, 2, 1, 1);
1682
card_grid.show_all ();
1687
public void update_buttons () {
1688
if (contact == null)
1691
var emails = contact.individual.email_addresses;
1692
email_button.set_sensitive (!emails.is_empty);
1694
var ims = contact.individual.im_addresses;
1695
var im_keys = ims.get_keys ();
1696
bool found_im = false;
1697
bool callable = false;
1698
PresenceType max_presence = 0;
1699
foreach (var protocol in im_keys) {
1700
foreach (var id in ims[protocol]) {
1701
var im_persona = contact.find_im_persona (protocol, id.value);
1702
if (im_persona != null) {
1703
var type = im_persona.presence_type;
1704
if (type != PresenceType.UNSET &&
1705
type != PresenceType.ERROR &&
1706
type != PresenceType.OFFLINE &&
1707
type != PresenceType.UNKNOWN) {
1709
if (type > max_presence)
1710
max_presence = type;
1714
if (contact.is_callable (protocol, id.value) != null)
1719
if (contacts_store.can_call) {
1720
var phones = contact.individual.phone_numbers;
1721
if (!phones.is_empty)
1727
icon = Contact.presence_to_icon_symbolic (max_presence);
1729
icon = "user-available-symbolic";
1730
(chat_button.get_child () as Image).set_from_icon_name (icon, IconSize.MENU);
1731
chat_button.set_sensitive (found_im);
1733
call_button.set_sensitive (callable);
1736
public signal void contacts_linked (string? main_contact, string linked_contact, LinkOperation operation);
1738
public void add_suggestion (Contact c) {
1739
var row = new FieldRow (row_group, this);
1740
personas_grid.add (row);
1742
var grid = new Grid ();
1743
grid.get_style_context ().add_class ("contacts-suggestion");
1744
grid.set_redraw_on_allocate (true);
1745
grid.draw.connect ( (cr) => {
1746
Allocation allocation;
1747
grid.get_allocation (out allocation);
1749
var context = grid.get_style_context ();
1750
context.render_background (cr,
1752
allocation.width, allocation.height);
1757
var image_frame = new ContactFrame (Contact.SMALL_AVATAR_SIZE);
1758
c.keep_widget_uptodate (image_frame, (w) => {
1759
(w as ContactFrame).set_image (c.individual, c);
1761
image_frame.set_hexpand (false);
1762
grid.attach (image_frame, 0, 0, 1, 2);
1764
var label = new Label ("");
1765
if (contact.is_main)
1766
label.set_markup (Markup.printf_escaped (_("Does %s from %s belong here?"), c.display_name, c.format_persona_stores ()));
1768
label.set_markup (Markup.printf_escaped (_("Do these details belong to %s?"), c.display_name));
1769
label.set_valign (Align.START);
1770
label.set_halign (Align.START);
1771
label.set_line_wrap (true);
1772
label.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
1773
label.set_hexpand (false);
1774
label.xalign = 0.0f;
1775
grid.attach (label, 1, 0, 1, 1);
1777
var bbox = new ButtonBox (Orientation.HORIZONTAL);
1778
var yes = new Button.with_label (_("Yes"));
1779
var no = new Button.with_label (_("No"));
1781
yes.clicked.connect ( () => {
1782
var linked_contact = c.display_name;
1783
link_contacts.begin (contact, c, (obj, result) => {
1784
var operation = link_contacts.end (result);
1785
this.contacts_linked (null, linked_contact, operation);
1790
no.clicked.connect ( () => {
1791
contacts_store.add_no_suggest_link (contact, c);
1792
/* TODO: Add undo */
1798
bbox.set_spacing (8);
1799
bbox.set_halign (Align.END);
1800
bbox.set_hexpand (true);
1801
bbox.set_border_width (4);
1802
grid.attach (bbox, 2, 0, 1, 2);
1805
private uint update_personas_timeout;
1806
public void update_personas (bool show_matches = true) {
1807
if (update_personas_timeout != 0) {
1808
Source.remove (update_personas_timeout);
1809
update_personas_timeout = 0;
1812
foreach (var w in personas_grid.get_children ()) {
1816
if (contact == null)
1819
var personas = contact.get_personas_for_display ();
1822
foreach (var p in personas) {
1823
var sheet = new PersonaSheet(this, p, i++);
1824
personas_grid.add (sheet);
1828
var matches = contact.store.aggregator.get_potential_matches (contact.individual, MatchResult.HIGH);
1829
foreach (var ind in matches.keys) {
1830
var c = Contact.from_individual (ind);
1831
if (c != null && contact.suggest_link_to (c)) {
1837
personas_grid.show_all ();
1840
public void show_contact (Contact? new_contact, bool edit=false, bool show_matches = true) {
1841
if (contact == new_contact)
1844
if (contact != null && editing_row != null)
1845
exit_edit_mode (true);
1847
if (contact != null) {
1848
contact.personas_changed.disconnect (personas_changed_cb);
1849
contact.changed.disconnect (contact_changed_cb);
1852
contact = new_contact;
1855
update_personas (show_matches);
1857
if (!show_matches) {
1858
update_personas_timeout = Gdk.threads_add_timeout (100, () => {
1864
bool can_remove = false;
1866
if (contact != null) {
1867
contact.personas_changed.connect (personas_changed_cb);
1868
contact.changed.connect (contact_changed_cb);
1870
can_remove = contact.can_remove_personas ();
1873
delete_menu_item.set_sensitive (can_remove);
1874
link_menu_item.set_sensitive (contact != null);
1877
private void personas_changed_cb (Contact contact) {
1881
private void contact_changed_cb (Contact contact) {
1885
public void enter_edit_mode (FieldRow row) {
1886
if (editing_row != row) {
1887
exit_edit_mode (true);
1889
if (row.enter_edit_mode ()) {
1891
editing_row.set_editing (true);
1896
public void exit_edit_mode (bool save) {
1897
if (editing_row != null) {
1898
editing_row.exit_edit_mode (save);
1899
editing_row.set_editing (false);
1905
private Dialog pick_one_dialog (string title, TreeModel model, out TreeSelection selection) {
1906
var dialog = new Dialog.with_buttons (title,
1907
(Window) this.get_toplevel (),
1908
DialogFlags.MODAL | DialogFlags.DESTROY_WITH_PARENT,
1909
Stock.CANCEL, ResponseType.CANCEL,
1910
Stock.OK, ResponseType.OK);
1912
dialog.set_resizable (false);
1913
dialog.set_default_response (ResponseType.OK);
1915
var tree_view = new TreeView ();
1916
tree_view.set_model (model);
1917
tree_view.set_headers_visible (false);
1918
tree_view.get_selection ().set_mode (SelectionMode.BROWSE);
1920
var column = new Gtk.TreeViewColumn ();
1921
tree_view.append_column (column);
1923
var renderer = new Gtk.CellRendererText ();
1924
column.pack_start (renderer, false);
1925
column.add_attribute (renderer, "text", 0);
1927
var scrolled = new ScrolledWindow(null, null);
1928
scrolled.set_size_request (340, 300);
1929
scrolled.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
1930
scrolled.set_vexpand (true);
1931
scrolled.set_hexpand (true);
1932
scrolled.set_shadow_type (ShadowType.IN);
1933
scrolled.add (tree_view);
1935
var grid = new Grid ();
1936
grid.set_orientation (Orientation.VERTICAL);
1937
grid.set_row_spacing (6);
1939
var l = new Label (title);
1940
l.set_halign (Align.START);
1943
grid.add (scrolled);
1945
var box = dialog.get_content_area () as Box;
1946
box.pack_start (grid, true, true, 0);
1947
grid.set_border_width (6);
1951
selection = tree_view.get_selection ();
1956
public void send_email () {
1957
var emails = contact.individual.email_addresses;
1958
if (emails.is_empty)
1960
if (emails.size == 1) {
1961
foreach (var email in emails) {
1962
var email_addr = email.value;
1963
Utils.compose_mail (email_addr);
1968
var store = new ListStore (1, typeof (string));
1969
foreach (var email in emails) {
1970
var email_addr = email.value;
1971
store.append (out iter);
1972
store.set (iter, 0, email_addr);
1975
TreeSelection selection;
1976
var dialog = pick_one_dialog (_("Select email address"), store, out selection);
1977
dialog.response.connect ( (response) => {
1978
if (response == ResponseType.OK) {
1982
if (selection.get_selected (null, out iter2)) {
1983
store.get (iter2, 0, out email2);
1984
Utils.compose_mail (email2);
1999
public void start_call () {
2000
var ims = contact.individual.im_addresses;
2001
var im_keys = ims.get_keys ();
2002
var call_targets = new ArrayList<CallValue?>();
2003
foreach (var protocol in im_keys) {
2004
foreach (var id in ims[protocol]) {
2005
var im_persona = contact.find_im_persona (protocol, id.value);
2006
if (im_persona != null &&
2007
contact.is_callable (protocol, id.value) != null) {
2008
var type = im_persona.presence_type;
2009
if (type != PresenceType.UNSET &&
2010
type != PresenceType.ERROR &&
2011
type != PresenceType.OFFLINE &&
2012
type != PresenceType.UNKNOWN) {
2013
CallValue? value = { null, protocol, id.value, Contact.format_im_name (im_persona, protocol, id.value) };
2014
call_targets.add (value);
2020
if (contacts_store.can_call) {
2021
var phones = contact.individual.phone_numbers;
2022
foreach (var phone in phones) {
2023
CallValue? value = { phone.value, null, null, phone.value };
2024
call_targets.add (value);
2029
if (call_targets.is_empty)
2032
if (call_targets.size == 1) {
2033
foreach (var value in call_targets) {
2034
if (value.phone_nr != null)
2035
Utils.start_call (value.phone_nr, this.contacts_store.calling_accounts);
2037
var account = contact.is_callable (value.protocol, value.id);
2038
Utils.start_call_with_account (value.id, account);
2042
var store = new ListStore (2, typeof (string), typeof (CallValue?));
2043
foreach (var value in call_targets) {
2045
store.append (out iter);
2046
store.set (iter, 0, value.name, 1, value);
2048
TreeSelection selection;
2049
var dialog = pick_one_dialog (_("Select what to call"), store, out selection);
2050
dialog.response.connect ( (response) => {
2051
if (response == ResponseType.OK) {
2055
if (selection.get_selected (null, out iter2)) {
2056
store.get (iter2, 1, out value2);
2057
if (value2.phone_nr != null)
2058
Utils.start_call (value2.phone_nr, this.contacts_store.calling_accounts);
2060
var account = contact.is_callable (value2.protocol, value2.id);
2061
Utils.start_call_with_account (value2.id, account);
2076
public void start_chat () {
2077
var ims = contact.individual.im_addresses;
2078
var im_keys = ims.get_keys ();
2079
var online_personas = new ArrayList<ImValue?>();
2080
if (contact != null) {
2081
foreach (var protocol in im_keys) {
2082
foreach (var id in ims[protocol]) {
2083
var im_persona = contact.find_im_persona (protocol, id.value);
2084
if (im_persona != null) {
2085
var type = im_persona.presence_type;
2086
if (type != PresenceType.UNSET &&
2087
type != PresenceType.ERROR &&
2088
type != PresenceType.OFFLINE &&
2089
type != PresenceType.UNKNOWN) {
2090
ImValue? value = { protocol, id.value, Contact.format_im_name (im_persona, protocol, id.value) };
2091
online_personas.add (value);
2098
if (online_personas.is_empty)
2101
if (online_personas.size == 1) {
2102
foreach (var value in online_personas) {
2103
Utils.start_chat (contact, value.protocol, value.id);
2106
var store = new ListStore (2, typeof (string), typeof (ImValue?));
2107
foreach (var value in online_personas) {
2109
store.append (out iter);
2110
store.set (iter, 0, value.name, 1, value);
2112
TreeSelection selection;
2113
var dialog = pick_one_dialog (_("Select chat account"), store, out selection);
2114
dialog.response.connect ( (response) => {
2115
if (response == ResponseType.OK) {
2119
if (selection.get_selected (null, out iter2)) {
2120
store.get (iter2, 1, out value2);
2121
Utils.start_chat (contact, value2.protocol, value2.id);
2129
public ContactPane (Store contacts_store) {
2130
this.get_style_context ().add_class ("contacts-content");
2131
this.set_shadow_type (ShadowType.IN);
2133
this.button_press_event.connect ( (e) => {
2134
exit_edit_mode (true);
2138
this.contacts_store = contacts_store;
2139
row_group = new RowGroup(3);
2140
row_group.set_column_min_width (0, 32);
2141
row_group.set_column_min_width (1, 400);
2142
row_group.set_column_max_width (1, 480);
2143
row_group.set_column_min_width (2, 32);
2144
row_group.set_column_spacing (0, 8);
2145
row_group.set_column_spacing (1, 8);
2146
row_group.set_column_priority (1, 1);
2148
card_row_group = row_group.copy ();
2149
/* This is kinda lame hardcoding so that the frame inside
2150
the button aligns with the other rows. It really
2151
depends on the theme, but there seems no good way to
2153
card_row_group.set_column_spacing (0, 0);
2155
this.set_hexpand (true);
2156
this.set_vexpand (true);
2157
this.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
2159
top_grid = new Grid ();
2160
top_grid.set_orientation (Orientation.VERTICAL);
2161
top_grid.set_margin_top (40);
2162
top_grid.set_margin_bottom (32);
2163
top_grid.set_row_spacing (20);
2164
this.add_with_viewport (top_grid);
2165
top_grid.set_focus_vadjustment (this.get_vadjustment ());
2167
var viewport = this.get_child ();
2168
viewport.button_press_event.connect ( (event) => {
2169
if (event.button == 3) {
2170
context_menu.popup (null, null, null, event.button, event.time);
2176
this.get_child().get_style_context ().add_class ("contacts-main-view");
2177
this.get_child().get_style_context ().add_class ("view");
2179
card_row = new FieldRow (card_row_group, this);
2180
top_grid.add (card_row);
2181
card_grid = new Grid ();
2182
card_grid.set_vexpand (false);
2183
card_row.pack (card_grid);
2185
personas_grid = new Grid ();
2186
personas_grid.set_orientation (Orientation.VERTICAL);
2187
personas_grid.set_row_spacing (40);
2188
top_grid.add (personas_grid);
2190
top_grid.show_all ();
2192
context_menu = new Gtk.Menu ();
2193
link_menu_item = Utils.add_menu_item (context_menu,_("Add/Remove Linked Contacts..."));
2194
link_menu_item.activate.connect (link_contact);
2195
link_menu_item.set_sensitive (false);
2196
//Utils.add_menu_item (context_menu,_("Send..."));
2197
delete_menu_item = Utils.add_menu_item (context_menu,_("Delete"));
2198
delete_menu_item.activate.connect (delete_contact);
2199
delete_menu_item.set_sensitive (false);
2201
contacts_store.quiescent.connect (() => {
2202
// Refresh the view when the store is quiescent as we may have missed
2203
// some potential matches while the store was still preparing.
2208
void link_contact () {
2209
var dialog = new LinkDialog (contact);
2210
dialog.contacts_linked.connect ( (main_contact, linked_contact, operation) => {
2211
this.contacts_linked (main_contact, linked_contact, operation);
2216
public signal void will_delete (Contact contact);
2218
void delete_contact () {
2219
if (contact != null) {
2221
this.will_delete (contact);