diff --git a/gajim/common/const.py b/gajim/common/const.py index 0f50872674a26a310be47e32939dbcc9e33f1f26..859efe0791ee79fc7599fe22219fa5d5d3bee94e 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -194,6 +194,7 @@ class URIType(Enum): WEB = 'web' FILE = 'file' AT = 'at' + TEL = 'tel' class URIAction(Enum): diff --git a/gajim/common/helpers.py b/gajim/common/helpers.py index 28d3fd8a0edde631d5232130955bbd707f5f9dc4..f8d942d9a52202b139e51fef63520235ed64715f 100644 --- a/gajim/common/helpers.py +++ b/gajim/common/helpers.py @@ -1063,6 +1063,10 @@ def parse_uri(uri): uri = uri[7:] return URI(type=URIType.MAIL, data=uri) + if uri.startswith('tel:'): + uri = uri[4:] + return URI(type=URIType.TEL, data=uri) + if app.interface.sth_at_sth_dot_sth_re.match(uri): return URI(type=URIType.AT, data=uri) @@ -1092,6 +1096,9 @@ def open_uri(uri, account=None): if uri.type == URIType.FILE: open_file(uri.data) + elif uri.type == URIType.TEL: + Gio.AppInfo.launch_default_for_uri(f'tel:{uri.data}') + elif uri.type == URIType.MAIL: Gio.AppInfo.launch_default_for_uri(f'mailto:{uri.data}') diff --git a/gajim/common/modules/__init__.py b/gajim/common/modules/__init__.py index c1d9e92fa071ecab26fb9fd2e5ce130e8dacda08..8205dfda7a6eea597bbd85e1592dff759351379d 100644 --- a/gajim/common/modules/__init__.py +++ b/gajim/common/modules/__init__.py @@ -71,6 +71,7 @@ 'user_mood', 'user_nickname', 'user_tune', + 'vcard4', 'vcard_avatars', 'vcard_temp', 'announce', diff --git a/gajim/common/modules/pubsub.py b/gajim/common/modules/pubsub.py index da3c5358f57a09bafb7ce1ff7602292bdeada8a2..e45a5dba49af5fa989bdfe20f686b62427eb7b22 100644 --- a/gajim/common/modules/pubsub.py +++ b/gajim/common/modules/pubsub.py @@ -35,6 +35,7 @@ class PubSub(BaseModule): 'delete', 'set_node_configuration', 'get_node_configuration', + 'get_access_model', ] def __init__(self, con): diff --git a/gajim/common/modules/user_avatar.py b/gajim/common/modules/user_avatar.py index f2564a02b048ba7dcbd50258a7ce808fc591719c..0eef8529a0958b247f9c2423049724e180f40106 100644 --- a/gajim/common/modules/user_avatar.py +++ b/gajim/common/modules/user_avatar.py @@ -29,6 +29,7 @@ class UserAvatar(BaseModule): 'request_avatar_metadata', 'request_avatar_data', 'set_avatar', + 'set_access_model' ] def __init__(self, con): diff --git a/gajim/common/modules/user_nickname.py b/gajim/common/modules/user_nickname.py index 5f0b38080aaff10089caae193d6173bbd6f9d718..93766b6e97d09aee1afa2cbc7f95b08bade9282b 100644 --- a/gajim/common/modules/user_nickname.py +++ b/gajim/common/modules/user_nickname.py @@ -30,6 +30,7 @@ class UserNickname(BaseModule): _nbxmpp_extends = 'Nickname' _nbxmpp_methods = [ 'set_nickname', + 'set_access_model', ] def __init__(self, con): diff --git a/gajim/common/modules/vcard4.py b/gajim/common/modules/vcard4.py new file mode 100644 index 0000000000000000000000000000000000000000..8d2b6972d6ff17fe02465cd4428e7580c61d29fc --- /dev/null +++ b/gajim/common/modules/vcard4.py @@ -0,0 +1,33 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. + +# XEP-0292: vCard4 Over XMPP + +from gajim.common.modules.base import BaseModule + + +class VCard4(BaseModule): + + _nbxmpp_extends = 'VCard4' + _nbxmpp_methods = [ + 'request_vcard', + 'set_vcard', + ] + + def __init__(self, con): + BaseModule.__init__(self, con) + + +def get_instance(*args, **kwargs): + return VCard4(*args, **kwargs), 'VCard4' diff --git a/gajim/data/gui/profile.ui b/gajim/data/gui/profile.ui new file mode 100644 index 0000000000000000000000000000000000000000..fd640265a4daf1b60938c269d566a23247e0b7c8 --- /dev/null +++ b/gajim/data/gui/profile.ui @@ -0,0 +1,446 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.36.0 --> +<interface> + <requires lib="gtk+" version="3.24"/> + <object class="GtkPopover" id="privacy_popover"> + <property name="can_focus">False</property> + <child> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <property name="column_spacing">12</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Privacy</property> + <style> + <class name="bold"/> + </style> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + <property name="width">3</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">Contact Infos</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">Picture and Name</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="margin_bottom">6</property> + <property name="label" translatable="yes">Make your profile visible for everyone or just for your contacts.</property> + <property name="wrap">True</property> + <property name="max_width_chars">42</property> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + <property name="width">3</property> + </packing> + </child> + <child> + <object class="GtkSwitch" id="avatar_nick_access"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">start</property> + <property name="active">True</property> + <signal name="notify::active" handler="_access_switch_toggled" swapped="no"/> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkSwitch" id="vcard_access"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="halign">start</property> + <property name="active">True</property> + <signal name="notify::active" handler="_access_switch_toggled" swapped="no"/> + </object> + <packing> + <property name="left_attach">2</property> + <property name="top_attach">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="avatar_nick_access_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">Everyone</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">2</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="vcard_access_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">end</property> + <property name="label" translatable="yes">Everyone</property> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">3</property> + </packing> + </child> + <style> + <class name="padding-6"/> + </style> + </object> + </child> + </object> + <object class="GtkStack" id="profile_stack"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkSpinner" id="spinner"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="active">True</property> + </object> + <packing> + <property name="name">spinner</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkScrolledWindow" id="scrolled"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="overlay_scrolling">False</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox" id="profile_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkOverlay" id="avatar_overlay"> + <property name="width_request">100</property> + <property name="height_request">100</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <child> + <object class="GtkImage" id="avatar_image"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">avatar-default-symbolic</property> + <property name="icon_size">6</property> + </object> + <packing> + <property name="index">1</property> + </packing> + </child> + <child type="overlay"> + <object class="GtkButton" id="remove_avatar_button"> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="no_show_all">True</property> + <property name="tooltip_text" translatable="yes">Remove your profile picture</property> + <property name="halign">start</property> + <property name="valign">end</property> + <property name="always_show_image">True</property> + <signal name="clicked" handler="_on_remove_avatar" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">user-trash-symbolic</property> + </object> + </child> + </object> + </child> + <child type="overlay"> + <object class="GtkButton" id="edit_avatar_button"> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="no_show_all">True</property> + <property name="tooltip_text" translatable="yes">Change your profile picture</property> + <property name="halign">end</property> + <property name="valign">end</property> + <property name="always_show_image">True</property> + <signal name="clicked" handler="_on_edit_avatar" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">document-edit-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="index">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="nickname_entry"> + <property name="name">NicknameEntry</property> + <property name="width_request">250</property> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">Enter your nickname. This is how your name is displayed to your contacts.</property> + <property name="halign">center</property> + <property name="xalign">0.5</property> + <property name="placeholder_text" translatable="yes">Nickname</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <style> + <class name="padding-18"/> + </style> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkActionBar"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkButton" id="cancel_button"> + <property name="label" translatable="yes">_Cancel</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="no_show_all">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_cancel_clicked" swapped="no"/> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child type="center"> + <object class="GtkMenuButton" id="add_entry_button"> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="no_show_all">True</property> + <property name="use_popover">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">list-add-symbolic</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Add Entry</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="privacy_button"> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="no_show_all">True</property> + <property name="tooltip_text" translatable="yes">Privacy</property> + <property name="direction">up</property> + <property name="popover">privacy_popover</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">preferences-system-privacy-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="save_button"> + <property name="label" translatable="yes">_Save</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="no_show_all">True</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_save_clicked" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="edit_button"> + <property name="label" translatable="yes">_Edit</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_edit_clicked" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">profile</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox" id="avatar_selector_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="spacing">12</property> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <signal name="clicked" handler="_on_cancel_update_avatar" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="avatar_update_button"> + <property name="label" translatable="yes">Update</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <signal name="clicked" handler="_on_update_avatar" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="name">avatar_selector</property> + <property name="position">2</property> + </packing> + </child> + <style> + <class name="padding-18"/> + </style> + </object> +</interface> diff --git a/gajim/data/gui/profile_window.ui b/gajim/data/gui/profile_window.ui deleted file mode 100644 index 68cdae7237e85e75414e72b45e70b35cd11b16a9..0000000000000000000000000000000000000000 --- a/gajim/data/gui/profile_window.ui +++ /dev/null @@ -1,1405 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.1 --> -<interface> - <requires lib="gtk+" version="3.18"/> - <object class="GtkBox" id="profile_box"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkNotebook" id="information_notebook"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">6</property> - <property name="margin_right">6</property> - <property name="margin_top">6</property> - <property name="margin_bottom">6</property> - <property name="vexpand">True</property> - <signal name="switch-page" handler="on_information_notebook_switch_page" swapped="no"/> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">18</property> - <property name="margin_right">18</property> - <property name="margin_top">18</property> - <property name="margin_bottom">18</property> - <property name="orientation">vertical</property> - <property name="spacing">18</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="spacing">18</property> - <child> - <object class="GtkBox"> - <property name="width_request">150</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkButton" id="PHOTO_button"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <signal name="clicked" handler="_on_set_avatar_clicked" swapped="no"/> - <child> - <object class="GtkOverlay"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkImage" id="PHOTO_image"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="stock">gtk-missing-image</property> - </object> - <packing> - <property name="index">-1</property> - </packing> - </child> - <child type="overlay"> - <object class="GtkButton" id="remove_avatar"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">True</property> - <property name="no_show_all">True</property> - <property name="tooltip_text" translatable="yes">Clear Avatar</property> - <property name="halign">end</property> - <property name="valign">start</property> - <property name="margin_left">6</property> - <property name="margin_right">6</property> - <property name="margin_top">6</property> - <property name="margin_bottom">6</property> - <property name="always_show_image">True</property> - <signal name="clicked" handler="_clear_photo" swapped="no"/> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="icon_name">window-close</property> - </object> - </child> - <style> - <class name="circular"/> - </style> - </object> - </child> - </object> - </child> - <style> - <class name="image-button"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="NOPHOTO_button"> - <property name="width_request">140</property> - <property name="height_request">140</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="receives_default">False</property> - <property name="no_show_all">True</property> - <property name="halign">start</property> - <property name="valign">start</property> - <property name="use_underline">True</property> - <signal name="clicked" handler="_on_set_avatar_clicked" swapped="no"/> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="valign">center</property> - <property name="orientation">vertical</property> - <property name="spacing">18</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_top">10</property> - <property name="icon_name">avatar-default</property> - <property name="icon_size">6</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Set Avatar…</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - </child> - <style> - <class name="image-button"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <child> - <object class="GtkLabel" id="label24"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Full Name</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="NICKNAME_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="FN_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label20"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Nickname</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="margin_top">6</property> - <property name="margin_bottom">12</property> - <property name="label" translatable="yes">Account</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="account_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="margin_left">2</property> - <property name="margin_top">6</property> - <property name="margin_bottom">12</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="grid1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <child> - <object class="GtkEntry" id="URL_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - <property name="width">3</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="EMAIL_HOME_USERID_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - <property name="width">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label23"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Phone No.</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="TEL_HOME_NUMBER_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="BDAY_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <signal name="focus-out-event" handler="on_BDAY_entry_focus_out_event" swapped="no"/> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkEventBox" id="eventbox1"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="tooltip_text" translatable="yes">Format: YYYY-MM-DD</property> - <property name="visible_window">False</property> - <child> - <object class="GtkLabel" id="label13"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Birthday</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - </child> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label22"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Homepage</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label57"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">E-Mail</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label2"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">XMPP Address</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="JABBERID_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - <property name="width">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - </child> - <child type="tab"> - <object class="GtkLabel" id="label4"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Main</property> - </object> - <packing> - <property name="tab_fill">False</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="grid4"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="valign">start</property> - <property name="margin_left">18</property> - <property name="margin_right">18</property> - <property name="margin_top">18</property> - <property name="margin_bottom">18</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <child> - <object class="GtkLabel" id="label18"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes" comments="Family Name">Family</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="N_FAMILY_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label17"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes" comments="Given Name">Given</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="N_GIVEN_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label16"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes" comments="Middle Name">Middle</property> - <property name="use_markup">True</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label15"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes" comments="Prefix in Name">Prefix</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="N_MIDDLE_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="N_PREFIX_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label14"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes" comments="Suffix in Name">Suffix</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="N_SUFFIX_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label31"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Street</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">5</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_HOME_STREET_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">5</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label29"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Extra Address</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">5</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_HOME_EXTADR_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">5</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label28"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">City</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">6</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label27"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">State</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">7</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_HOME_REGION_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">7</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_HOME_LOCALITY_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">6</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_HOME_PCODE_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">6</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label26"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Postal Code</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">6</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_HOME_CTRY_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">7</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label25"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Country</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">7</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="margin_top">12</property> - <property name="label" translatable="yes"><b>Address</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">4</property> - <property name="width">5</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">12</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes"><b>Name Details</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">5</property> - </packing> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="position">1</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Personal</property> - </object> - <packing> - <property name="position">1</property> - <property name="tab_fill">False</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">18</property> - <property name="margin_right">18</property> - <property name="margin_top">18</property> - <property name="margin_bottom">18</property> - <property name="orientation">vertical</property> - <property name="spacing">18</property> - <child> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <child> - <object class="GtkLabel" id="label44"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Company</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ORG_ORGNAME_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label43"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Department</property> - <property name="use_markup">True</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ORG_ORGUNIT_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hexpand">True</property> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label42"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Position</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="TITLE_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ROLE_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label41"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Role</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="grid2"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="valign">start</property> - <property name="row_spacing">6</property> - <property name="column_spacing">12</property> - <child> - <object class="GtkLabel" id="label32"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">E-Mail</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">5</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="EMAIL_WORK_USERID_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">5</property> - <property name="width">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label33"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Phone No.</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">6</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="TEL_WORK_NUMBER_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">6</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label39"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Street</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_WORK_STREET_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label38"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Extra Address</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_WORK_EXTADR_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label37"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">City</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_WORK_LOCALITY_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label35"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Postal Code</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_WORK_PCODE_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">2</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_WORK_REGION_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">2</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label36"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">State</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkEntry" id="ADR_WORK_CTRY_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - </object> - <packing> - <property name="left_attach">4</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="label34"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="label" translatable="yes">Country</property> - <property name="use_markup">True</property> - <property name="justify">right</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="left_attach">3</property> - <property name="top_attach">3</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes"><b>Address</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">0</property> - <property name="width">5</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="margin_top">12</property> - <property name="label" translatable="yes"><b>Contact</b></property> - <property name="use_markup">True</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">4</property> - <property name="width">5</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="width_request">12</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - </object> - <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> - </packing> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="position">2</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="label5"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes">Work</property> - </object> - <packing> - <property name="position">2</property> - <property name="tab_fill">False</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow" id="scrolledwindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="margin_left">6</property> - <property name="margin_right">6</property> - <property name="margin_top">6</property> - <property name="margin_bottom">6</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkTextView" id="DESC_textview"> - <property name="height_request">70</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="wrap_mode">word</property> - <property name="left_margin">6</property> - <property name="right_margin">6</property> - <property name="top_margin">6</property> - </object> - </child> - </object> - <packing> - <property name="position">3</property> - </packing> - </child> - <child type="tab"> - <object class="GtkLabel" id="label6"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="label" translatable="yes" comments=""About" is the text of a tab of vcard window">About</property> - </object> - <packing> - <property name="position">3</property> - <property name="tab_fill">False</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="hbox2"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="margin_left">6</property> - <property name="margin_right">6</property> - <property name="margin_top">6</property> - <property name="margin_bottom">6</property> - <property name="spacing">12</property> - <child> - <object class="GtkStatusbar" id="statusbar"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <style> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkProgressBar" id="progressbar"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="valign">center</property> - <property name="pulse_step">0.10000000149</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButtonBox" id="information_hbuttonbox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">end</property> - <property name="hexpand">True</property> - <property name="spacing">12</property> - <property name="layout_style">end</property> - <child> - <object class="GtkButton" id="cancel_button"> - <property name="label">gtk-cancel</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> - <property name="use_stock">True</property> - <signal name="clicked" handler="on_cancel_button_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="ok_button"> - <property name="label">gtk-ok</property> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="receives_default">False</property> - <property name="use_stock">True</property> - <signal name="clicked" handler="on_ok_button_clicked" swapped="no"/> - <style> - <class name="suggested-action"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> -</interface> diff --git a/gajim/data/icons/hicolor/scalable/devices/feather-briefcase-symbolic.svg b/gajim/data/icons/hicolor/scalable/devices/feather-briefcase-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..c5e5c836ed93e745e739efac5aa6df5d0c614ed8 --- /dev/null +++ b/gajim/data/icons/hicolor/scalable/devices/feather-briefcase-symbolic.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="feather feather-briefcase" + version="1.1" + id="svg793" + sodipodi:docname="feather-briefcase-symbolic.svg" + inkscape:version="1.0.1 (0767f8302a, 2020-10-17)"> + <metadata + id="metadata799"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs797" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1916" + inkscape:window-height="996" + id="namedview795" + showgrid="false" + inkscape:zoom="50.125" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="0" + inkscape:window-y="82" + inkscape:window-maximized="0" + inkscape:current-layer="svg793" /> + <path + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" + d="M 4 6 C 2.3552972 6 1 7.3552972 1 9 L 1 19 C 1 20.644703 2.3552972 22 4 22 L 20 22 C 21.644703 22 23 20.644703 23 19 L 23 9 C 23 7.3552972 21.644703 6 20 6 L 4 6 z M 4 8 L 20 8 C 20.571297 8 21 8.4287028 21 9 L 21 19 C 21 19.571297 20.571297 20 20 20 L 4 20 C 3.4287028 20 3 19.571297 3 19 L 3 9 C 3 8.4287028 3.4287028 8 4 8 z " + id="path804" /> + <path + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" + d="M 10 2 C 8.3549904 2 7 3.3549904 7 5 L 7 21 A 1 1 0 0 0 8 22 A 1 1 0 0 0 9 21 L 9 5 C 9 4.4358706 9.4358706 4 10 4 L 14 4 C 14.564129 4 15 4.4358706 15 5 L 15 21 A 1 1 0 0 0 16 22 A 1 1 0 0 0 17 21 L 17 5 C 17 3.3549904 15.64501 2 14 2 L 10 2 z " + id="path808" /> +</svg> diff --git a/gajim/data/icons/hicolor/scalable/devices/feather-home-symbolic.svg b/gajim/data/icons/hicolor/scalable/devices/feather-home-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..a722accee7b0978a769aafe9bd7650c6c86e8e28 --- /dev/null +++ b/gajim/data/icons/hicolor/scalable/devices/feather-home-symbolic.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="16" + height="16" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + class="feather feather-home" + version="1.1" + id="svg858" + sodipodi:docname="feather-home-symbolic.svg" + inkscape:version="1.0.1 (0767f8302a, 2020-10-17)"> + <metadata + id="metadata864"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs862" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1916" + inkscape:window-height="996" + id="namedview860" + showgrid="false" + inkscape:zoom="50.125" + inkscape:cx="8" + inkscape:cy="8" + inkscape:window-x="0" + inkscape:window-y="82" + inkscape:window-maximized="0" + inkscape:current-layer="svg858" /> + <path + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" + d="M 11.988281 1 A 1.0001 1.0001 0 0 0 11.386719 1.2109375 L 2.3867188 8.2109375 A 1.0001 1.0001 0 0 0 2 9 L 2 20 C 2 21.64501 3.3549904 23 5 23 L 19 23 C 20.64501 23 22 21.64501 22 20 L 22 9 A 1.0001 1.0001 0 0 0 21.613281 8.2109375 L 12.613281 1.2109375 A 1.0001 1.0001 0 0 0 11.988281 1 z M 12 3.2675781 L 20 9.4882812 L 20 20 C 20 20.564129 19.564129 21 19 21 L 5 21 C 4.4358706 21 4 20.564129 4 20 L 4 9.4882812 L 12 3.2675781 z " + id="path873" /> + <path + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;opacity:1" + d="M 9 11 A 1.0001 1.0001 0 0 0 8 12 L 8 22 A 1 1 0 0 0 9 23 A 1 1 0 0 0 10 22 L 10 13 L 14 13 L 14 22 A 1 1 0 0 0 15 23 A 1 1 0 0 0 16 22 L 16 12 A 1.0001 1.0001 0 0 0 15 11 L 9 11 z " + id="path869" /> +</svg> diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index b9ccfcd639f2aca5329439005c8032a32e4df81d..fc78bebc5838b91b9286d557f21b54b7aef37cfc 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -35,6 +35,34 @@ .link-button { min-height: 0px; } /* VCardWindow */ .VCard-GtkLinkButton { padding-left: 5px; border-left: none; } +#NicknameEntry:disabled { + font-size: 28px; + font-weight: bold; + border: none; + padding: 0px 0px 0px 0px; + background-color: @theme_unfocused_bg_color; + color: @theme_fg_color; +} + +#ProfileWindow grid { padding: 18px; } +#ProfileWindow grid > button { + padding-left: 4px; + padding-right: 4px; +} +#ProfileWindow popover { padding: 6px; } +#ProfileWindow actionbar box { + padding: 0px; +} +#ProfileWindow actionbar button { + margin-top: 12px; +} + +.profile-scrolled { + border: 1px solid; + border-radius: 4px; + border-color: @borders; +} + /* Emoticon Popover */ popover#EmoticonPopover button { background: none; border: none; box-shadow:none; padding: 0px;} popover#EmoticonPopover button > label { font-size: 24px; } @@ -265,6 +293,7 @@ .large-header { font-size: 20px; font-weight: bold; } .insensitive-fg-color {color: @insensitive_fg_color;} /* Padding/Margins */ +.margin-right18 { margin-right: 18px; } .margin-top6 { margin-top: 6px; } .margin-top12 { margin-top: 12px; } .margin-3 { margin: 3px; } diff --git a/gajim/gtk/avatar.py b/gajim/gtk/avatar.py index f59bf8ac12432c3c101d07ad468b192d7a2aadcf..30d4a70bbf89e7a853278fc902c4380c308656b7 100644 --- a/gajim/gtk/avatar.py +++ b/gajim/gtk/avatar.py @@ -198,25 +198,26 @@ def __init__(self): def invalidate_cache(self, jid): self._cache.pop(jid, None) - def get_pixbuf(self, contact, size, scale, show=None): - surface = self.get_surface(contact, size, scale, show) + def get_pixbuf(self, contact, size, scale, show=None, default=False): + surface = self.get_surface(contact, size, scale, show, default) return Gdk.pixbuf_get_from_surface(surface, 0, 0, size, size) - def get_surface(self, contact, size, scale, show=None): + def get_surface(self, contact, size, scale, show=None, default=False): jid = contact.jid if contact.is_gc_contact: jid = contact.get_full_jid() - surface = self._cache[jid].get((size, scale, show)) - if surface is not None: - return surface + if not default: + surface = self._cache[jid].get((size, scale, show)) + if surface is not None: + return surface - surface = self._get_avatar_from_storage(contact, size, scale) - if surface is not None: - if show is not None: - surface = add_status_to_avatar(surface, show) - self._cache[jid][(size, scale, show)] = surface - return surface + surface = self._get_avatar_from_storage(contact, size, scale) + if surface is not None: + if show is not None: + surface = add_status_to_avatar(surface, show) + self._cache[jid][(size, scale, show)] = surface + return surface name = contact.get_shown_name() # Use nickname for group chats and bare JID for single contacts @@ -233,19 +234,20 @@ def get_surface(self, contact, size, scale, show=None): self._cache[jid][(size, scale, show)] = surface return surface - def get_muc_surface(self, account, jid, size, scale): - surface = self._cache[jid].get((size, scale)) - if surface is not None: - return surface - - avatar_sha = app.storage.cache.get_muc_avatar_sha(jid) - if avatar_sha is not None: - surface = self.surface_from_filename(avatar_sha, size, scale) - if surface is None: - return None - surface = clip_circle(surface) - self._cache[jid][(size, scale)] = surface - return surface + def get_muc_surface(self, account, jid, size, scale, default=False): + if not default: + surface = self._cache[jid].get((size, scale)) + if surface is not None: + return surface + + avatar_sha = app.storage.cache.get_muc_avatar_sha(jid) + if avatar_sha is not None: + surface = self.surface_from_filename(avatar_sha, size, scale) + if surface is None: + return None + surface = clip_circle(surface) + self._cache[jid][(size, scale)] = surface + return surface con = app.connections[account] name = get_groupchat_name(con, jid) diff --git a/gajim/gtk/avatar_selector.py b/gajim/gtk/avatar_selector.py index b16ade8428b9de4c4e2dd08842b25dd7ad0064c2..32d18eb4273ae47aba05f2805907ed858baa91a5 100755 --- a/gajim/gtk/avatar_selector.py +++ b/gajim/gtk/avatar_selector.py @@ -85,6 +85,8 @@ def __init__(self): self._helper_label.get_style_context().add_class('bold') self._helper_label.get_style_context().add_class('dim-label') self._helper_label.set_vexpand(True) + self._helper_label.set_no_show_all(True) + self._helper_label.show() self.add(self._helper_label) self.show_all() @@ -129,24 +131,25 @@ def _scale_for_publish(pixbuf): pixbuf = pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR) - return pixbuf + return pixbuf, width, height def get_avatar_surface(self): pixbuf = self._crop_area.get_pixbuf() if pixbuf is None: return None - scaled = self._scale_for_publish(pixbuf) + scaled, width, height = self._scale_for_publish(pixbuf) return Gdk.cairo_surface_create_from_pixbuf( - scaled, self.get_scale_factor()) + scaled, self.get_scale_factor()), width, height def get_avatar_bytes(self): pixbuf = self._crop_area.get_pixbuf() if pixbuf is None: return None - scaled = self._scale_for_publish(pixbuf) + scaled, width, height = self._scale_for_publish(pixbuf) - return scaled.save_to_bufferv('png', [], []) + success, data = scaled.save_to_bufferv('png', [], []) + return success, data, width, height class CropArea(Gtk.DrawingArea): diff --git a/gajim/gtk/profile.py b/gajim/gtk/profile.py index e7c8880a81dfc9c5e3b1d86c530a23c7c3f91f02..58be0834ff71a8876620ea762c24f4ad075f6035 100644 --- a/gajim/gtk/profile.py +++ b/gajim/gtk/profile.py @@ -1,369 +1,307 @@ -# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org> -# Copyright (C) 2005-2006 Nikos Kouremenos <kourem AT gmail.com> -# Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org> -# -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see <http://www.gnu.org/licenses/>. - -import time import logging -from gi.repository import Gtk +from gi.repository import Gio from gi.repository import Gdk +from gi.repository import Gtk from gi.repository import GLib -from nbxmpp.errors import is_error -from nbxmpp.modules.vcard_temp import VCard +from nbxmpp.errors import StanzaError +from nbxmpp.namespaces import Namespace +from nbxmpp.modules.vcard4 import VCard +from nbxmpp.modules.user_avatar import Avatar from gajim.common import app -from gajim.common.i18n import _ from gajim.common.const import AvatarSize -from gajim.common.modules.util import as_task - -from gajim import gtkgui_helpers - -from .dialogs import ErrorDialog -from .dialogs import InformationDialog -from .util import get_builder -from .filechoosers import AvatarChooserDialog - - -log = logging.getLogger('gajim.profile') +from gajim.common.i18n import _ +from gajim.common.i18n import Q_ + +from gajim.gui.avatar import clip_circle +from gajim.gui.avatar_selector import AvatarSelector +from gajim.gui.filechoosers import AvatarChooserDialog +from gajim.gui.util import get_builder +from gajim.gui.vcard_grid import VCardGrid +from gajim.gui.util import scroll_to_end + +log = logging.getLogger('gajim.gui.profile') + +MENU_DICT = { + 'fn': _('Full Name'), + 'bday': _('Birthday'), + 'gender': _('Gender'), + 'adr': _('Address'), + 'email': _('Email'), + 'impp': 'IM Address', + 'tel': _('Phone No.'), + 'org': Q_('?profile:Organisation'), + 'title': Q_('?profile:Title'), + 'role': Q_('?profile:Role'), + 'url': _('URL'), + 'key': Q_('?profile:Key'), +} class ProfileWindow(Gtk.ApplicationWindow): - def __init__(self, account): + def __init__(self, account, *args): Gtk.ApplicationWindow.__init__(self) self.set_application(app.app) self.set_position(Gtk.WindowPosition.CENTER) self.set_show_menubar(False) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + self.set_resizable(True) + self.set_default_size(700, 600) + self.set_name('ProfileWindow') self.set_title(_('Profile')) - self.connect('destroy', self.on_profile_window_destroy) - self.connect('key-press-event', self.on_profile_window_key_press_event) - - self.xml = get_builder('profile_window.ui') - self.add(self.xml.get_object('profile_box')) - self.progressbar = self.xml.get_object('progressbar') - self.statusbar = self.xml.get_object('statusbar') - self.context_id = self.statusbar.get_context_id('profile') - self.account = account - self.jid = app.get_jid_from_account(account) - account_label = app.settings.get_account_setting(account, - 'account_label') - self.set_value('account_label', account_label) - - self.dialog = None - self.avatar_mime_type = None - self._avatar_bytes = None - self.message_id = self.statusbar.push(self.context_id, - _('Retrieving profile…')) - self.update_progressbar_timeout_id = GLib.timeout_add( - 100, self.update_progressbar) - self.remove_statusbar_timeout_id = None - - self.xml.connect_signals(self) - - self.show_all() - self.xml.get_object('ok_button').grab_focus() - self._request_vcard() + self._jid = app.get_jid_from_account(account) - def on_information_notebook_switch_page(self, widget, page, page_num): - GLib.idle_add(self.xml.get_object('ok_button').grab_focus) + self._ui = get_builder('profile.ui') - def update_progressbar(self): - self.progressbar.pulse() - return True + menu = Gio.Menu() + for action, label in MENU_DICT.items(): + menu.append(label, 'win.add-' + action.lower()) - def remove_statusbar(self, message_id): - self.statusbar.remove(self.context_id, message_id) - self.remove_statusbar_timeout_id = None + self._ui.add_entry_button.set_menu_model(menu) + self._add_actions() - def on_profile_window_destroy(self, widget): - app.cancel_tasks(self) - if self.update_progressbar_timeout_id is not None: - GLib.source_remove(self.update_progressbar_timeout_id) - if self.remove_statusbar_timeout_id is not None: - GLib.source_remove(self.remove_statusbar_timeout_id) + self._avatar_selector = None + self._current_avatar = None + self._avatar_nick_public = None - if self.dialog: # Image chooser dialog - self.dialog.destroy() + # False - no change to avatar + # None - we want to delete the avatar + # Avatar - upload new avatar + self._new_avatar = False - def on_profile_window_key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: - self.destroy() + self._ui.nickname_entry.set_text(app.nicks[account]) - def _clear_photo(self, widget): - # empty the image - button = self.xml.get_object('PHOTO_button') - image = self.xml.get_object('PHOTO_image') - image.set_from_pixbuf(None) - button.hide() - text_button = self.xml.get_object('NOPHOTO_button') - text_button.show() - self._avatar_bytes = None - self.avatar_mime_type = None - - def _on_set_avatar_clicked(self, _button): - def on_ok(path_to_file): - data, sha = app.interface.avatar_storage.prepare_for_publish( - path_to_file) - if sha is None: - ErrorDialog( - _('Could not load image'), transient_for=self) - return - - scale = self.get_scale_factor() - surface = app.interface.avatar_storage.surface_from_filename( - sha, AvatarSize.VCARD, scale) - - button = self.xml.get_object('PHOTO_button') - image = self.xml.get_object('PHOTO_image') - image.set_from_surface(surface) - button.show() - text_button = self.xml.get_object('NOPHOTO_button') - text_button.hide() - - self._avatar_bytes = data - self.avatar_mime_type = 'image/png' - - AvatarChooserDialog(on_ok, transient_for=self) - - def on_BDAY_entry_focus_out_event(self, widget, event): - txt = widget.get_text() - if not txt: - return - try: - time.strptime(txt, '%Y-%m-%d') - except ValueError: - if not widget.is_focus(): - pritext = _('Wrong date format') - ErrorDialog( - pritext, - _('Format of the date must be YYYY-MM-DD'), - transient_for=self) - GLib.idle_add(widget.grab_focus) - return True - - def set_value(self, entry_name, value): - try: - widget = self.xml.get_object(entry_name) - val = widget.get_text() - if val: - value = val + ' / ' + value - widget.set_text(value) - except AttributeError: - pass - - def _set_avatar(self, vcard): - avatar, _ = vcard.get_avatar() - - button = self.xml.get_object('PHOTO_button') - image = self.xml.get_object('PHOTO_image') - text_button = self.xml.get_object('NOPHOTO_button') - - if avatar is None: - image.set_from_pixbuf(None) - button.hide() - text_button.show() - return + self._profile = VCardGrid(self.account) + self._ui.profile_box.add(self._profile) - try: - surface = self._get_surface_from_data(avatar) - except Exception: - log.exception('Error while loading avatar') - image.set_from_pixbuf(None) - button.hide() - text_button.show() - return + self.add(self._ui.profile_stack) + self.show_all() - self._avatar_bytes = avatar - self.avatar_mime_type = vcard.data['PHOTO']['TYPE'] + self._load_avatar() - image.set_from_surface(surface) - button.show() - text_button.hide() + client = app.get_client(account) + client.get_module('VCard4').request_vcard( + callback=self._on_vcard_received) - def _get_surface_from_data(self, data): - scale = self.get_scale_factor() - pixbuf = gtkgui_helpers.scale_pixbuf_from_data(data, AvatarSize.VCARD) - return Gdk.cairo_surface_create_from_pixbuf(pixbuf, scale) - - def set_values(self, vcard_): - for i in vcard_.keys(): - if i == 'PHOTO': - continue - if i in ('ADR', 'TEL', 'EMAIL'): - for entry in vcard_[i]: - add_on = '_HOME' - if 'WORK' in entry: - add_on = '_WORK' - for j in entry.keys(): - self.set_value(i + add_on + '_' + j + '_entry', entry[j]) - if isinstance(vcard_[i], dict): - for j in vcard_[i].keys(): - self.set_value(i + '_' + j + '_entry', vcard_[i][j]) - else: - if i == 'DESC': - self.xml.get_object('DESC_textview').get_buffer().set_text( - vcard_[i], len(vcard_[i].encode('utf-8'))) - else: - self.set_value(i + '_entry', vcard_[i]) - if self.update_progressbar_timeout_id is not None: - if self.message_id: - self.statusbar.remove(self.context_id, self.message_id) - self.message_id = self.statusbar.push( - self.context_id, _('Information received')) - self.remove_statusbar_timeout_id = GLib.timeout_add_seconds( - 3, self.remove_statusbar, self.message_id) - GLib.source_remove(self.update_progressbar_timeout_id) - self.progressbar.hide() - self.progressbar.set_fraction(0) - self.update_progressbar_timeout_id = None - - def add_to_vcard(self, vcard_, entry, txt): - """ - Add an information to the vCard dictionary - """ - entries = entry.split('_') - loc = vcard_ - if len(entries) == 3: # We need to use lists - if entries[0] not in loc: - loc[entries[0]] = [] - - for e in loc[entries[0]]: - if entries[1] in e: - e[entries[2]] = txt - break - else: - loc[entries[0]].append({entries[1]: '', entries[2]: txt}) - return vcard_ - while len(entries) > 1: - if entries[0] not in loc: - loc[entries[0]] = {} - loc = loc[entries[0]] - del entries[0] - loc[entries[0]] = txt - return vcard_ - - def make_vcard(self): - """ - Make the vCard dictionary - """ - entries = [ - 'FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'JABBERID', 'URL', - 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', - 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', - 'ADR_HOME_REGION', 'ADR_HOME_PCODE', 'ADR_HOME_CTRY', 'ORG_ORGNAME', - 'ORG_ORGUNIT', 'TITLE', 'ROLE', 'TEL_WORK_NUMBER', 'EMAIL_WORK_USERID', - 'ADR_WORK_STREET', 'ADR_WORK_EXTADR', 'ADR_WORK_LOCALITY', - 'ADR_WORK_REGION', 'ADR_WORK_PCODE', 'ADR_WORK_CTRY'] - vcard_ = {} - for e in entries: - txt = self.xml.get_object(e + '_entry').get_text() - if txt != '': - vcard_ = self.add_to_vcard(vcard_, e, txt) - - # DESC textview - buff = self.xml.get_object('DESC_textview').get_buffer() - start_iter = buff.get_start_iter() - end_iter = buff.get_end_iter() - txt = buff.get_text(start_iter, end_iter, False) - if txt != '': - vcard_['DESC'] = txt - - # Avatar - vcard = VCard(vcard_) - if self._avatar_bytes: - vcard.set_avatar(self._avatar_bytes, self.avatar_mime_type) - return vcard - - def on_ok_button_clicked(self, widget): - if self.update_progressbar_timeout_id: - # Operation in progress - return - if not app.account_is_available(self.account): - ErrorDialog( - _('You are not connected to the server'), - _('Without a connection, you can not publish your contact ' - 'information.'), - transient_for=self) - return + client.get_module('PubSub').get_access_model( + Namespace.VCARD4_PUBSUB, + callback=self._on_access_model_received, + user_data=Namespace.VCARD4_PUBSUB) - vcard_ = self.make_vcard() + client.get_module('PubSub').get_access_model( + Namespace.AVATAR_METADATA, + callback=self._on_access_model_received, + user_data=Namespace.AVATAR_METADATA) - client = app.get_client(self.account) - client.get_module('VCardTemp').set_vcard( - vcard_, callback=self._on_set_vcard_result) - self._set_nickname(vcard_) + client.get_module('PubSub').get_access_model( + Namespace.AVATAR_DATA, + callback=self._on_access_model_received, + user_data=Namespace.AVATAR_DATA) - self.message_id = self.statusbar.push( - self.context_id, _('Sending profile…')) - self.progressbar.show() - self.update_progressbar_timeout_id = GLib.timeout_add( - 100, self.update_progressbar) + client.get_module('PubSub').get_access_model( + Namespace.NICK, + callback=self._on_access_model_received, + user_data=Namespace.NICK) - @as_task - def _request_vcard(self): - _task = yield + self._ui.connect_signals(self) + self.connect('key-press-event', self._on_key_press_event) - client = app.get_client(self.account) - vcard = yield client.get_module('VCardTemp').request_vcard() + def _on_access_model_received(self, task): + namespace = task.get_user_data() - if is_error(vcard): + try: + result = task.finish() + except StanzaError as error: + log.warning('Unable to get access model for %s: %s', + namespace, error) return - self._set_avatar(vcard) - self.set_values(vcard.data) + access_model = result == 'open' + + if namespace == Namespace.VCARD4_PUBSUB: + self._ui.vcard_access.set_active(access_model) + else: + if self._avatar_nick_public is None: + self._avatar_nick_public = access_model + else: + self._avatar_nick_public = (self._avatar_nick_public and + access_model) + self._ui.avatar_nick_access.set_active(self._avatar_nick_public) - def _on_set_vcard_result(self, task_): + def _on_vcard_received(self, task): try: - task_.finish() - except Exception as error: - log.warning(error) - - if self.message_id: - self.statusbar.remove(self.context_id, self.message_id) - self.message_id = self.statusbar.push( - self.context_id, _('Information NOT published')) - self.remove_statusbar_timeout_id = GLib.timeout_add_seconds( - 3, self.remove_statusbar, self.message_id) - if self.update_progressbar_timeout_id is not None: - GLib.source_remove(self.update_progressbar_timeout_id) - self.progressbar.set_fraction(0) - self.update_progressbar_timeout_id = None - InformationDialog( - _('vCard publication failed'), - _('There was an error while publishing your personal information, ' - 'try again later.'), transient_for=self) + vcard = task.finish() + except StanzaError as error: + log.info('Error loading VCard: %s', error) + vcard = VCard() - else: - if self.update_progressbar_timeout_id is not None: - GLib.source_remove(self.update_progressbar_timeout_id) - self.update_progressbar_timeout_id = None + self._load_avatar() + self._profile.set_vcard(vcard) + self._ui.profile_stack.set_visible_child_name('profile') + self._ui.spinner.stop() + + def _load_avatar(self): + scale = self.get_scale_factor() + self._current_avatar = app.contacts.get_avatar( + self.account, + self._jid, + AvatarSize.VCARD, + scale) + + self._ui.avatar_image.set_from_surface(self._current_avatar) + self._ui.avatar_image.show() + + def _on_key_press_event(self, _widget, event): + if event.keyval == Gdk.KEY_Escape: self.destroy() - def _set_nickname(self, vcard): - nick = vcard.data.get('NICKNAME') or None + def _add_actions(self): + for action in MENU_DICT: + action_name = 'add-' + action.lower() + act = Gio.SimpleAction.new(action_name, None) + act.connect('activate', self._on_action) + self.add_action(act) + + def _on_action(self, action, _param): + name = action.get_name() + key = name.split('-')[1] + self._profile.add_new_property(key) + GLib.idle_add(scroll_to_end, self._ui.scrolled) + + def _on_edit_clicked(self, *args): + self._profile.set_editable(True) + self._ui.edit_button.hide() + self._ui.add_entry_button.set_no_show_all(False) + self._ui.add_entry_button.show_all() + self._ui.cancel_button.show() + self._ui.save_button.show() + self._ui.remove_avatar_button.show() + self._ui.edit_avatar_button.show() + self._ui.nickname_entry.set_sensitive(True) + self._ui.privacy_button.show() + + def _on_cancel_clicked(self, _widget): + self._profile.set_editable(False) + self._ui.edit_button.show() + self._ui.add_entry_button.hide() + self._ui.cancel_button.hide() + self._ui.save_button.hide() + self._ui.remove_avatar_button.hide() + self._ui.edit_avatar_button.hide() + self._ui.privacy_button.hide() + self._ui.nickname_entry.set_sensitive(False) + self._ui.avatar_image.set_from_surface(self._current_avatar) + self._ui.nickname_entry.set_text(app.nicks[self.account]) + self._new_avatar = False + + def _on_save_clicked(self, _widget): + self._ui.spinner.start() + self._ui.profile_stack.set_visible_child_name('spinner') + self._ui.add_entry_button.hide() + self._ui.cancel_button.hide() + self._ui.save_button.hide() + self._ui.edit_button.show() + self._ui.remove_avatar_button.hide() + self._ui.edit_avatar_button.hide() + self._ui.privacy_button.hide() + self._ui.nickname_entry.set_sensitive(False) + + self._profile.validate() + + con = app.connections[self.account] + con.get_module('VCard4').set_vcard( + self._profile.get_vcard(), + public=self._ui.vcard_access.get_active(), + callback=self._on_save_finished) + + public = self._ui.avatar_nick_access.get_active() + + if self._new_avatar is False: + if self._avatar_nick_public != public: + con.get_module('UserAvatar').set_access_model(public) - client = app.get_client(self.account) - client.get_module('UserNickname').set_nickname(nick) + else: + # Only update avatar if it changed + con.get_module('UserAvatar').set_avatar(self._new_avatar, + public=public) + + nick = GLib.markup_escape_text(self._ui.nickname_entry.get_text()) + con.get_module('UserNickname').set_nickname(nick, public=public) if not nick: - nick = app.config.get_per('accounts', self.account, 'name') + nick = app.settings.get_account_setting( + self.account, 'name') app.nicks[self.account] = nick - def on_cancel_button_clicked(self, widget): - self.destroy() + def _on_remove_avatar(self, _button): + contact = app.contacts.create_contact(self._jid, self.account) + scale = self.get_scale_factor() + surface = app.interface.avatar_storage.get_surface( + contact, AvatarSize.VCARD, scale, default=True) + + self._ui.avatar_image.set_from_surface(surface) + self._ui.remove_avatar_button.hide() + self._new_avatar = None + + def _on_edit_avatar(self, button): + def _on_file_selected(path): + if self._avatar_selector is None: + self._avatar_selector = AvatarSelector() + self._ui.avatar_selector_box.add(self._avatar_selector) + + self._avatar_selector.prepare_crop_area(path) + self._ui.avatar_update_button.set_sensitive( + self._avatar_selector.get_prepared()) + self._ui.profile_stack.set_visible_child_name('avatar_selector') + + AvatarChooserDialog(_on_file_selected, + transient_for=button.get_toplevel()) + + def _on_cancel_update_avatar(self, _button): + self._ui.profile_stack.set_visible_child_name('profile') + + def _on_update_avatar(self, _button): + success, data, width, height = self._avatar_selector.get_avatar_bytes() + if not success: + # TODO: Error handling + return + + sha = app.interface.avatar_storage.save_avatar(data) + if sha is None: + # TODO: Error handling + return + + self._new_avatar = Avatar() + self._new_avatar.add_image_source(data, 'image/png', height, width) + + scale = self.get_scale_factor() + surface = app.interface.avatar_storage.surface_from_filename( + sha, AvatarSize.VCARD, scale) + + self._ui.avatar_image.set_from_surface(clip_circle(surface)) + self._ui.remove_avatar_button.show() + self._ui.profile_stack.set_visible_child_name('profile') + + def _access_switch_toggled(self, *args): + avatar_nick_access = self._ui.avatar_nick_access.get_active() + vcard_access = self._ui.vcard_access.get_active() + self._ui.avatar_nick_access_label.set_text( + _('Everyone') if avatar_nick_access else _('Contacts')) + self._ui.vcard_access_label.set_text( + _('Everyone') if vcard_access else _('Contacts')) + + def _on_save_finished(self, task): + try: + task.finish() + except StanzaError as err: + log.error('Could not publish VCard: %s', err) + # TODO Handle error + return + + self._profile.set_editable(False) + self._ui.profile_stack.set_visible_child_name('profile') + self._ui.spinner.stop() diff --git a/gajim/gtk/vcard_grid.py b/gajim/gtk/vcard_grid.py new file mode 100644 index 0000000000000000000000000000000000000000..6b8fcd3c3ab9cfb762b851374fa15cfaa90eb18b --- /dev/null +++ b/gajim/gtk/vcard_grid.py @@ -0,0 +1,729 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. + +import datetime + +from gi.repository import Gdk +from gi.repository import GLib +from gi.repository import Gtk +from gi.repository import GObject + +from gajim.common.i18n import _ +from gajim.common.i18n import Q_ +from gajim.common.const import URIType +from gajim.common.helpers import open_uri +from gajim.common.helpers import parse_uri +from gajim.common.structs import URI +from gajim.gui.util import gtk_month +from gajim.gui.util import python_month + + +LABEL_DICT = { + 'fn': _('Full Name'), + 'n': _('Name'), + 'bday': _('Birthday'), + 'gender': _('Gender'), + 'adr': Q_('?profile:Address'), + 'tel': _('Phone No.'), + 'email': _('Email'), + 'impp': _('IM Address'), + 'title': Q_('?profile:Title'), + 'role': Q_('?profile:Role'), + 'org': _('Organisation'), + 'note': Q_('?profile:Note'), + 'url': _('URL'), + 'key': Q_('?profile:Key'), +} + + +FIELD_TOOLTIPS = { + 'key': _('Your public key or authentication certificate') +} + + +ADR_FIELDS = ['street', 'ext', 'pobox', 'code', 'locality', 'region', 'country'] + + +ADR_PLACEHOLDER_TEXT = { + 'pobox': _('Post Office Box'), + 'street': _('Street'), + 'ext': _('Extended Address'), + 'locality': _('City'), + 'region': _('State'), + 'code': _('Postal Code'), + 'country': _('Country'), +} + + +DEFAULT_KWARGS = { + 'fn': {'value': ''}, + 'bday': {'value': '', 'value_type': 'date'}, + 'gender': {'sex': '', 'identity': ''}, + 'adr': {}, + 'email': {'value': ''}, + 'impp': {'value': ''}, + 'tel': {'value': '', 'value_type': 'text'}, + 'org': {'values': []}, + 'title': {'value': ''}, + 'role': {'value': ''}, + 'url': {'value': ''}, + 'key': {'value': '', 'value_type': 'text'}, +} + + +PROPERTIES_WITH_TYPE = [ + 'adr', + 'email', + 'impp', + 'tel', + 'key', +] + + +ORDER = [ + 'fn', + 'gender', + 'bday', + 'adr', + 'email', + 'impp', + 'tel', + 'org', + 'title', + 'role', + 'url', + 'key', +] + + +SEX_VALUES = { + 'M': _('Male'), + 'F': _('Female'), + 'O': Q_('?Gender:Other'), + 'N': Q_('?Gender:None'), + 'U': _('Unknown') +} + + +TYPE_VALUES = { + '-' : None, + 'home': _('Home'), + 'work': _('Work') +} + + +class VCardGrid(Gtk.Grid): + def __init__(self, account): + Gtk.Grid.__init__(self) + + self._callbacks = { + 'fn': TextEntryProperty, + 'bday': DateProperty, + 'gender': GenderProperty, + 'adr': AdrProperty, + 'tel': TextEntryProperty, + 'email': TextEntryProperty, + 'impp': TextEntryProperty, + 'title': TextEntryProperty, + 'role': TextEntryProperty, + 'org': TextEntryProperty, + 'url': TextEntryProperty, + 'key': KeyProperty, + } + + self.set_column_spacing(12) + self.set_row_spacing(12) + self.set_no_show_all(True) + self.set_visible(True) + self.set_halign(Gtk.Align.CENTER) + + self._account = account + self._row_count = 0 + self._vcard = None + self._props = [] + + def set_editable(self, enabled): + for prop in self._props: + prop.set_editable(enabled) + + def set_vcard(self, vcard): + self._vcard = vcard + + for entry in ORDER: + for prop in vcard.get_properties(): + if entry != prop.name: + continue + + self.add_property(prop) + + def get_vcard(self): + return self._vcard + + def validate(self): + for prop in list(self._props): + base_prop = prop.get_base_property() + if base_prop.is_empty: + self.remove_property(prop) + + def add_new_property(self, name): + kwargs = DEFAULT_KWARGS[name] + prop = self._vcard.add_property(name, **kwargs) + self.add_property(prop, editable=True) + #GLib.idle_add(scroll_to_end, self.get_parent()) + + def add_property(self, prop, editable=False): + prop_class = self._callbacks.get(prop.name) + if prop_class is None: + return + prop_obj = prop_class(prop, self._account) + prop_obj.set_editable(editable) + prop_obj.add_to_grid(self, self._row_count) + + self._props.append(prop_obj) + self._row_count += 1 + + def remove_property(self, prop): + self.remove_row(prop.row_number) + self._props.remove(prop) + self._vcard.remove_property(prop.get_base_property()) + + +class DescriptionLabel(Gtk.Label): + def __init__(self, value): + Gtk.Label.__init__(self, label=LABEL_DICT[value]) + if value == 'adr': + self.set_valign(Gtk.Align.START) + else: + self.set_valign(Gtk.Align.CENTER) + self.get_style_context().add_class('dim-label') + self.get_style_context().add_class('margin-right18') + self.set_visible(True) + self.set_xalign(1) + self.set_tooltip_text(FIELD_TOOLTIPS.get(value, '')) + + +class ValueLabel(Gtk.Label): + def __init__(self, prop, account): + Gtk.Label.__init__(self) + self._prop = prop + self._uri = None + self._account = account + self.set_selectable(True) + self.set_xalign(0) + self.set_max_width_chars(50) + self.set_valign(Gtk.Align.CENTER) + self.set_halign(Gtk.Align.START) + + self.connect('activate-link', self._on_activate_link) + if prop.name == 'org': + self.set_value(prop.values[0] if prop.values else '') + else: + self.set_value(prop.value) + + def set_value(self, value): + if self._prop.name == 'email': + self._uri = URI(type=URIType.MAIL, data=value) + self.set_markup(value) + + elif self._prop.name in ('impp', 'tel'): + self._uri = parse_uri(value) + self.set_markup(value) + + else: + self.set_text(value) + + def set_markup(self, text): + if not text: + self.set_text('') + return + super().set_markup('<a href="{}">{}</a>'.format( + GLib.markup_escape_text(text), + GLib.markup_escape_text(text))) + + def _on_activate_link(self, _label, _value): + open_uri(self._uri, self._account) + return Gdk.EVENT_STOP + + +class SexLabel(Gtk.Label): + def __init__(self, prop): + Gtk.Label.__init__(self) + self._prop = prop + + self.set_selectable(True) + self.set_xalign(0) + self.set_max_width_chars(50) + self.set_valign(Gtk.Align.CENTER) + self.set_halign(Gtk.Align.START) + + self.set_text(prop.sex) + + def set_text(self, value): + if not value or value == '-': + super().set_text('') + else: + super().set_text(SEX_VALUES[value]) + + +class IdentityLabel(Gtk.Label): + def __init__(self, prop): + Gtk.Label.__init__(self) + self._prop = prop + + self.set_selectable(True) + self.set_xalign(0) + self.set_max_width_chars(50) + self.set_valign(Gtk.Align.CENTER) + self.set_halign(Gtk.Align.START) + + self.set_text(prop.identity) + + def set_text(self, value): + super().set_text('' if not value else value) + + +class ValueEntry(Gtk.Entry): + def __init__(self, prop): + Gtk.Entry.__init__(self) + self.set_valign(Gtk.Align.CENTER) + self.set_max_width_chars(50) + if prop.name == 'org': + self.set_text(prop.values[0] if prop.values else '') + else: + self.set_text(prop.value) + + +class AdrEntry(Gtk.Entry): + def __init__(self, prop, type_): + Gtk.Entry.__init__(self) + self.set_valign(Gtk.Align.CENTER) + self.set_max_width_chars(50) + values = getattr(prop, type_) + if not values: + value = '' + else: + value = values[0] + self.set_text(value) + self.set_placeholder_text(ADR_PLACEHOLDER_TEXT.get(type_)) + + +class IdentityEntry(Gtk.Entry): + def __init__(self, prop): + Gtk.Entry.__init__(self) + self.set_valign(Gtk.Align.CENTER) + self.set_max_width_chars(50) + self.set_text('' if not prop.identity else prop.identity) + + +class AdrBox(Gtk.Box): + + __gsignals__ = { + 'field-changed': ( + GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, + None, # return value + (str, str) # arguments + )} + + def __init__(self, prop): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=6) + + for field in ADR_FIELDS: + entry = AdrEntry(prop, field) + entry.connect('notify::text', self._on_text_changed, field) + self.add(entry) + + self.show_all() + + def _on_text_changed(self, entry, _param, field): + self.emit('field-changed', field, entry.get_text()) + + +class AdrLabel(Gtk.Label): + def __init__(self, prop, type_): + Gtk.Label.__init__(self) + self.set_selectable(True) + self.set_xalign(0) + self.set_max_width_chars(50) + self.set_valign(Gtk.Align.CENTER) + self.set_halign(Gtk.Align.START) + values = getattr(prop, type_) + if not values: + value = '' + else: + value = values[0] + self.set_text(value) + + def set_text(self, value): + self.set_visible(bool(value)) + super().set_text(value) + + +class AdrBoxReadOnly(Gtk.Box): + def __init__(self, prop): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=6) + + self._labels = {} + + for field in ADR_FIELDS: + label = AdrLabel(prop, field) + self._labels[field] = label + self.add(label) + + def set_field(self, field, value): + self._labels[field].set_text(value) + + +class ValueTextView(Gtk.TextView): + def __init__(self, prop): + Gtk.TextView.__init__(self) + self.props.right_margin = 8 + self.props.left_margin = 8 + self.props.top_margin = 8 + self.props.bottom_margin = 8 + + self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) + self.set_hexpand(True) + self.set_valign(Gtk.Align.FILL) + + self._prop = prop + self.get_buffer().set_text(prop.value) + self.get_buffer().connect('notify::text', self._on_text_changed) + + def get_text(self): + start_iter, end_iter = self.get_buffer().get_bounds() + return self.get_buffer().get_text(start_iter, end_iter, False) + + def _on_text_changed(self, _buffer, _param): + self._prop.value = self.get_text() + + +class TypeComboBox(Gtk.ComboBoxText): + def __init__(self, parameters): + Gtk.ComboBoxText.__init__(self) + self.set_valign(Gtk.Align.CENTER) + self._parameters = parameters + self.append('-', '-') + self.append('home', _('Home')) + self.append('work', _('Work')) + + values = self._parameters.get_types() + if 'home' in values: + self.set_active_id('home') + + elif 'work' in values: + self.set_active_id('work') + + else: + self.set_active_id('-') + + self.connect('notify::active-id', self._on_active_id_changed) + + def _on_active_id_changed(self, _combobox, _param): + type_ = self.get_active_id() + if type_ == '-': + self._parameters.remove_types(['work', 'home']) + + elif type_ == 'work': + self._parameters.add_types(['work']) + self._parameters.remove_types(['home']) + + elif type_ == 'home': + self._parameters.add_types(['home']) + self._parameters.remove_types(['work']) + + def get_text(self): + type_value = self.get_active_id() + if type_value == '-': + return '' + return self.get_active_text() + + +class GenderComboBox(Gtk.ComboBoxText): + def __init__(self, prop): + Gtk.ComboBoxText.__init__(self) + self.set_valign(Gtk.Align.CENTER) + self.set_halign(Gtk.Align.START) + + self._prop = prop + + self.append('-', '-') + for key, value in SEX_VALUES.items(): + self.append(key, value) + + if not prop.sex: + self.set_active_id('-') + else: + self.set_active_id(prop.sex) + + +class RemoveButton(Gtk.Button): + def __init__(self): + Gtk.Button.__init__(self) + self.set_valign(Gtk.Align.CENTER) + self.set_halign(Gtk.Align.START) + image = Gtk.Image.new_from_icon_name('user-trash-symbolic', + Gtk.IconSize.MENU) + self.set_image(image) + self.set_no_show_all(True) + + +class VCardProperty: + def __init__(self, prop): + self._prop = prop + + self._second_column = [] + self._third_column = [] + + self._desc_label = DescriptionLabel(prop.name) + self._remove_button = RemoveButton() + self._remove_button.connect('clicked', self._on_remove_clicked) + + self._edit_widgets = [self._remove_button] + self._read_widgets = [] + + if prop.name in PROPERTIES_WITH_TYPE: + self._type_combobox = TypeComboBox(prop.parameters) + self._type_combobox.connect('notify::active-id', + self._on_type_changed) + type_ = self._type_combobox.get_active_id() + icon_name = self._get_icon_name(type_) + self._type_image = Gtk.Image.new_from_icon_name( + icon_name, Gtk.IconSize.MENU) + self._type_image.set_tooltip_text(TYPE_VALUES[type_]) + + if prop.name == 'adr': + self._type_image.set_valign(Gtk.Align.START) + self._type_combobox.set_valign(Gtk.Align.START) + + self._edit_widgets.append(self._type_combobox) + self._read_widgets.append(self._type_image) + self._second_column.extend([self._type_combobox, self._type_image]) + + @staticmethod + def _get_icon_name(type_): + if type_ == 'home': + return 'feather-home' + if type_ == 'work': + return 'feather-briefcase' + return None + + def _on_type_changed(self, _combobox, _param): + type_ = self._type_combobox.get_active_id() + icon_name = self._get_icon_name(type_) + self._type_image.set_from_icon_name(icon_name, Gtk.IconSize.MENU) + self._type_image.set_tooltip_text(TYPE_VALUES[type_]) + + def _on_remove_clicked(self, button): + button.get_parent().remove_property(self) + + @property + def row_number(self): + grid = self._desc_label.get_parent() + return grid.child_get_property(self._desc_label, 'top-attach') + + def get_base_property(self): + return self._prop + + def set_editable(self, enabled): + for widget in self._edit_widgets: + widget.set_visible(enabled) + + for widget in self._read_widgets: + widget.set_visible(not enabled) + + def add_to_grid(self, grid, row_number): + # child, left, top, width, height + grid.attach(self._desc_label, 0, row_number, 1, 1) + + for widget in self._second_column: + grid.attach(widget, 1, row_number, 1, 1) + + for widget in self._third_column: + grid.attach(widget, 2, row_number, 1, 1) + + grid.attach(self._remove_button, 3, row_number, 1, 1) + + +class TextEntryProperty(VCardProperty): + def __init__(self, prop, account): + VCardProperty.__init__(self, prop) + + self._value_entry = ValueEntry(prop) + self._value_entry.connect('notify::text', self._on_text_changed) + + self._value_label = ValueLabel(prop, account) + + self._edit_widgets.append(self._value_entry) + self._read_widgets.append(self._value_label) + + self._third_column = [self._value_entry, self._value_label] + + def _on_text_changed(self, entry, _param): + text = entry.get_text() + if self._prop.name == 'org': + self._prop.values[0] = text + else: + self._prop.value = text + self._value_label.set_value(text) + + +class DateProperty(VCardProperty): + def __init__(self, prop, account): + VCardProperty.__init__(self, prop) + + self._box = Gtk.Box(spacing=6) + self._value_entry = ValueEntry(prop) + self._value_entry.set_placeholder_text(_('YYYY-MM-DD')) + self._value_entry.connect('notify::text', self._on_text_changed) + + self._calendar_button = Gtk.MenuButton() + image = Gtk.Image.new_from_icon_name( + 'x-office-calendar-symbolic', Gtk.IconSize.BUTTON) + self._calendar_button.set_image(image) + self._calendar_button.connect( + 'clicked', self._on_calendar_button_clicked) + self._box.add(self._value_entry) + self._box.add(self._calendar_button) + self._box.show_all() + + self.calendar = Gtk.Calendar(year=1980, month=5, day=15) + self.calendar.set_visible(True) + self.calendar.connect( + 'day-selected', self._on_calendar_day_selected) + + popover = Gtk.Popover() + popover.add(self.calendar) + self._calendar_button.set_popover(popover) + + self._value_label = ValueLabel(prop, account) + + self._edit_widgets.append(self._box) + self._read_widgets.append(self._value_label) + + self._third_column = [self._box, self._value_label] + + def _on_text_changed(self, entry, _param): + text = entry.get_text() + self._prop.value = text + self._value_label.set_value(text) + + def _on_calendar_button_clicked(self, _widget): + birthday = self._value_entry.get_text() + if not birthday: + return + try: + date = datetime.datetime.strptime(birthday, '%Y-%m-%d') + except ValueError: + return + month = gtk_month(date.month) + self.calendar.select_month(month, date.year) + self.calendar.select_day(date.day) + + def _on_calendar_day_selected(self, _widget): + year, month, day = self.calendar.get_date() # Integers + month = python_month(month) + date_str = datetime.date(year, month, day).strftime('%Y-%m-%d') + self._value_entry.set_text(date_str) + + +class KeyProperty(VCardProperty): + def __init__(self, prop, _account): + VCardProperty.__init__(self, prop) + + self._value_text_view = ValueTextView(prop) + self._value_text_view.show() + + self._scrolled_window = Gtk.ScrolledWindow() + self._scrolled_window.set_policy(Gtk.PolicyType.NEVER, + Gtk.PolicyType.AUTOMATIC) + self._scrolled_window.add(self._value_text_view) + self._scrolled_window.set_valign(Gtk.Align.CENTER) + self._scrolled_window.set_size_request(-1, 200) + self._scrolled_window.get_style_context().add_class('profile-scrolled') + + self._copy_button = Gtk.Button.new_from_icon_name('edit-copy-symbolic', + Gtk.IconSize.MENU) + self._copy_button.connect('clicked', self._on_copy_clicked) + self._copy_button.set_halign(Gtk.Align.START) + self._copy_button.set_valign(Gtk.Align.CENTER) + + self._edit_widgets.append(self._scrolled_window) + self._read_widgets.append(self._copy_button) + + self._third_column = [self._scrolled_window, self._copy_button] + + def _on_copy_clicked(self, _button): + clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) + clipboard.set_text(self._value_text_view.get_text(), -1) + + +class GenderProperty(VCardProperty): + def __init__(self, prop, _account): + VCardProperty.__init__(self, prop) + + value_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) + self._value_combobox = GenderComboBox(prop) + self._value_combobox.connect('notify::active-id', + self._on_active_id_changed) + self._value_combobox.show() + + self._value_entry = IdentityEntry(prop) + self._value_entry.show() + self._value_entry.connect('notify::text', self._on_text_changed) + + value_box.add(self._value_combobox) + value_box.add(self._value_entry) + + label_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=12) + self._identity_label = IdentityLabel(prop) + self._identity_label.show() + self._sex_label = SexLabel(prop) + self._sex_label.show() + + label_box.add(self._sex_label) + label_box.add(self._identity_label) + + self._edit_widgets.append(value_box) + self._read_widgets.append(label_box) + + self._third_column = [value_box, label_box] + + def _on_text_changed(self, entry, _param): + text = entry.get_text() + self._prop.identity = text + self._identity_label.set_text(text) + + def _on_active_id_changed(self, combobox, _param): + sex = combobox.get_active_id() + self._prop.sex = None if sex == '-' else sex + self._sex_label.set_text(sex) + + +class AdrProperty(VCardProperty): + def __init__(self, prop, _account): + VCardProperty.__init__(self, prop) + + self._entry_box = AdrBox(prop) + self._entry_box.connect('field-changed', self._on_field_changed) + + self._read_box = AdrBoxReadOnly(prop) + + self._edit_widgets.append(self._entry_box) + self._read_widgets.append(self._read_box) + + self._third_column = [self._entry_box, self._read_box] + + def _on_field_changed(self, _box, field, value): + setattr(self._prop, field, [value]) + self._read_box.set_field(field, value)