Commit 921594c1 authored by Philipp Hörist's avatar Philipp Hörist

Rework Groupchat Roster

- Add own module for roster code
- Use avatars with status integrated
- Favor performance over features
parent 7f5a6d0a
Pipeline #4267 passed with stages
in 2 minutes and 29 seconds
......@@ -1320,7 +1320,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if contact:
app.interface.roster.draw_contact(room_jid, self.account)
if groupchat_control:
groupchat_control.draw_contact(nick)
groupchat_control.roster.draw_contact(nick)
if groupchat_control.parent_win:
groupchat_control.parent_win.redraw_tab(groupchat_control)
else:
......
......@@ -282,17 +282,17 @@ class StandardGroupChatCommands(CommandContainer):
def chat(self, nick):
nicks = app.contacts.get_nick_list(self.account, self.room_jid)
if nick in nicks:
self.on_send_pm(nick=nick)
self.send_pm(nick)
else:
raise CommandError(_("Nickname not found"))
@command('msg', raw=True)
@doc(_("Open a private chat window with a specified participant and send "
"him a message"))
def message(self, nick, a_message):
def message(self, nick, message):
nicks = app.contacts.get_nick_list(self.account, self.room_jid)
if nick in nicks:
self.on_send_pm(nick=nick, msg=a_message)
self.send_pm(nick, message)
else:
raise CommandError(_("Nickname not found"))
......
......@@ -128,7 +128,6 @@ class Config:
'custombrowser': [opt_str, DEFAULT_BROWSER],
'custommailapp': [opt_str, DEFAULT_MAILAPP],
'custom_file_manager': [opt_str, DEFAULT_FILE_MANAGER],
'gc-hpaned-position': [opt_int, 430],
'gc_refer_to_nick_char': [opt_str, ',', _('Character to add after nickname when using nickname completion (tab) in group chat.')],
'gc_proposed_nick_char': [opt_str, '_', _('Character to propose to add after desired nickname when nickname is already used in group chat.')],
'msgwin-max-state': [opt_bool, False],
......@@ -289,6 +288,7 @@ class Config:
'muclumbus_api_pref': [opt_str, 'http', _('API Preferences. Possible values: \'http\', \'iq\'')],
'auto_copy': [opt_bool, True, _('Selecting text will copy it to the clipboard')],
'command_system_execute': [opt_bool, False, _('If enabled, Gajim will execute commands (/show, /sh, /execute, /exec).')],
'groupchat_roster_width': [opt_int, 210, _('Width of group chat roster in pixel')],
}, {}) # type: Tuple[Dict[str, List[Any]], Dict[Any, Any]]
__options_per_key = {
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkAccelGroup" id="accelgroup1"/>
<object class="GtkMenu" id="gc_occupants_menu">
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="send_private_message_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Send Private Message</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="send_file_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="label" translatable="yes">Send _File</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="group_chat_actions_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Participant Actions</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="group_chat_actions_menuitem_menu">
<property name="can_focus">False</property>
<child>
<object class="GtkCheckMenuItem" id="voice_checkmenuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Voice</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="moderator_checkmenuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Mo_derator</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separator5">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="member_checkmenuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Member</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="admin_checkmenuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Admin</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkCheckMenuItem" id="owner_checkmenuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Owner</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separator4">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="kick_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Kick</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="ban_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Ban</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="invite_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">In_vite to</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separator6">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="add_to_roster_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Add to Contact List</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="execute_command_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Execute command</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="block_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Block</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="unblock_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Unblock</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separator1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="information_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">_Information</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="history_menuitem">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_History</property>
<property name="use_underline">True</property>
</object>
</child>
</object>
</interface>
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkTreeStore" id="participant_store">
<columns>
<!-- column-name Avatar -->
<column type="CairoGObjectSurface"/>
<!-- column-name Text -->
<column type="gchararray"/>
<!-- column-name Event -->
<column type="gboolean"/>
<!-- column-name IsContact -->
<column type="gboolean"/>
<!-- column-name GroupOrNickname -->
<column type="gchararray"/>
</columns>
</object>
<object class="GtkTreeView" id="roster_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">participant_store</property>
<property name="headers_visible">False</property>
<property name="expander_column">expander</property>
<property name="search_column">1</property>
<signal name="button-press-event" handler="_on_roster_button_press_event" swapped="no"/>
<signal name="focus-out-event" handler="_on_focus_out" swapped="no"/>
<signal name="row-activated" handler="_on_roster_row_activated" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="contact_column">
<property name="sizing">fixed</property>
<property name="fixed_width">210</property>
<property name="title">avatar</property>
<property name="expand">True</property>
<child>
<object class="GtkCellRendererPixbuf" id="avatar_renderer">
<property name="width">40</property>
<property name="xalign">0</property>
</object>
<attributes>
<attribute name="visible">3</attribute>
<attribute name="surface">0</attribute>
</attributes>
</child>
<child>
<object class="GtkCellRendererText" id="text_renderer">
<property name="ellipsize">end</property>
</object>
<attributes>
<attribute name="markup">1</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="event_column">
<property name="sizing">fixed</property>
<property name="fixed_width">35</property>
<property name="title">event</property>
<child>
<object class="GtkCellRendererPixbuf" id="icon">
<property name="xalign">0</property>
<property name="icon_name">gajim-event</property>
</object>
<attributes>
<attribute name="visible">2</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="expander">
<property name="visible">False</property>
<property name="sizing">fixed</property>
<property name="title">expander</property>
</object>
</child>
</object>
</interface>
......@@ -141,6 +141,10 @@ list.settings > row > box {
.start-chat-row:not(.activatable) label { color: @insensitive_fg_color }
.start-chat-row:focus { outline: none; }
/* GroupChatRoster */
.groupchat-roster { border-left: 1px solid;
border-color: @borders; }
.groupchat-roster treeview { padding-left: 4px; }
/* GroupchatConfig */
#GroupchatConfig > box > buttonbox { margin: 0px 12px 12px 12px; }
......
This diff is collapsed.
This diff is collapsed.
......@@ -22,6 +22,7 @@ from gajim.common import app
from gajim.common import helpers
from gajim.common import config as c_config
from gajim.common import idle
from gajim.common.nec import NetworkEvent
from gajim.common.i18n import _
from gajim.common.i18n import ngettext
......@@ -517,15 +518,12 @@ class Preferences(Gtk.ApplicationWindow):
def on_show_avatars_in_roster_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'show_avatars_in_roster')
app.interface.roster.setup_and_draw_roster()
# Redraw groupchats (in an ugly way)
for ctrl in self._get_all_muc_controls():
ctrl.draw_roster()
def on_show_status_msgs_in_roster_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'show_status_msgs_in_roster')
app.interface.roster.setup_and_draw_roster()
for ctrl in self._get_all_muc_controls():
ctrl.update_ui()
ctrl.roster.draw_contacts()
def on_show_pep_in_roster_checkbutton_toggled(self, widget):
self.on_checkbutton_toggled(widget, 'show_mood_in_roster')
......@@ -542,7 +540,7 @@ class Preferences(Gtk.ApplicationWindow):
self.on_checkbutton_toggled(widget, 'sort_by_show_in_muc')
# Redraw groupchats
for ctrl in self._get_all_muc_controls():
ctrl.draw_roster()
ctrl.roster.invalidate_sort()
### Chat tab ###
def on_auto_copy_toggled(self, widget):
......@@ -860,6 +858,7 @@ class Preferences(Gtk.ApplicationWindow):
theme = combobox.get_active_id()
app.config.set('roster_theme', theme)
app.css_config.change_theme(theme)
app.nec.push_incoming_event(NetworkEvent('theme-update'))
# Begin repainting themed widgets throughout
app.interface.roster.repaint_themed_widgets()
......
......@@ -467,22 +467,6 @@ def get_metacontact_surface(icon_name, expanded, scale):
return state_surface
def get_affiliation_surface(icon_name, affiliation, scale):
surface = _icon_theme.load_surface(
icon_name, 16, scale, None, 0)
ctx = cairo.Context(surface)
ctx.rectangle(16 - 4, 16 - 4, 4, 4)
if affiliation == 'owner':
ctx.set_source_rgb(204/255, 0, 0)
elif affiliation == 'admin':
ctx.set_source_rgb(255/255, 140/255, 0)
elif affiliation == 'member':
ctx.set_source_rgb(0, 255/255, 0)
ctx.fill()
return surface
def get_show_in_roster(event, session=None):
"""
Return True if this event must be shown in roster, else False
......
......@@ -1923,7 +1923,7 @@ class Interface:
def update_avatar(self, account=None, jid=None,
contact=None, room_avatar=False):
self.avatar_storage.invalidate_cache(jid or contact.jid)
self.avatar_storage.invalidate_cache(jid or contact.get_full_jid())
if room_avatar:
app.nec.push_incoming_event(
NetworkEvent('update-room-avatar', account=account, jid=jid))
......@@ -1931,8 +1931,9 @@ class Interface:
app.nec.push_incoming_event(
NetworkEvent('update-roster-avatar', account=account, jid=jid))
else:
app.nec.push_incoming_event(
NetworkEvent('update-gc-avatar', contact=contact))
app.nec.push_incoming_event(NetworkEvent('update-gc-avatar',
contact=contact,
room_jid=contact.room_jid))
def save_avatar(self, data):
return self.avatar_storage.save_avatar(data)
......
......@@ -22,6 +22,8 @@ from gajim import gtkgui_helpers
from gajim import message_control
from gajim.common import app
from gajim.common import helpers
from gajim.common.helpers import is_affiliation_change_allowed
from gajim.common.helpers import is_role_change_allowed
from gajim.common.i18n import ngettext
from gajim.common.i18n import _
from gajim.common.const import URIType
......@@ -604,6 +606,13 @@ def get_groupchat_menu(control_id, account, jid):
menuitem.set_action_and_target_value(action_name,
variant_dict)
menu.append_item(menuitem)
elif action_name == 'win.execute-command-':
action_name = action_name + control_id
menuitem = Gio.MenuItem.new(label, action_name)
menuitem.set_action_and_target_value(action_name,
GLib.Variant('s', ''))
menu.append_item(menuitem)
else:
menu.append(label, action_name + control_id)
else:
......@@ -834,6 +843,127 @@ def get_conv_context_menu(account, uri):
return menu
def get_groupchat_roster_menu(account, control_id, self_contact, contact):
menu = Gtk.Menu()
item = Gtk.MenuItem(label=_('Information'))
action = 'win.contact-information-%s::%s' % (control_id, contact.name)
item.set_detailed_action_name(action)
menu.append(item)
item = Gtk.MenuItem(label=_('Add to Contact List'))
action = 'app.{account}-add-contact(["{account}", "{jid}"])'.format(
account=account, jid=contact.jid or '')
if contact.jid is None:
item.set_sensitive(False)
else:
item.set_detailed_action_name(action)
menu.append(item)
item = Gtk.MenuItem(label=_('Invite'))
if contact.jid is not None:
build_invite_submenu(item,
((contact, account),),
show_bookmarked=True)
else:
item.set_sensitive(False)
menu.append(item)
item = Gtk.MenuItem(label=_('Execute command'))
action = 'win.execute-command-%s::%s' % (control_id, contact.name)
item.set_detailed_action_name(action)
menu.append(item)
menu.append(Gtk.SeparatorMenuItem())
if helpers.jid_is_blocked(account, contact.get_full_jid()):
action = 'win.unblock-%s::%s' % (control_id, contact.name)
label = _('Unblock')
else:
action = 'win.block-%s::%s' % (control_id, contact.name)
label = _('Block')
item = Gtk.MenuItem(label=label)
item.set_detailed_action_name(action)
menu.append(item)
item = Gtk.MenuItem(label=_('Kick'))
action = 'win.kick-%s::%s' % (control_id, contact.name)
if is_role_change_allowed(self_contact, contact):
item.set_detailed_action_name(action)
else:
item.set_sensitive(False)
menu.append(item)
item = Gtk.MenuItem(label=_('Ban'))
action = 'win.ban-%s::%s' % (control_id, contact.jid or '')
if is_affiliation_change_allowed(self_contact, contact):
item.set_detailed_action_name(action)
else:
item.set_sensitive(False)
menu.append(item)
menu.append(Gtk.SeparatorMenuItem())
item = Gtk.MenuItem(label=_('Make Owner'))
action = 'win.change-affiliation-%s(["%s", "owner"])' % (control_id,
contact.jid)
if (is_affiliation_change_allowed(self_contact, contact) and
not contact.affiliation.is_owner):
item.set_detailed_action_name(action)
else:
item.set_sensitive(False)
menu.append(item)
item = Gtk.MenuItem(label=_('Make Admin'))
action = 'win.change-affiliation-%s(["%s", "admin"])' % (control_id,
contact.jid)
if (is_affiliation_change_allowed(self_contact, contact) and
not contact.affiliation.is_admin):
item.set_detailed_action_name(action)
else:
item.set_sensitive(False)
menu.append(item)
if contact.affiliation.is_none:
label = _('Make Member')
affiliation = 'member'
else:
label = _('Revoke Member')
affiliation = 'none'
item = Gtk.MenuItem(label=label)
action = 'win.change-affiliation-%s(["%s", "%s"])' % (control_id,
contact.jid,
affiliation)
if (is_affiliation_change_allowed(self_contact, contact) and
contact.affiliation.value != affiliation):
item.set_detailed_action_name(action)
else:
item.set_sensitive(False)
menu.append(item)
if contact.role.is_visitor:
label = _('Grant Voice')
role = 'participant'
else:
label = _('Revoke Voice')
role = 'visitor'
item = Gtk.MenuItem(label=label)
action = 'win.change-role-%s(["%s", "%s"])' % (control_id,
contact.name,
role)
if is_role_change_allowed(self_contact, contact):
item.set_detailed_action_name(action)
else:
item.set_sensitive(False)
menu.append(item)
menu.show_all()
return menu
class SearchMenu(Gtk.Menu):
def __init__(self, treeview):
Gtk.Menu.__init__(self)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment