comparison cagou/plugins/plugin_wid_chat.py @ 491:203755bbe0fe

massive refactoring from camelCase -> snake_case. See backend commit log for more details
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:44:32 +0200
parents 962d17c4078c
children
comparison
equal deleted inserted replaced
490:962d17c4078c 491:203755bbe0fe
98 attachments = properties.ObjectProperty() 98 attachments = properties.ObjectProperty()
99 reduce_checkbox = properties.ObjectProperty() 99 reduce_checkbox = properties.ObjectProperty()
100 show_resize = properties.BooleanProperty(False) 100 show_resize = properties.BooleanProperty(False)
101 101
102 def on_kv_post(self, __): 102 def on_kv_post(self, __):
103 self.attachments.bind(children=self.onAttachment) 103 self.attachments.bind(children=self.on_attachment)
104 104
105 def onAttachment(self, __, attachments): 105 def on_attachment(self, __, attachments):
106 if len(attachments) == 0: 106 if len(attachments) == 0:
107 self.show_resize = False 107 self.show_resize = False
108 108
109 109
110 class BaseAttachmentItem(BoxLayout): 110 class BaseAttachmentItem(BoxLayout):
138 image = properties.ObjectProperty() 138 image = properties.ObjectProperty()
139 139
140 def on_press(self): 140 def on_press(self):
141 full_size_source = self.data.get('path', self.data.get('url')) 141 full_size_source = self.data.get('path', self.data.get('url'))
142 gallery = ImagesGallery(sources=[full_size_source]) 142 gallery = ImagesGallery(sources=[full_size_source])
143 G.host.showExtraUI(gallery) 143 G.host.show_extra_ui(gallery)
144 144
145 def on_kv_post(self, __): 145 def on_kv_post(self, __):
146 self.on_data(None, self.data) 146 self.on_data(None, self.data)
147 147
148 def on_data(self, __, data): 148 def on_data(self, __, data):
156 class AttachmentImagesCollectionItem(ButtonBehavior, GridLayout): 156 class AttachmentImagesCollectionItem(ButtonBehavior, GridLayout):
157 attachments = properties.ListProperty([]) 157 attachments = properties.ListProperty([])
158 chat = properties.ObjectProperty() 158 chat = properties.ObjectProperty()
159 mess_data = properties.ObjectProperty() 159 mess_data = properties.ObjectProperty()
160 160
161 def _setPreview(self, attachment, wid, preview_path): 161 def _set_preview(self, attachment, wid, preview_path):
162 attachment['preview'] = preview_path 162 attachment['preview'] = preview_path
163 wid.source = preview_path 163 wid.source = preview_path
164 164
165 def _setPath(self, attachment, wid, path): 165 def _set_path(self, attachment, wid, path):
166 attachment['path'] = path 166 attachment['path'] = path
167 if wid is not None: 167 if wid is not None:
168 # we also need a preview for the widget 168 # we also need a preview for the widget
169 if 'preview' in attachment: 169 if 'preview' in attachment:
170 wid.source = attachment['preview'] 170 wid.source = attachment['preview']
171 else: 171 else:
172 G.host.bridge.imageGeneratePreview( 172 G.host.bridge.image_generate_preview(
173 path, 173 path,
174 self.chat.profile, 174 self.chat.profile,
175 callback=partial(self._setPreview, attachment, wid), 175 callback=partial(self._set_preview, attachment, wid),
176 ) 176 )
177 177
178 def on_kv_post(self, __): 178 def on_kv_post(self, __):
179 attachments = self.attachments 179 attachments = self.attachments
180 self.clear_widgets() 180 self.clear_widgets()
192 else: 192 else:
193 to_download = False 193 to_download = False
194 194
195 if idx < 3 or len(attachments) <= 4: 195 if idx < 3 or len(attachments) <= 4:
196 if ((self.mess_data.own_mess 196 if ((self.mess_data.own_mess
197 or self.chat.contact_list.isInRoster(self.mess_data.from_jid))): 197 or self.chat.contact_list.is_in_roster(self.mess_data.from_jid))):
198 wid = AsyncImage(size_hint=(1, 1), source="data/images/image-loading.gif") 198 wid = AsyncImage(size_hint=(1, 1), source="data/images/image-loading.gif")
199 if 'preview' in attachment: 199 if 'preview' in attachment:
200 wid.source = attachment["preview"] 200 wid.source = attachment["preview"]
201 elif 'path' in attachment: 201 elif 'path' in attachment:
202 G.host.bridge.imageGeneratePreview( 202 G.host.bridge.image_generate_preview(
203 attachment['path'], 203 attachment['path'],
204 self.chat.profile, 204 self.chat.profile,
205 callback=partial(self._setPreview, attachment, wid), 205 callback=partial(self._set_preview, attachment, wid),
206 ) 206 )
207 elif url is None: 207 elif url is None:
208 log.warning(f"Can't find any source for {attachment}") 208 log.warning(f"Can't find any source for {attachment}")
209 else: 209 else:
210 # we'll download the file, the preview will then be generated 210 # we'll download the file, the preview will then be generated
218 wid = None 218 wid = None
219 219
220 if to_download: 220 if to_download:
221 # the file needs to be downloaded, the widget source, 221 # the file needs to be downloaded, the widget source,
222 # attachment path, and preview will then be completed 222 # attachment path, and preview will then be completed
223 G.host.downloadURL( 223 G.host.download_url(
224 url, 224 url,
225 callback=partial(self._setPath, attachment, wid), 225 callback=partial(self._set_path, attachment, wid),
226 dest=C.FILE_DEST_CACHE, 226 dest=C.FILE_DEST_CACHE,
227 profile=self.chat.profile, 227 profile=self.chat.profile,
228 ) 228 )
229 229
230 if len(attachments) > 4: 230 if len(attachments) > 4:
241 if not source: 241 if not source:
242 log.warning(f"no source for {attachment}") 242 log.warning(f"no source for {attachment}")
243 else: 243 else:
244 sources.append(source) 244 sources.append(source)
245 gallery = ImagesGallery(sources=sources) 245 gallery = ImagesGallery(sources=sources)
246 G.host.showExtraUI(gallery) 246 G.host.show_extra_ui(gallery)
247 247
248 248
249 class AttachmentToSendItem(AttachmentItem): 249 class AttachmentToSendItem(AttachmentItem):
250 # True when the item is being sent 250 # True when the item is being sent
251 sending = properties.BooleanProperty(False) 251 sending = properties.BooleanProperty(False)
320 320
321 def update(self, update_dict): 321 def update(self, update_dict):
322 if 'avatar' in update_dict: 322 if 'avatar' in update_dict:
323 avatar_data = update_dict['avatar'] 323 avatar_data = update_dict['avatar']
324 if avatar_data is None: 324 if avatar_data is None:
325 source = G.host.getDefaultAvatar() 325 source = G.host.get_default_avatar()
326 else: 326 else:
327 source = avatar_data['path'] 327 source = avatar_data['path']
328 self.avatar.source = source 328 self.avatar.source = source
329 if 'status' in update_dict: 329 if 'status' in update_dict:
330 status = update_dict['status'] 330 status = update_dict['status']
331 self.delivery.text = '\u2714' if status == 'delivered' else '' 331 self.delivery.text = '\u2714' if status == 'delivered' else ''
332 332
333 def _setPath(self, data, path): 333 def _set_path(self, data, path):
334 """Set path of decrypted file to an item""" 334 """Set path of decrypted file to an item"""
335 data['path'] = path 335 data['path'] = path
336 336
337 def add_attachments(self): 337 def add_attachments(self):
338 """Add attachments layout + attachments item""" 338 """Add attachments layout + attachments item"""
370 elif image_attachments: 370 elif image_attachments:
371 attachment = image_attachments[0] 371 attachment = image_attachments[0]
372 # to avoid leaking IP address, we only display image if the contact is in 372 # to avoid leaking IP address, we only display image if the contact is in
373 # roster 373 # roster
374 if ((self.mess_data.own_mess 374 if ((self.mess_data.own_mess
375 or self.chat.contact_list.isInRoster(self.mess_data.from_jid))): 375 or self.chat.contact_list.is_in_roster(self.mess_data.from_jid))):
376 try: 376 try:
377 url = urlparse(attachment['url']) 377 url = urlparse(attachment['url'])
378 except KeyError: 378 except KeyError:
379 item = AttachmentImageItem(data=attachment) 379 item = AttachmentImageItem(data=attachment)
380 else: 380 else:
381 if url.scheme == "aesgcm": 381 if url.scheme == "aesgcm":
382 # we remove the URL now, we'll replace it by 382 # we remove the URL now, we'll replace it by
383 # the local decrypted version 383 # the local decrypted version
384 del attachment['url'] 384 del attachment['url']
385 item = AttachmentImageItem(data=attachment) 385 item = AttachmentImageItem(data=attachment)
386 G.host.downloadURL( 386 G.host.download_url(
387 url.geturl(), 387 url.geturl(),
388 callback=partial(self._setPath, item.data), 388 callback=partial(self._set_path, item.data),
389 dest=C.FILE_DEST_CACHE, 389 dest=C.FILE_DEST_CACHE,
390 profile=self.chat.profile, 390 profile=self.chat.profile,
391 ) 391 )
392 else: 392 else:
393 item = AttachmentImageItem(data=attachment) 393 item = AttachmentImageItem(data=attachment)
430 chat = properties.ObjectProperty() 430 chat = properties.ObjectProperty()
431 431
432 def on_release(self, *args): 432 def on_release(self, *args):
433 menu.TransferMenu( 433 menu.TransferMenu(
434 encrypted=self.chat.encrypted, 434 encrypted=self.chat.encrypted,
435 callback=self.chat.transferFile, 435 callback=self.chat.transfer_file,
436 ).show(self) 436 ).show(self)
437 437
438 438
439 class ExtraMenu(DropDown): 439 class ExtraMenu(DropDown):
440 chat = properties.ObjectProperty() 440 chat = properties.ObjectProperty()
441 441
442 def on_select(self, menu): 442 def on_select(self, menu):
443 if menu == 'bookmark': 443 if menu == 'bookmark':
444 G.host.bridge.menuLaunch(C.MENU_GLOBAL, ("groups", "bookmarks"), 444 G.host.bridge.menu_launch(C.MENU_GLOBAL, ("groups", "bookmarks"),
445 {}, C.NO_SECURITY_LIMIT, self.chat.profile, 445 {}, C.NO_SECURITY_LIMIT, self.chat.profile,
446 callback=partial( 446 callback=partial(
447 G.host.actionManager, profile=self.chat.profile), 447 G.host.action_manager, profile=self.chat.profile),
448 errback=G.host.errback) 448 errback=G.host.errback)
449 elif menu == 'close': 449 elif menu == 'close':
450 if self.chat.type == C.CHAT_GROUP: 450 if self.chat.type == C.CHAT_GROUP:
451 # for MUC, we have to indicate the backend that we've left 451 # for MUC, we have to indicate the backend that we've left
452 G.host.bridge.mucLeave(self.chat.target, self.chat.profile) 452 G.host.bridge.muc_leave(self.chat.target, self.chat.profile)
453 else: 453 else:
454 # for one2one, backend doesn't keep any state, so we just delete the 454 # for one2one, backend doesn't keep any state, so we just delete the
455 # widget here in the frontend 455 # widget here in the frontend
456 G.host.widgets.deleteWidget( 456 G.host.widgets.delete_widget(
457 self.chat, all_instances=True, explicit_close=True) 457 self.chat, all_instances=True, explicit_close=True)
458 else: 458 else:
459 raise exceptions.InternalError("Unknown menu: {}".format(menu)) 459 raise exceptions.InternalError("Unknown menu: {}".format(menu))
460 460
461 461
472 self.chat = chat 472 self.chat = chat
473 self.encryption_menu = EncryptionMenu(chat) 473 self.encryption_menu = EncryptionMenu(chat)
474 super(EncryptionMainButton, self).__init__(**kwargs) 474 super(EncryptionMainButton, self).__init__(**kwargs)
475 self.bind(on_release=self.encryption_menu.open) 475 self.bind(on_release=self.encryption_menu.open)
476 476
477 def selectAlgo(self, name): 477 def select_algo(self, name):
478 """Mark an encryption algorithm as selected. 478 """Mark an encryption algorithm as selected.
479 479
480 This will also deselect all other button 480 This will also deselect all other button
481 @param name(unicode, None): encryption plugin name 481 @param name(unicode, None): encryption plugin name
482 None for plain text 482 None for plain text
484 buttons = self.encryption_menu.container.children 484 buttons = self.encryption_menu.container.children
485 buttons[-1].selected = name is None 485 buttons[-1].selected = name is None
486 for button in buttons[:-1]: 486 for button in buttons[:-1]:
487 button.selected = button.text == name 487 button.selected = button.text == name
488 488
489 def getColor(self): 489 def get_color(self):
490 if self.chat.otr_state_encryption == OTR_STATE_UNENCRYPTED: 490 if self.chat.otr_state_encryption == OTR_STATE_UNENCRYPTED:
491 return (0.4, 0.4, 0.4, 1) 491 return (0.4, 0.4, 0.4, 1)
492 elif self.chat.otr_state_trust == OTR_STATE_TRUSTED: 492 elif self.chat.otr_state_trust == OTR_STATE_TRUSTED:
493 return (0.29,0.87,0.0,1) 493 return (0.29,0.87,0.0,1)
494 else: 494 else:
495 return (0.4, 0.4, 0.4, 1) 495 return (0.4, 0.4, 0.4, 1)
496 496
497 def getSymbol(self): 497 def get_symbol(self):
498 if self.chat.otr_state_encryption == OTR_STATE_UNENCRYPTED: 498 if self.chat.otr_state_encryption == OTR_STATE_UNENCRYPTED:
499 return 'lock-open' 499 return 'lock-open'
500 elif self.chat.otr_state_trust == OTR_STATE_TRUSTED: 500 elif self.chat.otr_state_trust == OTR_STATE_TRUSTED:
501 return 'lock-filled' 501 return 'lock-filled'
502 else: 502 else:
555 btn = EncryptionButton( 555 btn = EncryptionButton(
556 text=plugin['name'], 556 text=plugin['name'],
557 trust_button=True, 557 trust_button=True,
558 ) 558 )
559 btn.bind( 559 btn.bind(
560 on_release=partial(self.startEncryption, plugin=plugin), 560 on_release=partial(self.start_encryption, plugin=plugin),
561 on_trust_release=partial(self.getTrustUI, plugin=plugin), 561 on_trust_release=partial(self.get_trust_ui, plugin=plugin),
562 ) 562 )
563 self.add_widget(btn) 563 self.add_widget(btn)
564 log.info("added encryption: {}".format(plugin['name'])) 564 log.info("added encryption: {}".format(plugin['name']))
565 565
566 def messageEncryptionStopCb(self): 566 def message_encryption_stop_cb(self):
567 log.info(_("Session with {destinee} is now in plain text").format( 567 log.info(_("Session with {destinee} is now in plain text").format(
568 destinee = self.chat.target)) 568 destinee = self.chat.target))
569 569
570 def messageEncryptionStopEb(self, failure_): 570 def message_encryption_stop_eb(self, failure_):
571 msg = _("Error while stopping encryption with {destinee}: {reason}").format( 571 msg = _("Error while stopping encryption with {destinee}: {reason}").format(
572 destinee = self.chat.target, 572 destinee = self.chat.target,
573 reason = failure_) 573 reason = failure_)
574 log.warning(msg) 574 log.warning(msg)
575 G.host.addNote(_("encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR) 575 G.host.add_note(_("encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR)
576 576
577 def unencrypted(self, button): 577 def unencrypted(self, button):
578 self.dismiss() 578 self.dismiss()
579 G.host.bridge.messageEncryptionStop( 579 G.host.bridge.message_encryption_stop(
580 str(self.chat.target), 580 str(self.chat.target),
581 self.chat.profile, 581 self.chat.profile,
582 callback=self.messageEncryptionStopCb, 582 callback=self.message_encryption_stop_cb,
583 errback=self.messageEncryptionStopEb) 583 errback=self.message_encryption_stop_eb)
584 584
585 def messageEncryptionStartCb(self, plugin): 585 def message_encryption_start_cb(self, plugin):
586 log.info(_("Session with {destinee} is now encrypted with {encr_name}").format( 586 log.info(_("Session with {destinee} is now encrypted with {encr_name}").format(
587 destinee = self.chat.target, 587 destinee = self.chat.target,
588 encr_name = plugin['name'])) 588 encr_name = plugin['name']))
589 589
590 def messageEncryptionStartEb(self, failure_): 590 def message_encryption_start_eb(self, failure_):
591 msg = _("Session can't be encrypted with {destinee}: {reason}").format( 591 msg = _("Session can't be encrypted with {destinee}: {reason}").format(
592 destinee = self.chat.target, 592 destinee = self.chat.target,
593 reason = failure_) 593 reason = failure_)
594 log.warning(msg) 594 log.warning(msg)
595 G.host.addNote(_("encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR) 595 G.host.add_note(_("encryption problem"), msg, C.XMLUI_DATA_LVL_ERROR)
596 596
597 def startEncryption(self, button, plugin): 597 def start_encryption(self, button, plugin):
598 """Request encryption with given plugin for this session 598 """Request encryption with given plugin for this session
599 599
600 @param button(EncryptionButton): button which has been pressed 600 @param button(EncryptionButton): button which has been pressed
601 @param plugin(dict): plugin data 601 @param plugin(dict): plugin data
602 """ 602 """
603 self.dismiss() 603 self.dismiss()
604 G.host.bridge.messageEncryptionStart( 604 G.host.bridge.message_encryption_start(
605 str(self.chat.target), 605 str(self.chat.target),
606 plugin['namespace'], 606 plugin['namespace'],
607 True, 607 True,
608 self.chat.profile, 608 self.chat.profile,
609 callback=partial(self.messageEncryptionStartCb, plugin=plugin), 609 callback=partial(self.message_encryption_start_cb, plugin=plugin),
610 errback=self.messageEncryptionStartEb) 610 errback=self.message_encryption_start_eb)
611 611
612 def encryptionTrustUIGetCb(self, xmlui_raw): 612 def encryption_trust_ui_get_cb(self, xmlui_raw):
613 xml_ui = xmlui.create( 613 xml_ui = xmlui.create(
614 G.host, xmlui_raw, profile=self.chat.profile) 614 G.host, xmlui_raw, profile=self.chat.profile)
615 xml_ui.show() 615 xml_ui.show()
616 616
617 def encryptionTrustUIGetEb(self, failure_): 617 def encryption_trust_ui_get_eb(self, failure_):
618 msg = _("Trust manager interface can't be retrieved: {reason}").format( 618 msg = _("Trust manager interface can't be retrieved: {reason}").format(
619 reason = failure_) 619 reason = failure_)
620 log.warning(msg) 620 log.warning(msg)
621 G.host.addNote(_("encryption trust management problem"), msg, 621 G.host.add_note(_("encryption trust management problem"), msg,
622 C.XMLUI_DATA_LVL_ERROR) 622 C.XMLUI_DATA_LVL_ERROR)
623 623
624 def getTrustUI(self, button, plugin): 624 def get_trust_ui(self, button, plugin):
625 """Request and display trust management UI 625 """Request and display trust management UI
626 626
627 @param button(EncryptionButton): button which has been pressed 627 @param button(EncryptionButton): button which has been pressed
628 @param plugin(dict): plugin data 628 @param plugin(dict): plugin data
629 """ 629 """
630 self.dismiss() 630 self.dismiss()
631 G.host.bridge.encryptionTrustUIGet( 631 G.host.bridge.encryption_trust_ui_get(
632 str(self.chat.target), 632 str(self.chat.target),
633 plugin['namespace'], 633 plugin['namespace'],
634 self.chat.profile, 634 self.chat.profile,
635 callback=self.encryptionTrustUIGetCb, 635 callback=self.encryption_trust_ui_get_cb,
636 errback=self.encryptionTrustUIGetEb) 636 errback=self.encryption_trust_ui_get_eb)
637 637
638 638
639 class Chat(quick_chat.QuickChat, cagou_widget.CagouWidget): 639 class Chat(quick_chat.QuickChat, cagou_widget.CagouWidget):
640 message_input = properties.ObjectProperty() 640 message_input = properties.ObjectProperty()
641 messages_widget = properties.ObjectProperty() 641 messages_widget = properties.ObjectProperty()
661 self._hi_comp_last = None 661 self._hi_comp_last = None
662 self._hi_comp_dropdown = DropDown() 662 self._hi_comp_dropdown = DropDown()
663 self._hi_comp_allowed = True 663 self._hi_comp_allowed = True
664 cagou_widget.CagouWidget.__init__(self) 664 cagou_widget.CagouWidget.__init__(self)
665 transfer_btn = TransferButton(chat=self) 665 transfer_btn = TransferButton(chat=self)
666 self.headerInputAddExtra(transfer_btn) 666 self.header_input_add_extra(transfer_btn)
667 if (type_ == C.CHAT_ONE2ONE or "REALJID_PUBLIC" in statuses): 667 if (type_ == C.CHAT_ONE2ONE or "REALJID_PUBLIC" in statuses):
668 self.encryption_btn = EncryptionMainButton(self) 668 self.encryption_btn = EncryptionMainButton(self)
669 self.headerInputAddExtra(self.encryption_btn) 669 self.header_input_add_extra(self.encryption_btn)
670 self.extra_menu = ExtraMenu(chat=self) 670 self.extra_menu = ExtraMenu(chat=self)
671 extra_btn = ExtraButton(chat=self) 671 extra_btn = ExtraButton(chat=self)
672 self.headerInputAddExtra(extra_btn) 672 self.header_input_add_extra(extra_btn)
673 self.header_input.hint_text = target 673 self.header_input.hint_text = target
674 self._history_prepend_lock = False 674 self._history_prepend_lock = False
675 self.history_count = 0 675 self.history_count = 0
676 676
677 def on_kv_post(self, __): 677 def on_kv_post(self, __):
678 self.postInit() 678 self.post_init()
679 679
680 def screenManagerInit(self, screen_manager): 680 def screen_manager_init(self, screen_manager):
681 screen_manager.transition = screenmanager.SlideTransition(direction='down') 681 screen_manager.transition = screenmanager.SlideTransition(direction='down')
682 sel_screen = Screen(name='chat_selector') 682 sel_screen = Screen(name='chat_selector')
683 chat_selector = ChatSelector(profile=self.profile) 683 chat_selector = ChatSelector(profile=self.profile)
684 sel_screen.add_widget(chat_selector) 684 sel_screen.add_widget(chat_selector)
685 screen_manager.add_widget(sel_screen) 685 screen_manager.add_widget(sel_screen)
703 if target is None: 703 if target is None:
704 show_chat_selector = True 704 show_chat_selector = True
705 target = G.host.profiles[profiles[0]].whoami 705 target = G.host.profiles[profiles[0]].whoami
706 else: 706 else:
707 show_chat_selector = False 707 show_chat_selector = False
708 wid = G.host.widgets.getOrCreateWidget(cls, target, on_new_widget=None, 708 wid = G.host.widgets.get_or_create_widget(cls, target, on_new_widget=None,
709 on_existing_widget=G.host.getOrClone, 709 on_existing_widget=G.host.get_or_clone,
710 profiles=profiles) 710 profiles=profiles)
711 wid.show_chat_selector = show_chat_selector 711 wid.show_chat_selector = show_chat_selector
712 return wid 712 return wid
713 713
714 @property 714 @property
725 return True 725 return True
726 726
727 ## drop ## 727 ## drop ##
728 728
729 def on_drop_file(self, path): 729 def on_drop_file(self, path):
730 self.addAttachment(path) 730 self.add_attachment(path)
731 731
732 ## header ## 732 ## header ##
733 733
734 def changeWidget(self, jid_): 734 def change_widget(self, jid_):
735 """change current widget for a new one with given jid 735 """change current widget for a new one with given jid
736 736
737 @param jid_(jid.JID): jid of the widget to create 737 @param jid_(jid.JID): jid of the widget to create
738 """ 738 """
739 plugin_info = G.host.getPluginInfo(main=Chat) 739 plugin_info = G.host.get_plugin_info(main=Chat)
740 factory = plugin_info['factory'] 740 factory = plugin_info['factory']
741 G.host.switchWidget(self, factory(plugin_info, jid_, profiles=[self.profile])) 741 G.host.switch_widget(self, factory(plugin_info, jid_, profiles=[self.profile]))
742 self.header_input.text = '' 742 self.header_input.text = ''
743 743
744 def onHeaderInput(self): 744 def on_header_wid_input(self):
745 text = self.header_input.text.strip() 745 text = self.header_input.text.strip()
746 try: 746 try:
747 if text.count('@') != 1 or text.count(' '): 747 if text.count('@') != 1 or text.count(' '):
748 raise ValueError 748 raise ValueError
749 jid_ = jid.JID(text) 749 jid_ = jid.JID(text)
750 except ValueError: 750 except ValueError:
751 log.info("entered text is not a jid") 751 log.info("entered text is not a jid")
752 return 752 return
753 753
754 def discoCb(disco): 754 def disco_cb(disco):
755 # TODO: check if plugin XEP-0045 is activated 755 # TODO: check if plugin XEP-0045 is activated
756 if "conference" in [i[0] for i in disco[1]]: 756 if "conference" in [i[0] for i in disco[1]]:
757 G.host.bridge.mucJoin(str(jid_), "", "", self.profile, 757 G.host.bridge.muc_join(str(jid_), "", "", self.profile,
758 callback=self._mucJoinCb, errback=self._mucJoinEb) 758 callback=self._muc_join_cb, errback=self._muc_join_eb)
759 else: 759 else:
760 self.changeWidget(jid_) 760 self.change_widget(jid_)
761 761
762 def discoEb(failure): 762 def disco_eb(failure):
763 log.warning("Disco failure, ignore this text: {}".format(failure)) 763 log.warning("Disco failure, ignore this text: {}".format(failure))
764 764
765 G.host.bridge.discoInfos( 765 G.host.bridge.disco_infos(
766 jid_.domain, 766 jid_.domain,
767 profile_key=self.profile, 767 profile_key=self.profile,
768 callback=discoCb, 768 callback=disco_cb,
769 errback=discoEb) 769 errback=disco_eb)
770 770
771 def onHeaderInputCompleted(self, input_wid, completed_text): 771 def on_header_wid_input_completed(self, input_wid, completed_text):
772 self._hi_comp_allowed = False 772 self._hi_comp_allowed = False
773 input_wid.text = completed_text 773 input_wid.text = completed_text
774 self._hi_comp_allowed = True 774 self._hi_comp_allowed = True
775 self._hi_comp_dropdown.dismiss() 775 self._hi_comp_dropdown.dismiss()
776 self.onHeaderInput() 776 self.on_header_wid_input()
777 777
778 def onHeaderInputComplete(self, wid, text): 778 def on_header_wid_input_complete(self, wid, text):
779 if not self._hi_comp_allowed: 779 if not self._hi_comp_allowed:
780 return 780 return
781 text = text.lstrip() 781 text = text.lstrip()
782 if not text: 782 if not text:
783 self._hi_comp_data = None 783 self._hi_comp_data = None
811 btn = JidButton( 811 btn = JidButton(
812 jid = jid_.bare, 812 jid = jid_.bare,
813 profile = profile, 813 profile = profile,
814 size_hint = (0.5, None), 814 size_hint = (0.5, None),
815 nick = nick, 815 nick = nick,
816 on_release=lambda __, txt=jid_.bare: self.onHeaderInputCompleted(wid, txt) 816 on_release=lambda __, txt=jid_.bare: self.on_header_wid_input_completed(wid, txt)
817 ) 817 )
818 dropdown.add_widget(btn) 818 dropdown.add_widget(btn)
819 else: 819 else:
820 # more chars, we continue completion by removing unwanted widgets 820 # more chars, we continue completion by removing unwanted widgets
821 to_remove = [] 821 to_remove = []
826 dropdown.remove_widget(c) 826 dropdown.remove_widget(c)
827 if dropdown.attach_to is None: 827 if dropdown.attach_to is None:
828 dropdown.open(wid) 828 dropdown.open(wid)
829 self._hi_comp_last = text 829 self._hi_comp_last = text
830 830
831 def messageDataConverter(self, idx, mess_id): 831 def message_data_converter(self, idx, mess_id):
832 return {"mess_data": self.messages[mess_id]} 832 return {"mess_data": self.messages[mess_id]}
833 833
834 def _onHistoryPrinted(self): 834 def _on_history_printed(self):
835 """Refresh or scroll down the focus after the history is printed""" 835 """Refresh or scroll down the focus after the history is printed"""
836 # self.adapter.data = self.messages 836 # self.adapter.data = self.messages
837 for mess_data in self.messages.values(): 837 for mess_data in self.messages.values():
838 self.appendMessage(mess_data) 838 self.appendMessage(mess_data)
839 super(Chat, self)._onHistoryPrinted() 839 super(Chat, self)._on_history_printed()
840 840
841 def createMessage(self, message): 841 def create_message(self, message):
842 self.appendMessage(message) 842 self.appendMessage(message)
843 # we need to render immediatly next 2 layouts to avoid an unpleasant flickering 843 # we need to render immediatly next 2 layouts to avoid an unpleasant flickering
844 # when sending or receiving a message 844 # when sending or receiving a message
845 self.messages_widget.dont_delay_next_layouts = 2 845 self.messages_widget.dont_delay_next_layouts = 2
846 846
847 def appendMessage(self, mess_data): 847 def appendMessage(self, mess_data):
848 """Append a message Widget to the history 848 """Append a message Widget to the history
849 849
850 @param mess_data(quick_chat.Message): message data 850 @param mess_data(quick_chat.Message): message data
851 """ 851 """
852 if self.handleUserMoved(mess_data): 852 if self.handle_user_moved(mess_data):
853 return 853 return
854 self.messages_widget.add_widget(MessageWidget(mess_data=mess_data)) 854 self.messages_widget.add_widget(MessageWidget(mess_data=mess_data))
855 self.notify(mess_data) 855 self.notify(mess_data)
856 856
857 def prependMessage(self, mess_data): 857 def prepend_message(self, mess_data):
858 """Prepend a message Widget to the history 858 """Prepend a message Widget to the history
859 859
860 @param mess_data(quick_chat.Message): message data 860 @param mess_data(quick_chat.Message): message data
861 """ 861 """
862 mess_wid = self.messages_widget 862 mess_wid = self.messages_widget
875 or when one2one chat is not visible. A note is also there when widget 875 or when one2one chat is not visible. A note is also there when widget
876 is not visible. 876 is not visible.
877 For group chat, note will be added on mention, with a desktop notification if 877 For group chat, note will be added on mention, with a desktop notification if
878 window has not focus or is not visible. 878 window has not focus or is not visible.
879 """ 879 """
880 visible_clones = [w for w in G.host.getVisibleList(self.__class__) 880 visible_clones = [w for w in G.host.get_visible_list(self.__class__)
881 if w.target == self.target] 881 if w.target == self.target]
882 if len(visible_clones) > 1 and visible_clones.index(self) > 0: 882 if len(visible_clones) > 1 and visible_clones.index(self) > 0:
883 # to avoid multiple notifications in case of multiple cloned widgets 883 # to avoid multiple notifications in case of multiple cloned widgets
884 # we only handle first clone 884 # we only handle first clone
885 return 885 return
894 subject=_("private message"), 894 subject=_("private message"),
895 widget=self, 895 widget=self,
896 profile=self.profile 896 profile=self.profile
897 ) 897 )
898 if not is_visible: 898 if not is_visible:
899 G.host.addNote( 899 G.host.add_note(
900 _("private message"), 900 _("private message"),
901 notif_msg, 901 notif_msg,
902 symbol = "chat", 902 symbol = "chat",
903 action = { 903 action = {
904 "action": 'chat', 904 "action": 'chat',
906 "profiles": self.profiles} 906 "profiles": self.profiles}
907 ) 907 )
908 else: 908 else:
909 if mess_data.mention: 909 if mess_data.mention:
910 notif_msg = self._get_notif_msg(mess_data) 910 notif_msg = self._get_notif_msg(mess_data)
911 G.host.addNote( 911 G.host.add_note(
912 _("mention"), 912 _("mention"),
913 notif_msg, 913 notif_msg,
914 symbol = "chat", 914 symbol = "chat",
915 action = { 915 action = {
916 "action": 'chat', 916 "action": 'chat',
928 profile=self.profile 928 profile=self.profile
929 ) 929 )
930 930
931 # message input 931 # message input
932 932
933 def _attachmentProgressCb(self, item, metadata, profile): 933 def _attachment_progress_cb(self, item, metadata, profile):
934 item.parent.remove_widget(item) 934 item.parent.remove_widget(item)
935 log.info(f"item {item.data.get('path')} uploaded successfully") 935 log.info(f"item {item.data.get('path')} uploaded successfully")
936 936
937 def _attachmentProgressEb(self, item, err_msg, profile): 937 def _attachment_progress_eb(self, item, err_msg, profile):
938 item.parent.remove_widget(item) 938 item.parent.remove_widget(item)
939 path = item.data.get('path') 939 path = item.data.get('path')
940 msg = _("item {path} could not be uploaded: {err_msg}").format( 940 msg = _("item {path} could not be uploaded: {err_msg}").format(
941 path=path, err_msg=err_msg) 941 path=path, err_msg=err_msg)
942 G.host.addNote(_("can't upload file"), msg, C.XMLUI_DATA_LVL_WARNING) 942 G.host.add_note(_("can't upload file"), msg, C.XMLUI_DATA_LVL_WARNING)
943 log.warning(msg) 943 log.warning(msg)
944 944
945 def _progressGetCb(self, item, metadata): 945 def _progress_get_cb(self, item, metadata):
946 try: 946 try:
947 position = int(metadata["position"]) 947 position = int(metadata["position"])
948 size = int(metadata["size"]) 948 size = int(metadata["size"])
949 except KeyError: 949 except KeyError:
950 # we got empty metadata, the progression is either not yet started or 950 # we got empty metadata, the progression is either not yet started or
958 item.progress = position/size*100 958 item.progress = position/size*100
959 959
960 if item.parent is not None: 960 if item.parent is not None:
961 # the item is not yet fully received, we reschedule an update 961 # the item is not yet fully received, we reschedule an update
962 Clock.schedule_once( 962 Clock.schedule_once(
963 partial(self._attachmentProgressUpdate, item), 963 partial(self._attachment_progress_update, item),
964 PROGRESS_UPDATE) 964 PROGRESS_UPDATE)
965 965
966 def _attachmentProgressUpdate(self, item, __): 966 def _attachment_progress_update(self, item, __):
967 G.host.bridge.progressGet( 967 G.host.bridge.progress_get(
968 item.data["progress_id"], 968 item.data["progress_id"],
969 self.profile, 969 self.profile,
970 callback=partial(self._progressGetCb, item), 970 callback=partial(self._progress_get_cb, item),
971 errback=G.host.errback, 971 errback=G.host.errback,
972 ) 972 )
973 973
974 def addNick(self, nick): 974 def add_nick(self, nick):
975 """Add a nickname to message_input if suitable""" 975 """Add a nickname to message_input if suitable"""
976 if (self.type == C.CHAT_GROUP and not self.message_input.text.startswith(nick)): 976 if (self.type == C.CHAT_GROUP and not self.message_input.text.startswith(nick)):
977 self.message_input.text = f'{nick}: {self.message_input.text}' 977 self.message_input.text = f'{nick}: {self.message_input.text}'
978 978
979 def onSend(self, input_widget): 979 def on_send(self, input_widget):
980 extra = {} 980 extra = {}
981 for item in self.attachments_to_send.attachments.children: 981 for item in self.attachments_to_send.attachments.children:
982 if item.sending: 982 if item.sending:
983 # the item is already being sent 983 # the item is already being sent
984 continue 984 continue
997 attachment[C.KEY_ATTACHMENTS_RESIZE] = True 997 attachment[C.KEY_ATTACHMENTS_RESIZE] = True
998 998
999 attachments.append(attachment) 999 attachments.append(attachment)
1000 1000
1001 Clock.schedule_once( 1001 Clock.schedule_once(
1002 partial(self._attachmentProgressUpdate, item), 1002 partial(self._attachment_progress_update, item),
1003 PROGRESS_UPDATE) 1003 PROGRESS_UPDATE)
1004 1004
1005 G.host.registerProgressCbs( 1005 G.host.register_progress_cbs(
1006 progress_id, 1006 progress_id,
1007 callback=partial(self._attachmentProgressCb, item), 1007 callback=partial(self._attachment_progress_cb, item),
1008 errback=partial(self._attachmentProgressEb, item) 1008 errback=partial(self._attachment_progress_eb, item)
1009 ) 1009 )
1010 1010
1011 1011
1012 G.host.messageSend( 1012 G.host.message_send(
1013 self.target, 1013 self.target,
1014 # TODO: handle language 1014 # TODO: handle language
1015 {'': input_widget.text}, 1015 {'': input_widget.text},
1016 # TODO: put this in QuickChat 1016 # TODO: put this in QuickChat
1017 mess_type= 1017 mess_type=
1019 extra=extra, 1019 extra=extra,
1020 profile_key=self.profile 1020 profile_key=self.profile
1021 ) 1021 )
1022 input_widget.text = '' 1022 input_widget.text = ''
1023 1023
1024 def _imageCheckCb(self, report_raw): 1024 def _image_check_cb(self, report_raw):
1025 report = data_format.deserialise(report_raw) 1025 report = data_format.deserialise(report_raw)
1026 if report['too_large']: 1026 if report['too_large']:
1027 self.attachments_to_send.show_resize=True 1027 self.attachments_to_send.show_resize=True
1028 self.attachments_to_send.reduce_checkbox.active=True 1028 self.attachments_to_send.reduce_checkbox.active=True
1029 1029
1030 def addAttachment(self, file_path, media_type=None): 1030 def add_attachment(self, file_path, media_type=None):
1031 file_path = Path(file_path) 1031 file_path = Path(file_path)
1032 if media_type is None: 1032 if media_type is None:
1033 media_type = mimetypes.guess_type(str(file_path), strict=False)[0] 1033 media_type = mimetypes.guess_type(str(file_path), strict=False)[0]
1034 if not self.attachments_to_send.show_resize and media_type is not None: 1034 if not self.attachments_to_send.show_resize and media_type is not None:
1035 # we check if the attachment is an image and if it's too large. 1035 # we check if the attachment is an image and if it's too large.
1036 # If too large, the reduce size check box will be displayed, and checked by 1036 # If too large, the reduce size check box will be displayed, and checked by
1037 # default. 1037 # default.
1038 main_type = media_type.split('/')[0] 1038 main_type = media_type.split('/')[0]
1039 if main_type == "image": 1039 if main_type == "image":
1040 G.host.bridge.imageCheck( 1040 G.host.bridge.image_check(
1041 str(file_path), 1041 str(file_path),
1042 callback=self._imageCheckCb, 1042 callback=self._image_check_cb,
1043 errback=partial( 1043 errback=partial(
1044 G.host.errback, 1044 G.host.errback,
1045 title=_("Can't check image size"), 1045 title=_("Can't check image size"),
1046 message=_("Can't check image at {path}: {{msg}}").format( 1046 message=_("Can't check image at {path}: {{msg}}").format(
1047 path=file_path), 1047 path=file_path),
1058 1058
1059 self.attachments_to_send.attachments.add_widget( 1059 self.attachments_to_send.attachments.add_widget(
1060 AttachmentToSendItem(data=data) 1060 AttachmentToSendItem(data=data)
1061 ) 1061 )
1062 1062
1063 def transferFile(self, file_path, transfer_type=C.TRANSFER_UPLOAD, cleaning_cb=None): 1063 def transfer_file(self, file_path, transfer_type=C.TRANSFER_UPLOAD, cleaning_cb=None):
1064 # FIXME: cleaning_cb is not managed 1064 # FIXME: cleaning_cb is not managed
1065 if transfer_type == C.TRANSFER_UPLOAD: 1065 if transfer_type == C.TRANSFER_UPLOAD:
1066 self.addAttachment(file_path) 1066 self.add_attachment(file_path)
1067 elif transfer_type == C.TRANSFER_SEND: 1067 elif transfer_type == C.TRANSFER_SEND:
1068 if self.type == C.CHAT_GROUP: 1068 if self.type == C.CHAT_GROUP:
1069 log.warning("P2P transfer is not possible for group chat") 1069 log.warning("P2P transfer is not possible for group chat")
1070 # TODO: show an error dialog to user, or better hide the send button for 1070 # TODO: show an error dialog to user, or better hide the send button for
1071 # MUC 1071 # MUC
1072 else: 1072 else:
1073 jid_ = self.target 1073 jid_ = self.target
1074 if not jid_.resource: 1074 if not jid_.resource:
1075 jid_ = G.host.contact_lists[self.profile].getFullJid(jid_) 1075 jid_ = G.host.contact_lists[self.profile].get_full_jid(jid_)
1076 G.host.bridge.fileSend(str(jid_), str(file_path), "", "", "", 1076 G.host.bridge.file_send(str(jid_), str(file_path), "", "", "",
1077 profile=self.profile) 1077 profile=self.profile)
1078 # TODO: notification of sending/failing 1078 # TODO: notification of sending/failing
1079 else: 1079 else:
1080 raise log.error("transfer of type {} are not handled".format(transfer_type)) 1080 raise log.error("transfer of type {} are not handled".format(transfer_type))
1081 1081
1082 def messageEncryptionStarted(self, plugin_data): 1082 def message_encryption_started(self, plugin_data):
1083 quick_chat.QuickChat.messageEncryptionStarted(self, plugin_data) 1083 quick_chat.QuickChat.message_encryption_started(self, plugin_data)
1084 self.encryption_btn.symbol = SYMBOL_ENCRYPTED 1084 self.encryption_btn.symbol = SYMBOL_ENCRYPTED
1085 self.encryption_btn.color = COLOR_ENCRYPTED 1085 self.encryption_btn.color = COLOR_ENCRYPTED
1086 self.encryption_btn.selectAlgo(plugin_data['name']) 1086 self.encryption_btn.select_algo(plugin_data['name'])
1087 1087
1088 def messageEncryptionStopped(self, plugin_data): 1088 def message_encryption_stopped(self, plugin_data):
1089 quick_chat.QuickChat.messageEncryptionStopped(self, plugin_data) 1089 quick_chat.QuickChat.message_encryption_stopped(self, plugin_data)
1090 self.encryption_btn.symbol = SYMBOL_UNENCRYPTED 1090 self.encryption_btn.symbol = SYMBOL_UNENCRYPTED
1091 self.encryption_btn.color = COLOR_UNENCRYPTED 1091 self.encryption_btn.color = COLOR_UNENCRYPTED
1092 self.encryption_btn.selectAlgo(None) 1092 self.encryption_btn.select_algo(None)
1093 1093
1094 def _mucJoinCb(self, joined_data): 1094 def _muc_join_cb(self, joined_data):
1095 joined, room_jid_s, occupants, user_nick, subject, statuses, profile = joined_data 1095 joined, room_jid_s, occupants, user_nick, subject, statuses, profile = joined_data
1096 self.host.mucRoomJoinedHandler(*joined_data[1:]) 1096 self.host.muc_room_joined_handler(*joined_data[1:])
1097 jid_ = jid.JID(room_jid_s) 1097 jid_ = jid.JID(room_jid_s)
1098 self.changeWidget(jid_) 1098 self.change_widget(jid_)
1099 1099
1100 def _mucJoinEb(self, failure): 1100 def _muc_join_eb(self, failure):
1101 log.warning("Can't join room: {}".format(failure)) 1101 log.warning("Can't join room: {}".format(failure))
1102 1102
1103 def onOTRState(self, state, dest_jid, profile): 1103 def on_otr_state(self, state, dest_jid, profile):
1104 assert profile in self.profiles 1104 assert profile in self.profiles
1105 if state in OTR_STATE_ENCRYPTION: 1105 if state in OTR_STATE_ENCRYPTION:
1106 self.otr_state_encryption = state 1106 self.otr_state_encryption = state
1107 elif state in OTR_STATE_TRUST: 1107 elif state in OTR_STATE_TRUST:
1108 self.otr_state_trust = state 1108 self.otr_state_trust = state
1109 else: 1109 else:
1110 log.error(_("Unknown OTR state received: {}".format(state))) 1110 log.error(_("Unknown OTR state received: {}".format(state)))
1111 return 1111 return
1112 self.encryption_btn.symbol = self.encryption_btn.getSymbol() 1112 self.encryption_btn.symbol = self.encryption_btn.get_symbol()
1113 self.encryption_btn.color = self.encryption_btn.getColor() 1113 self.encryption_btn.color = self.encryption_btn.get_color()
1114 1114
1115 def onVisible(self): 1115 def on_visible(self):
1116 if not self.sync: 1116 if not self.sync:
1117 self.resync() 1117 self.resync()
1118 1118
1119 def onSelected(self): 1119 def on_selected(self):
1120 G.host.clearNotifs(self.target, profile=self.profile) 1120 G.host.clear_notifs(self.target, profile=self.profile)
1121 1121
1122 def onDelete(self, **kwargs): 1122 def on_delete(self, **kwargs):
1123 if kwargs.get('explicit_close', False): 1123 if kwargs.get('explicit_close', False):
1124 wrapper = self.whwrapper 1124 wrapper = self.whwrapper
1125 if wrapper is not None: 1125 if wrapper is not None:
1126 if len(wrapper.carousel.slides) == 1: 1126 if len(wrapper.carousel.slides) == 1:
1127 # if we delete the last opened chat, we need to show the selector 1127 # if we delete the last opened chat, we need to show the selector
1130 screen_manager.current = 'chat_selector' 1130 screen_manager.current = 'chat_selector'
1131 wrapper.carousel.remove_widget(self) 1131 wrapper.carousel.remove_widget(self)
1132 return True 1132 return True
1133 # we always keep one widget, so it's available when swiping 1133 # we always keep one widget, so it's available when swiping
1134 # TODO: delete all widgets when chat is closed 1134 # TODO: delete all widgets when chat is closed
1135 nb_instances = sum(1 for _ in self.host.widgets.getWidgetInstances(self)) 1135 nb_instances = sum(1 for _ in self.host.widgets.get_widget_instances(self))
1136 # we want to keep at least one instance of Chat by WHWrapper 1136 # we want to keep at least one instance of Chat by WHWrapper
1137 nb_to_keep = len(G.host.widgets_handler.children) 1137 nb_to_keep = len(G.host.widgets_handler.children)
1138 if nb_instances <= nb_to_keep: 1138 if nb_instances <= nb_to_keep:
1139 return False 1139 return False
1140 1140
1141 def _history_unlock(self, __): 1141 def _history_unlock(self, __):
1142 self._history_prepend_lock = False 1142 self._history_prepend_lock = False
1143 log.debug("history prepend unlocked") 1143 log.debug("history prepend unlocked")
1144 # we call manually onScroll, to check if we are still in the scrolling zone 1144 # we call manually on_scroll, to check if we are still in the scrolling zone
1145 self.onScroll(self.history_scroll, self.history_scroll.scroll_y) 1145 self.on_scroll(self.history_scroll, self.history_scroll.scroll_y)
1146 1146
1147 def _history_scroll_adjust(self, __, scroll_start_height): 1147 def _history_scroll_adjust(self, __, scroll_start_height):
1148 # history scroll position must correspond to where it was before new messages 1148 # history scroll position must correspond to where it was before new messages
1149 # have been appended 1149 # have been appended
1150 self.history_scroll.scroll_y = ( 1150 self.history_scroll.scroll_y = (
1153 1153
1154 # we want a small delay before unlocking, to avoid re-fetching history 1154 # we want a small delay before unlocking, to avoid re-fetching history
1155 # again 1155 # again
1156 Clock.schedule_once(self._history_unlock, 1.5) 1156 Clock.schedule_once(self._history_unlock, 1.5)
1157 1157
1158 def _backHistoryGetCb_post(self, __, history, scroll_start_height): 1158 def _back_history_get_cb_post(self, __, history, scroll_start_height):
1159 if len(history) == 0: 1159 if len(history) == 0:
1160 # we don't unlock self._history_prepend_lock if there is no history, as there 1160 # we don't unlock self._history_prepend_lock if there is no history, as there
1161 # is no sense to try to retrieve more in this case. 1161 # is no sense to try to retrieve more in this case.
1162 log.debug(f"we've reached top of history for {self.target.bare} chat") 1162 log.debug(f"we've reached top of history for {self.target.bare} chat")
1163 else: 1163 else:
1168 self._history_scroll_adjust, 1168 self._history_scroll_adjust,
1169 scroll_start_height=scroll_start_height)) 1169 scroll_start_height=scroll_start_height))
1170 log.debug( 1170 log.debug(
1171 f"{len(history)} messages prepended to history (last: {history[0][0]})") 1171 f"{len(history)} messages prepended to history (last: {history[0][0]})")
1172 1172
1173 def _backHistoryGetCb(self, history): 1173 def _back_history_get_cb(self, history):
1174 # TODO: factorise with QuickChat._historyGetCb 1174 # TODO: factorise with QuickChat._history_get_cb
1175 scroll_start_height = self.messages_widget.height * self.history_scroll.scroll_y 1175 scroll_start_height = self.messages_widget.height * self.history_scroll.scroll_y
1176 for data in reversed(history): 1176 for data in reversed(history):
1177 uid, timestamp, from_jid, to_jid, message, subject, type_, extra_s = data 1177 uid, timestamp, from_jid, to_jid, message, subject, type_, extra_s = data
1178 from_jid = jid.JID(from_jid) 1178 from_jid = jid.JID(from_jid)
1179 to_jid = jid.JID(to_jid) 1179 to_jid = jid.JID(to_jid)
1190 type_, 1190 type_,
1191 extra, 1191 extra,
1192 self.profile, 1192 self.profile,
1193 ) 1193 )
1194 self.messages.move_to_end(uid, last=False) 1194 self.messages.move_to_end(uid, last=False)
1195 self.prependMessage(message) 1195 self.prepend_message(message)
1196 Clock.schedule_once(partial( 1196 Clock.schedule_once(partial(
1197 self._backHistoryGetCb_post, 1197 self._back_history_get_cb_post,
1198 history=history, 1198 history=history,
1199 scroll_start_height=scroll_start_height)) 1199 scroll_start_height=scroll_start_height))
1200 1200
1201 def _backHistoryGetEb(self, failure_): 1201 def _back_history_get_eb(self, failure_):
1202 G.host.addNote( 1202 G.host.add_note(
1203 _("Problem while getting back history"), 1203 _("Problem while getting back history"),
1204 _("Can't back history for {target}: {problem}").format( 1204 _("Can't back history for {target}: {problem}").format(
1205 target=self.target, problem=failure_), 1205 target=self.target, problem=failure_),
1206 C.XMLUI_DATA_LVL_ERROR) 1206 C.XMLUI_DATA_LVL_ERROR)
1207 # we don't unlock self._history_prepend_lock on purpose, no need 1207 # we don't unlock self._history_prepend_lock on purpose, no need
1208 # to try to get more history if something is wrong 1208 # to try to get more history if something is wrong
1209 1209
1210 def onScroll(self, scroll_view, scroll_y): 1210 def on_scroll(self, scroll_view, scroll_y):
1211 if self._history_prepend_lock: 1211 if self._history_prepend_lock:
1212 return 1212 return
1213 if (1-scroll_y) * self.messages_widget.height < INFINITE_SCROLL_LIMIT: 1213 if (1-scroll_y) * self.messages_widget.height < INFINITE_SCROLL_LIMIT:
1214 self._history_prepend_lock = True 1214 self._history_prepend_lock = True
1215 log.debug(f"Retrieving back history for {self} [{self.history_count}]") 1215 log.debug(f"Retrieving back history for {self} [{self.history_count}]")
1216 self.history_count += 1 1216 self.history_count += 1
1217 first_uid = next(iter(self.messages.keys())) 1217 first_uid = next(iter(self.messages.keys()))
1218 filters = self.history_filters.copy() 1218 filters = self.history_filters.copy()
1219 filters['before_uid'] = first_uid 1219 filters['before_uid'] = first_uid
1220 self.host.bridge.historyGet( 1220 self.host.bridge.history_get(
1221 str(self.host.profiles[self.profile].whoami.bare), 1221 str(self.host.profiles[self.profile].whoami.bare),
1222 str(self.target), 1222 str(self.target),
1223 30, 1223 30,
1224 True, 1224 True,
1225 {k: str(v) for k,v in filters.items()}, 1225 {k: str(v) for k,v in filters.items()},
1226 self.profile, 1226 self.profile,
1227 callback=self._backHistoryGetCb, 1227 callback=self._back_history_get_cb,
1228 errback=self._backHistoryGetEb, 1228 errback=self._back_history_get_eb,
1229 ) 1229 )
1230 1230
1231 1231
1232 class ChatSelector(cagou_widget.CagouWidget, FilterBehavior): 1232 class ChatSelector(cagou_widget.CagouWidget, FilterBehavior):
1233 jid_selector = properties.ObjectProperty() 1233 jid_selector = properties.ObjectProperty()
1235 plugin_info_class = Chat 1235 plugin_info_class = Chat
1236 use_header_input = True 1236 use_header_input = True
1237 1237
1238 def on_select(self, contact_button): 1238 def on_select(self, contact_button):
1239 contact_jid = jid.JID(contact_button.jid) 1239 contact_jid = jid.JID(contact_button.jid)
1240 plugin_info = G.host.getPluginInfo(main=Chat) 1240 plugin_info = G.host.get_plugin_info(main=Chat)
1241 factory = plugin_info['factory'] 1241 factory = plugin_info['factory']
1242 self.screen_manager.transition.direction = 'up' 1242 self.screen_manager.transition.direction = 'up'
1243 carousel = self.whwrapper.carousel 1243 carousel = self.whwrapper.carousel
1244 current_slides = {w.target: w for w in carousel.slides} 1244 current_slides = {w.target: w for w in carousel.slides}
1245 if contact_jid in current_slides: 1245 if contact_jid in current_slides:
1246 slide = current_slides[contact_jid] 1246 slide = current_slides[contact_jid]
1247 idx = carousel.slides.index(slide) 1247 idx = carousel.slides.index(slide)
1248 carousel.index = idx 1248 carousel.index = idx
1249 self.screen_manager.current = '' 1249 self.screen_manager.current = ''
1250 else: 1250 else:
1251 G.host.switchWidget( 1251 G.host.switch_widget(
1252 self, factory(plugin_info, contact_jid, profiles=[self.profile])) 1252 self, factory(plugin_info, contact_jid, profiles=[self.profile]))
1253 1253
1254 1254
1255 def onHeaderInput(self): 1255 def on_header_wid_input(self):
1256 text = self.header_input.text.strip() 1256 text = self.header_input.text.strip()
1257 try: 1257 try:
1258 if text.count('@') != 1 or text.count(' '): 1258 if text.count('@') != 1 or text.count(' '):
1259 raise ValueError 1259 raise ValueError
1260 jid_ = jid.JID(text) 1260 jid_ = jid.JID(text)
1261 except ValueError: 1261 except ValueError:
1262 log.info("entered text is not a jid") 1262 log.info("entered text is not a jid")
1263 return 1263 return
1264 G.host.doAction("chat", jid_, [self.profile]) 1264 G.host.do_action("chat", jid_, [self.profile])
1265 1265
1266 def onHeaderInputComplete(self, wid, text, **kwargs): 1266 def on_header_wid_input_complete(self, wid, text, **kwargs):
1267 """we filter items when text is entered in input box""" 1267 """we filter items when text is entered in input box"""
1268 for layout in self.jid_selector.items_layouts: 1268 for layout in self.jid_selector.items_layouts:
1269 self.do_filter( 1269 self.do_filter(
1270 layout, 1270 layout,
1271 text, 1271 text,