Daniel Brötzmann pushed to branch mainwindow at gajim / gajim

Commits:

9 changed files:

Changes:

  • gajim/common/const.py
    ... ... @@ -32,6 +32,7 @@ class AvatarSize(IntEnum):
    32 32
         WORKSPACE_EDIT = 100
    
    33 33
         CHAT = 48
    
    34 34
         NOTIFICATION = 48
    
    35
    +    CALL = 100
    
    35 36
         GROUP_INFO = 100
    
    36 37
         TOOLTIP = 100
    
    37 38
         ACCOUNT_PAGE = 150
    

  • gajim/data/style/default-dark.css
    ... ... @@ -15,6 +15,10 @@ .gajim-subject-message {
    15 15
         color: rgb(205, 225, 255);
    
    16 16
         background: rgb(40, 60, 80);
    
    17 17
     }
    
    18
    +.gajim-call-message {
    
    19
    +    color: rgb(205, 225, 255);
    
    20
    +    background: rgb(40, 60, 80);
    
    21
    +}
    
    18 22
     .gajim-url {
    
    19 23
         color: rgb(60, 135, 220);
    
    20 24
     }
    

  • gajim/data/style/default.css
    ... ... @@ -15,6 +15,10 @@ .gajim-subject-message {
    15 15
         color: rgb(40, 70, 100);
    
    16 16
         background: rgb(225, 235, 250);
    
    17 17
     }
    
    18
    +.gajim-call-message {
    
    19
    +    color: rgb(40, 70, 100);
    
    20
    +    background: rgb(225, 235, 250);
    
    21
    +}
    
    18 22
     .gajim-url {
    
    19 23
         color: rgb(30, 75, 135);
    
    20 24
     }
    

  • gajim/data/style/gajim.css
    ... ... @@ -82,7 +82,8 @@ .conversation-system-row {
    82 82
     .conversation-date-row {
    
    83 83
         padding: 12px;
    
    84 84
     }
    
    85
    -.conversation-subject-box {
    
    85
    +.conversation-subject-box,
    
    86
    +.conversation-call-box {
    
    86 87
         border-radius: 6px;
    
    87 88
         padding: 12px;
    
    88 89
     }
    

  • gajim/gtk/controls/base.py
    ... ... @@ -1098,6 +1098,9 @@ def add_info_message(self, text):
    1098 1098
         def add_file_transfer(self, transfer):
    
    1099 1099
             self.conversation_view.add_file_transfer(transfer)
    
    1100 1100
     
    
    1101
    +    def add_call_message(self, timestamp, text, event=None):
    
    1102
    +        self.conversation_view.add_call_message(timestamp, text, event)
    
    1103
    +
    
    1101 1104
         def add_message(self,
    
    1102 1105
                         text,
    
    1103 1106
                         kind,
    

  • gajim/gtk/controls/chat.py
    ... ... @@ -136,6 +136,7 @@ def __init__(self, account, jid):
    136 136
             }
    
    137 137
             self._video_widget_other = None
    
    138 138
             self._video_widget_self = None
    
    139
    +        self.conversation_view.connect('call-answered', self._on_call_answered)
    
    139 140
     
    
    140 141
             self.update_toolbar()
    
    141 142
             self.update_all_pep_types()
    
    ... ... @@ -831,6 +832,44 @@ def on_sound_hscale_value_changed(self, _widget, value):
    831 832
             self._get_audio_content().set_out_volume(value / 100)
    
    832 833
             app.settings.set('audio_output_volume', int(value))
    
    833 834
     
    
    835
    +    def _on_call_answered(self, _view, action, event):
    
    836
    +        if action == 'accept':
    
    837
    +            session = self._client.get_module('Jingle').get_jingle_session(
    
    838
    +                event.fjid, event.sid)
    
    839
    +            if not session:
    
    840
    +                return
    
    841
    +
    
    842
    +            audio = session.get_content('audio')
    
    843
    +            video = session.get_content('video')
    
    844
    +
    
    845
    +            if audio and not audio.negotiated:
    
    846
    +                self.set_jingle_state(
    
    847
    +                    'audio', JingleState.CONNECTING, event.sid)
    
    848
    +            if video and not video.negotiated:
    
    849
    +                self.set_jingle_state(
    
    850
    +                    'video', JingleState.CONNECTING, event.sid)
    
    851
    +
    
    852
    +            if not session.accepted:
    
    853
    +                session.approve_session()
    
    854
    +
    
    855
    +            for item in event.contents:
    
    856
    +                session.approve_content(item.media)
    
    857
    +
    
    858
    +        if action == 'reject':
    
    859
    +            session = self._client.get_module('Jingle').get_jingle_session(
    
    860
    +                event.fjid, event.sid)
    
    861
    +            if not session:
    
    862
    +                return
    
    863
    +
    
    864
    +            if not session.accepted:
    
    865
    +                session.decline_session()
    
    866
    +            else:
    
    867
    +                for item in event.contents:
    
    868
    +                    session.reject_content(item.media)
    
    869
    +
    
    870
    +    def add_incoming_call(self, event):
    
    871
    +        self.add_call_message(time.time(), '', event=event)
    
    872
    +
    
    834 873
         def on_location_eventbox_button_release_event(self, _widget, _event):
    
    835 874
             return
    
    836 875
             # TODO
    
    ... ... @@ -1200,61 +1239,6 @@ def _got_file_error(self, file_props, type_, pri_txt, sec_txt):
    1200 1239
                 file_props,
    
    1201 1240
                 Gtk.MessageType.ERROR)
    
    1202 1241
     
    
    1203
    -    def _on_reject_call(self, _button, event):
    
    1204
    -        app.events.remove_events(
    
    1205
    -            self.account, event.jid, types='jingle-incoming')
    
    1206
    -
    
    1207
    -        session = self._client.get_module('Jingle').get_jingle_session(
    
    1208
    -            event.peerjid, event.sid)
    
    1209
    -        if not session:
    
    1210
    -            return
    
    1211
    -
    
    1212
    -        if not session.accepted:
    
    1213
    -            session.decline_session()
    
    1214
    -        else:
    
    1215
    -            for content in event.content_types:
    
    1216
    -                session.reject_content(content)
    
    1217
    -
    
    1218
    -    def _on_accept_call(self, _button, event):
    
    1219
    -        app.events.remove_events(
    
    1220
    -            self.account, event.jid, types='jingle-incoming')
    
    1221
    -
    
    1222
    -        session = self._client.get_module('Jingle').get_jingle_session(
    
    1223
    -            event.peerjid, event.sid)
    
    1224
    -        if not session:
    
    1225
    -            return
    
    1226
    -
    
    1227
    -        audio = session.get_content('audio')
    
    1228
    -        video = session.get_content('video')
    
    1229
    -
    
    1230
    -        if audio and not audio.negotiated:
    
    1231
    -            self.set_jingle_state('audio', JingleState.CONNECTING, event.sid)
    
    1232
    -        if video and not video.negotiated:
    
    1233
    -            self.set_jingle_state('video', JingleState.CONNECTING, event.sid)
    
    1234
    -
    
    1235
    -        if not session.accepted:
    
    1236
    -            session.approve_session()
    
    1237
    -
    
    1238
    -        for content in event.content_types:
    
    1239
    -            session.approve_content(content)
    
    1240
    -
    
    1241
    -    def add_call_received_message(self, event):
    
    1242
    -        markup = '<b>%s</b>' % (_('Incoming Call'))
    
    1243
    -        if 'video' in event.content_types:
    
    1244
    -            markup += _('\nVideo Call')
    
    1245
    -        else:
    
    1246
    -            markup += _('\nVoice Call')
    
    1247
    -
    
    1248
    -        button_reject = Gtk.Button.new_with_mnemonic(_('_Reject'))
    
    1249
    -        button_reject.connect('clicked', self._on_reject_call, event)
    
    1250
    -        button_accept = Gtk.Button.new_with_mnemonic(_('_Accept'))
    
    1251
    -        button_accept.connect('clicked', self._on_accept_call, event)
    
    1252
    -        self._add_info_bar_message(
    
    1253
    -            markup,
    
    1254
    -            [button_reject, button_accept],
    
    1255
    -            event,
    
    1256
    -            Gtk.MessageType.QUESTION)
    
    1257
    -
    
    1258 1242
         def on_event_added(self, event):
    
    1259 1243
             if event.account != self.account:
    
    1260 1244
                 return
    

  • gajim/gtk/conversation/rows/call.py
    1
    +# This file is part of Gajim.
    
    2
    +#
    
    3
    +# Gajim is free software; you can redistribute it and/or modify
    
    4
    +# it under the terms of the GNU General Public License as published
    
    5
    +# by the Free Software Foundation; version 3 only.
    
    6
    +#
    
    7
    +# Gajim is distributed in the hope that it will be useful,
    
    8
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    +# GNU General Public License for more details.
    
    11
    +#
    
    12
    +# You should have received a copy of the GNU General Public License
    
    13
    +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    14
    +
    
    15
    +import time
    
    16
    +from datetime import datetime
    
    17
    +
    
    18
    +from gi.repository import Gtk
    
    19
    +
    
    20
    +from gajim.common.const import AvatarSize
    
    21
    +from gajim.common.i18n import _
    
    22
    +
    
    23
    +from .widgets import SimpleLabel
    
    24
    +from .base import BaseRow
    
    25
    +
    
    26
    +
    
    27
    +class CallRow(BaseRow):
    
    28
    +    def __init__(self, account, contact, timestamp, text, event=None):
    
    29
    +        BaseRow.__init__(self, account)
    
    30
    +
    
    31
    +        self.type = 'call'
    
    32
    +        timestamp = time.time()
    
    33
    +        self.timestamp = datetime.fromtimestamp(timestamp)
    
    34
    +        self.db_timestamp = timestamp
    
    35
    +
    
    36
    +        self._contact = contact
    
    37
    +        self._event = event
    
    38
    +
    
    39
    +        avatar_placeholder = Gtk.Box()
    
    40
    +        avatar_placeholder.set_size_request(AvatarSize.ROSTER, -1)
    
    41
    +        self.grid.attach(avatar_placeholder, 0, 0, 1, 1)
    
    42
    +
    
    43
    +        if event is None:
    
    44
    +            icon = Gtk.Image.new_from_icon_name('call-start-symbolic',
    
    45
    +                                                Gtk.IconSize.MENU)
    
    46
    +            self.grid.attach(icon, 1, 0, 1, 1)
    
    47
    +
    
    48
    +            self._label = SimpleLabel()
    
    49
    +            self._label.set_text(text)
    
    50
    +            self.grid.attach(self._label, 2, 0, 1, 1)
    
    51
    +        else:
    
    52
    +            self._prepare_incoming_call()
    
    53
    +
    
    54
    +        timestamp_widget = self.create_timestamp_widget(self.timestamp)
    
    55
    +        timestamp_widget.set_hexpand(True)
    
    56
    +        timestamp_widget.set_halign(Gtk.Align.END)
    
    57
    +        timestamp_widget.set_valign(Gtk.Align.START)
    
    58
    +        self.grid.attach(timestamp_widget, 3, 0, 1, 1)
    
    59
    +
    
    60
    +        self.show_all()
    
    61
    +
    
    62
    +    def _on_accept(self, button):
    
    63
    +        button.set_sensitive(False)
    
    64
    +        self._reject_button.set_sensitive(False)
    
    65
    +        self.get_parent().on_call_answered('accept', self._event)
    
    66
    +
    
    67
    +    def _on_reject(self, button):
    
    68
    +        button.set_sensitive(False)
    
    69
    +        self._accept_button.set_sensitive(False)
    
    70
    +        self.get_parent().on_call_answered('reject', self._event)
    
    71
    +
    
    72
    +    def _prepare_incoming_call(self):
    
    73
    +        call_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
    
    74
    +        call_box.set_size_request(350, -1)
    
    75
    +        call_box.get_style_context().add_class('conversation-call-box')
    
    76
    +        call_box.get_style_context().add_class('gajim-call-message')
    
    77
    +
    
    78
    +        scale = self.get_scale_factor()
    
    79
    +        avatar = self._contact.get_avatar(
    
    80
    +            AvatarSize.CALL,
    
    81
    +            scale,
    
    82
    +            add_show=False)
    
    83
    +        avatar_image = Gtk.Image.new_from_surface(avatar)
    
    84
    +        call_box.add(avatar_image)
    
    85
    +
    
    86
    +        content_types = []
    
    87
    +        for item in self._event.contents:
    
    88
    +            content_types.append(item.media)
    
    89
    +        text = _('%s is calling') % self._contact.name
    
    90
    +        if 'video' in content_types:
    
    91
    +            text += _(' (Video Call)')
    
    92
    +        else:
    
    93
    +            text += _(' (Voice Call)')
    
    94
    +        label = Gtk.Label(label=text)
    
    95
    +        label.get_style_context().add_class('bold')
    
    96
    +        label.set_max_width_chars(40)
    
    97
    +        label.set_line_wrap(True)
    
    98
    +        call_box.add(label)
    
    99
    +
    
    100
    +        self._accept_button = Gtk.Button.new_with_label(_('Accept Call'))
    
    101
    +        self._accept_button.connect('clicked', self._on_accept)
    
    102
    +
    
    103
    +        self._reject_button = Gtk.Button.new_with_label(_('Reject'))
    
    104
    +        self._reject_button.connect('clicked', self._on_reject)
    
    105
    +
    
    106
    +        button_box = Gtk.Box(spacing=12)
    
    107
    +        button_box.set_halign(Gtk.Align.CENTER)
    
    108
    +        button_box.add(self._accept_button)
    
    109
    +        button_box.add(self._reject_button)
    
    110
    +        call_box.add(button_box)
    
    111
    +
    
    112
    +        self.grid.attach(call_box, 1, 0, 1, 1)

  • gajim/gtk/conversation/view.py
    ... ... @@ -29,6 +29,7 @@
    29 29
     from .rows.scroll_hint import ScrollHintRow
    
    30 30
     from .rows.message import MessageRow
    
    31 31
     from .rows.info import InfoMessage
    
    32
    +from .rows.call import CallRow
    
    32 33
     from .rows.date import DateRow
    
    33 34
     from .rows.file_transfer import FileTransferRow
    
    34 35
     from .rows.muc_subject import MUCSubject
    
    ... ... @@ -47,6 +48,11 @@ class ConversationView(Gtk.ListBox):
    47 48
                 None,
    
    48 49
                 (str, )
    
    49 50
             ),
    
    51
    +        'call-answered': (
    
    52
    +            GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
    
    53
    +            None,
    
    54
    +            (str, object)
    
    55
    +        ),
    
    50 56
         }
    
    51 57
     
    
    52 58
         def __init__(self, account, contact):
    
    ... ... @@ -168,6 +174,14 @@ def add_file_transfer(self, transfer):
    168 174
             transfer_row = FileTransferRow(self._account, transfer)
    
    169 175
             self._insert_message(transfer_row)
    
    170 176
     
    
    177
    +    def add_call_message(self, timestamp, text, event):
    
    178
    +        call_row = CallRow(self._account,
    
    179
    +                           self._contact,
    
    180
    +                           timestamp,
    
    181
    +                           text,
    
    182
    +                           event)
    
    183
    +        self._insert_message(call_row)
    
    184
    +
    
    171 185
         def add_message(self,
    
    172 186
                         text,
    
    173 187
                         kind,
    
    ... ... @@ -409,5 +423,8 @@ def show_error(self, id_, error):
    409 423
         def on_quote(self, text):
    
    410 424
             self.emit('quote', text)
    
    411 425
     
    
    426
    +    def on_call_answered(self, action, event):
    
    427
    +        self.emit('call-answered', action, event)
    
    428
    +
    
    412 429
         def _on_contact_setting_changed(self, *args):
    
    413 430
             self.invalidate_filter()

  • gajim/gui_interface.py
    ... ... @@ -1022,32 +1022,22 @@ def _popup_ft_result(self, account, jid, file_props):
    1022 1022
                     text=txt)
    
    1023 1023
     
    
    1024 1024
         # Jingle AV handling
    
    1025
    -    def handle_event_jingle_incoming(self, event):
    
    1026
    -        # ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
    
    1027
    -        # data...))
    
    1025
    +    @staticmethod
    
    1026
    +    def handle_event_jingle_incoming(event):
    
    1027
    +        # ('JINGLE_INCOMING', conn, fjid, sid, resource, jingle session,
    
    1028
    +        # tuple-of-contents==(type, data...))
    
    1028 1029
             # TODO: conditional blocking if peer is not in roster
    
    1029 1030
     
    
    1030 1031
             account = event.conn.name
    
    1032
    +
    
    1031 1033
             content_types = []
    
    1032 1034
             for item in event.contents:
    
    1033 1035
                 content_types.append(item.media)
    
    1034
    -        # check type of jingle session
    
    1035
    -        if 'audio' in content_types or 'video' in content_types:
    
    1036
    -            # a voip session...
    
    1037
    -            # we now handle only voip, so the only thing we will do here is
    
    1038
    -            # not to return from function
    
    1039
    -            pass
    
    1040
    -        else:
    
    1041
    -            # unknown session type... it should be declined in common/jingle.py
    
    1042
    -            return
    
    1043 1036
     
    
    1044
    -        notification_event = events.JingleIncomingEvent(
    
    1045
    -            event.fjid,
    
    1046
    -            event.sid,
    
    1047
    -            content_types)
    
    1037
    +        if 'audio' not in content_types and 'video' not in content_types:
    
    1038
    +            return
    
    1048 1039
     
    
    1049
    -        ctrl = (app.window.get_control(account, event.fjid) or
    
    1050
    -                app.window.get_control(account, event.jid))
    
    1040
    +        ctrl = app.window.get_control(account, event.jid)
    
    1051 1041
             if ctrl:
    
    1052 1042
                 if 'audio' in content_types:
    
    1053 1043
                     ctrl.set_jingle_state(
    
    ... ... @@ -1059,25 +1049,24 @@ def handle_event_jingle_incoming(self, event):
    1059 1049
                         'video',
    
    1060 1050
                         JingleState.CONNECTION_RECEIVED,
    
    1061 1051
                         event.sid)
    
    1062
    -            ctrl.add_call_received_message(notification_event)
    
    1052
    +            ctrl.add_incoming_call(event)
    
    1063 1053
     
    
    1064 1054
             if helpers.allow_popup_window(account):
    
    1065 1055
                 def _prepare_control():
    
    1066 1056
                     ctrl = app.window.get_control(account, event.jid)
    
    1067
    -                ctrl.add_call_received_message(notification_event)
    
    1057
    +                ctrl.add_incoming_call(event)
    
    1068 1058
     
    
    1069 1059
                 if not ctrl:
    
    1070 1060
                     app.window.add_chat(
    
    1071 1061
                         account, event.jid, 'contact', select=True)
    
    1072
    -            GLib.idle_add(_prepare_control)
    
    1073
    -            return
    
    1074
    -
    
    1075
    -        self.add_event(account, event.fjid, notification_event)
    
    1062
    +                GLib.idle_add(_prepare_control)
    
    1063
    +                return
    
    1076 1064
     
    
    1077 1065
             if helpers.allow_showing_notification(account):
    
    1078 1066
                 heading = _('Incoming Call')
    
    1079
    -            contact = app.get_name_from_jid(account, event.jid)
    
    1080
    -            text = _('%s is calling') % contact
    
    1067
    +            client = app.get_client(account)
    
    1068
    +            contact = client.get_module('Contacts').get_contact(event.jid)
    
    1069
    +            text = _('%s is calling') % contact.name
    
    1081 1070
                 app.notification.popup(
    
    1082 1071
                     heading,
    
    1083 1072
                     event.jid,
    
    ... ... @@ -1092,11 +1081,10 @@ def handle_event_jingle_connected(event):
    1092 1081
             # ('JINGLE_CONNECTED', account, (peerjid, sid, media))
    
    1093 1082
             if event.media in ('audio', 'video'):
    
    1094 1083
                 account = event.conn.name
    
    1095
    -            ctrl = (app.window.get_control(account, event.fjid) or
    
    1096
    -                    app.window.get_control(account, event.jid))
    
    1084
    +            ctrl = app.window.get_control(account, event.jid)
    
    1097 1085
                 if ctrl:
    
    1098
    -                con = app.connections[account]
    
    1099
    -                session = con.get_module('Jingle').get_jingle_session(
    
    1086
    +                client = app.get_client(account)
    
    1087
    +                session = client.get_module('Jingle').get_jingle_session(
    
    1100 1088
                         event.fjid, event.sid)
    
    1101 1089
     
    
    1102 1090
                     if event.media == 'audio':
    
    ... ... @@ -1123,8 +1111,7 @@ def handle_event_jingle_connected(event):
    1123 1111
         def handle_event_jingle_disconnected(event):
    
    1124 1112
             # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
    
    1125 1113
             account = event.conn.name
    
    1126
    -        ctrl = (app.window.get_control(account, event.fjid) or
    
    1127
    -                app.window.get_control(account, event.jid))
    
    1114
    +        ctrl = app.window.get_control(account, event.jid)
    
    1128 1115
             if ctrl:
    
    1129 1116
                 if event.media is None:
    
    1130 1117
                     ctrl.stop_jingle(sid=event.sid, reason=event.reason)
    
    ... ... @@ -1145,8 +1132,7 @@ def handle_event_jingle_disconnected(event):
    1145 1132
         def handle_event_jingle_error(event):
    
    1146 1133
             # ('JINGLE_ERROR', account, (peerjid, sid, reason))
    
    1147 1134
             account = event.conn.name
    
    1148
    -        ctrl = (app.window.get_control(account, event.fjid) or
    
    1149
    -                app.window.get_control(account, event.jid))
    
    1135
    +        ctrl = app.window.get_control(account, event.jid)
    
    1150 1136
             if ctrl and event.sid == ctrl.jingle['audio'].sid:
    
    1151 1137
                 ctrl.set_jingle_state(
    
    1152 1138
                     'audio',