...
 
Commits (40)
......@@ -382,6 +382,7 @@
<property name="spacing">5</property>
<child>
<object class="GtkEventBox" id="banner_eventbox">
<property name="name">ChatControl-BannerEventBox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
......@@ -411,6 +412,7 @@
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="banner_name_label">
<property name="name">ChatControl-BannerNameLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;span weight="heavy" size="large"&gt;Contact name&lt;/span&gt;</property>
......@@ -425,6 +427,7 @@
</child>
<child>
<object class="GtkLabel" id="banner_label">
<property name="name">ChatControl-BannerLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">label</property>
......@@ -603,6 +606,7 @@
<property name="spacing">3</property>
<child>
<object class="GtkButton" id="authentication_button">
<property name="name">ChatControl-AuthenticationButton</property>
<property name="can_focus">True</property>
<property name="focus_on_click">False</property>
<property name="receives_default">True</property>
......
......@@ -20,6 +20,7 @@
<property name="spacing">5</property>
<child>
<object class="GtkEventBox" id="banner_eventbox">
<property name="name">GroupChatControl-BannerEventBox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
......@@ -48,6 +49,7 @@
<property name="orientation">vertical</property>
<child>
<object class="GtkLabel" id="banner_name_label">
<property name="name">GroupChatControl-BannerNameLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">&lt;span weight="heavy" size="large"&gt;room jid&lt;/span&gt;</property>
......@@ -62,6 +64,7 @@
</child>
<child>
<object class="GtkLabel" id="banner_label">
<property name="name">GroupChatControl-BannerLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label">label</property>
......
......@@ -24,6 +24,7 @@
<property name="spacing">6</property>
<child>
<object class="GtkEventBox" id="banner_agent_eventbox">
<property name="name">Discovery-BannerEventBox</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
......@@ -33,6 +34,7 @@
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="banner_agent_label">
<property name="name">Discovery-BannerLabel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="ypad">6</property>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<!-- Generated with glade 3.20.0 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkWindow" id="vcard_information_window">
......@@ -914,6 +914,9 @@
<property name="halign">start</property>
<property name="relief">none</property>
<property name="xalign">0</property>
<style>
<class name="VCard-GtkLinkButton"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
......@@ -929,6 +932,9 @@
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="xalign">0</property>
<style>
<class name="VCard-GtkLinkButton"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
......@@ -1327,6 +1333,9 @@
<property name="halign">start</property>
<property name="relief">none</property>
<property name="xalign">0</property>
<style>
<class name="VCard-GtkLinkButton"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
......
/* Gajim Application CSS File */
/* ChatControl */
#ChatControl-AuthenticationButton { padding-top: 0px; padding-bottom: 0px}
/* VCardWindow */
.VCard-GtkLinkButton { padding-left: 5px; border-left: none; }
\ No newline at end of file
......@@ -39,6 +39,7 @@ import gui_menu_builder
import message_control
import dialogs
from common import logger
from common import gajim
from common import helpers
from common import exceptions
......@@ -157,15 +158,6 @@ class ChatControl(ChatControlBase):
# Add lock image to show chat encryption
self.lock_image = self.xml.get_object('lock_image')
# Remove padding from authentication button or else it will
# be higher than the message box
style_provider = Gtk.CssProvider()
css = 'GtkButton { padding-top: 0px; padding-bottom: 0px}'
style_provider.load_from_data(css.encode())
context = self.authentication_button.get_style_context()
context.add_provider(style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
# Convert to GC icon
img = self.xml.get_object('convert_to_gc_button_image')
img.set_from_pixbuf(gtkgui_helpers.load_icon(
......@@ -673,11 +665,11 @@ class ChatControl(ChatControlBase):
"""
Just moved the mouse so show the cursor
"""
cursor = Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)
cursor = gtkgui_helpers.get_cursor('LEFT_PTR')
self.parent_win.window.get_window().set_cursor(cursor)
def on_location_eventbox_enter_notify_event(self, widget, event):
cursor = Gdk.Cursor.new(Gdk.CursorType.HAND2)
cursor = gtkgui_helpers.get_cursor('HAND2')
self.parent_win.window.get_window().set_cursor(cursor)
def _on_window_motion_notify(self, widget, event):
......@@ -1024,8 +1016,8 @@ class ChatControl(ChatControlBase):
displaymarking = None
if self.correcting:
self.correcting = False
self.msg_textview.override_background_color(
Gtk.StateType.NORMAL, self.old_message_tv_color)
gtkgui_helpers.remove_css_class(
self.msg_textview, 'msgcorrectingcolor')
self.print_conversation(message, self.contact.jid,
encrypted=encrypted, xep0184_id=xep0184_id, xhtml=xhtml,
......@@ -1226,7 +1218,7 @@ class ChatControl(ChatControlBase):
else:
self.old_msg_kind = kind
def get_tab_label(self, chatstate):
def get_tab_label(self):
unread = ''
if self.resource:
jid = self.contact.get_full_jid()
......@@ -1239,48 +1231,13 @@ class ChatControl(ChatControlBase):
elif num_unread > 1:
unread = '[' + str(num_unread) + ']'
# Draw tab label using chatstate
theme = gajim.config.get('roster_theme')
color_s = None
if not chatstate:
chatstate = self.contact.chatstate
if chatstate is not None:
if chatstate == 'composing':
color_s = gajim.config.get_per('themes', theme,
'state_composing_color')
elif chatstate == 'inactive':
color_s = gajim.config.get_per('themes', theme,
'state_inactive_color')
elif chatstate == 'gone':
color_s = gajim.config.get_per('themes', theme,
'state_gone_color')
elif chatstate == 'paused':
color_s = gajim.config.get_per('themes', theme,
'state_paused_color')
context = self.parent_win.notebook.get_style_context()
if color_s:
# We set the color for when it's the current tab or not
color = Gdk.RGBA()
ok = Gdk.RGBA.parse(color, color_s)
if not ok:
del color
color = context.get_color(Gtk.StateFlags.ACTIVE)
# In inactive tab color to be lighter against the darker inactive
# background
if chatstate in ('inactive', 'gone') and\
self.parent_win.get_active_control() != self:
color = self.lighten_color(color)
else: # active or not chatstate, get color from gtk
color = context.get_color(Gtk.StateFlags.ACTIVE)
name = self.contact.get_shown_name()
if self.resource:
name += '/' + self.resource
label_str = GLib.markup_escape_text(name)
if num_unread: # if unread, text in the label becomes bold
label_str = '<b>' + unread + label_str + '</b>'
return (label_str, color)
return label_str
def get_tab_image(self, count_unread=True):
if self.resource:
......@@ -1670,10 +1627,9 @@ class ChatControl(ChatControlBase):
rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
pending_how_many, timeout, self.account)
except exceptions.DatabaseMalformed:
import common.logger
dialogs.ErrorDialog(_('Database Error'),
_('The database file (%s) cannot be read. Try to repair it or '
'remove it (all history will be lost).') % common.logger.LOG_DB_PATH)
'remove it (all history will be lost).') % logger.LOG_DB_PATH)
rows = []
local_old_kind = None
self.conv_textview.just_cleared = True
......
......@@ -54,6 +54,13 @@ from common.connection_handlers_events import MessageOutgoingEvent
from command_system.implementation.middleware import ChatCommandProcessor
from command_system.implementation.middleware import CommandTools
# The members of these modules are not referenced directly anywhere in this
# module, but still they need to be kept around. Importing them automatically
# registers the contained CommandContainers with the command system, thereby
# populating the list of available commands.
import command_system.implementation.standard
import command_system.implementation.execute
try:
import gtkspell
HAS_GTK_SPELL = True
......@@ -158,7 +165,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
"""
Derived types MAY implement this
"""
self._paint_banner()
self.draw_banner()
def _update_banner_state_image(self):
......@@ -317,7 +323,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
id_ = widget.connect('changed',
self.on_conversation_vadjustment_changed)
self.handlers[id_] = widget
self.scroll_to_end_id = None
self.was_at_the_end = True
self.correcting = False
self.last_sent_msg = None
......@@ -344,8 +349,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.msg_textview.drag_dest_set(Gtk.DestDefaults.MOTION |
Gtk.DestDefaults.HIGHLIGHT, self.dnd_list, Gdk.DragAction.COPY)
self.update_font()
# Hook up send button
widget = self.xml.get_object('send_button')
id_ = widget.connect('clicked', self._on_send_button_clicked)
......@@ -371,7 +374,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if gajim.config.get('use_speller') and HAS_GTK_SPELL:
self.set_speller()
self.conv_textview.tv.show()
self._paint_banner()
# For XEP-0172
self.user_nick = None
......@@ -503,76 +505,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# send the message
self.send_message(message, xhtml=xhtml)
def _paint_banner(self):
"""
Repaint banner with theme color
"""
theme = gajim.config.get('roster_theme')
bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
# the backgrounds are colored by using an eventbox by
# setting the bg color of the eventbox and the fg of the name_label
banner_eventbox = self.xml.get_object('banner_eventbox')
banner_name_label = self.xml.get_object('banner_name_label')
self.disconnect_style_event(banner_name_label)
self.disconnect_style_event(self.banner_status_label)
if bgcolor:
color = Gdk.RGBA()
Gdk.RGBA.parse(color, bgcolor)
banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
color)
default_bg = False
else:
default_bg = True
if textcolor:
color = Gdk.RGBA()
Gdk.RGBA.parse(color, textcolor)
banner_name_label.override_color(Gtk.StateType.NORMAL,
color)
self.banner_status_label.override_color(
Gtk.StateType.NORMAL, color)
default_fg = False
else:
default_fg = True
if default_bg or default_fg:
self._on_style_set_event(banner_name_label, None, default_fg,
default_bg)
if self.banner_status_label.get_realized():
# Widget is realized
self._on_style_set_event(self.banner_status_label, None, default_fg,
default_bg)
def disconnect_style_event(self, widget):
# Try to find the event_id
for id_ in self.handlers.keys():
if self.handlers[id_] == widget:
widget.disconnect(id_)
del self.handlers[id_]
break
def connect_style_event(self, widget, set_fg=False, set_bg=False):
self.disconnect_style_event(widget)
id_ = widget.connect('style-set', self._on_style_set_event, set_fg,
set_bg)
self.handlers[id_] = widget
def _on_style_set_event(self, widget, style, *opts):
"""
Set style of widget from style class *.Frame.Eventbox
opts[0] == True -> set fg color
opts[1] == True -> set bg color
"""
banner_eventbox = self.xml.get_object('banner_eventbox')
self.disconnect_style_event(widget)
context = widget.get_style_context()
if opts[1]:
bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
banner_eventbox.override_background_color(Gtk.StateType.NORMAL, bg_color)
if opts[0]:
fg_color = context.get_color(Gtk.StateFlags.SELECTED)
widget.override_color(Gtk.StateType.NORMAL, fg_color)
self.connect_style_event(widget, opts[0], opts[1])
def _conv_textview_key_press_event(self, widget, event):
# translate any layout to latin_layout
valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
......@@ -968,11 +900,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
gtkgui_helpers.popup_emoticons_under_button(menu, widget,
self.parent_win)
def update_font(self):
font = Pango.FontDescription(gajim.config.get('conversation_font'))
self.conv_textview.tv.override_font(font)
self.msg_textview.override_font(font)
def update_tags(self):
self.conv_textview.update_tags()
......@@ -1056,27 +983,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# There were events to remove
self.redraw_after_event_removed(jid)
def bring_scroll_to_end(self, textview, diff_y=0):
"""
Scroll to the end of textview if end is not visible
"""
if self.scroll_to_end_id:
# a scroll is already planned
return
buffer_ = textview.get_buffer()
end_iter = buffer_.get_end_iter()
end_rect = textview.get_iter_location(end_iter)
visible_rect = textview.get_visible_rect()
# scroll only if expected end is not visible
if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
textview)
def scroll_to_end_iter(self, textview):
buffer_ = textview.get_buffer()
end_iter = buffer_.get_end_iter()
textview.scroll_to_iter(end_iter, 0, False, 1, 1)
self.scroll_to_end_id = None
def scroll_to_end_iter(self):
self.conv_textview.scroll_to_end_iter()
return False
def on_configure_event(self, msg_textview, event):
......@@ -1137,18 +1045,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# used to stay at the end of the textview when we shrink conversation
# textview.
if self.was_at_the_end:
if self.conv_textview.at_the_end():
# we are at the end
self.conv_textview.bring_scroll_to_end(-18)
else:
self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
self.scroll_to_end_iter()
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
- adjustment.get_page_size()) < 18
def on_conversation_vadjustment_value_changed(self, adjustment):
# stop automatic scroll when we manually scroll
if not self.conv_textview.auto_scrolling:
self.conv_textview.stop_scrolling()
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
- adjustment.get_page_size()) < 18
if self.resource:
......@@ -1226,20 +1127,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
msg_type == 'sent' and not self.correcting and (not \
history[pos - 1].startswith('/') or history[pos - 1].startswith('/me')):
self.correcting = True
context = self.msg_textview.get_style_context()
state = Gtk.StateFlags.NORMAL
self.old_message_tv_color = context.get_background_color(state)
color = Gdk.RGBA()
Gdk.RGBA.parse(color, 'PaleGoldenrod')
self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
color)
gtkgui_helpers.add_css_class(
self.msg_textview, 'msgcorrectingcolor')
message = history[pos - 1]
msg_buf.set_text(message)
return
if self.correcting:
# We were previously correcting
self.msg_textview.override_background_color(Gtk.StateType.NORMAL,
self.old_message_tv_color)
gtkgui_helpers.remove_css_class(
self.msg_textview, 'msgcorrectingcolor')
self.correcting = False
pos += -1 if direction == 'up' else +1
if pos == -1:
......@@ -1258,14 +1154,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
message = '> %s\n' % message.replace('\n', '\n> ')
msg_buf.set_text(message)
def lighten_color(self, color):
p = 0.4
mask = 0
color.red = int((color.red * p) + (mask * (1 - p)))
color.green = int((color.green * p) + (mask * (1 - p)))
color.blue = int((color.blue * p) + (mask * (1 - p)))
return color
def widget_set_visible(self, widget, state):
"""
Show or hide a widget
......
......@@ -15,7 +15,7 @@
"""
Provides a tiny framework with simple, yet powerful and extensible
architecture to implement commands in a streight and flexible,
architecture to implement commands in a straight and flexible,
declarative way.
"""
......@@ -67,7 +67,7 @@ class CommandProcessor(object):
"""
# This defines a command prefix (or an initializer), which should
# preceede a a text in order it to be processed as a command.
# precede a text in order for it to be processed as a command.
COMMAND_PREFIX = '/'
def process_as_command(self, text):
......
......@@ -75,7 +75,7 @@ class ChatCommandProcessor(CommandProcessor):
self.command_succeeded = True
def looks_like_command(self, text, body, name, arguments):
# Command escape stuff ggoes here. If text was prepended by the
# Command escape stuff goes here. If text was prepended by the
# command prefix twice, like //not_a_command (if prefix is set
# to /) then it will be escaped, that is sent just as a regular
# message with one (only one) prefix removed, so message will be
......
......@@ -24,7 +24,7 @@ import dialogs
from common import gajim
from common import helpers
from common.exceptions import GajimGeneralException
from common.logger import Constants
from common.logger import KindConstant
from ..errors import CommandError
from ..framework import CommandContainer, command, doc
......@@ -32,10 +32,6 @@ from ..mapping import generate_usage
from .hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
# This holds constants fron the logger, which we'll be using in some of our
# commands.
lc = Constants()
class StandardCommonCommands(CommandContainer):
"""
This command container contains standard commands which are common
......@@ -104,10 +100,10 @@ class StandardCommonCommands(CommandContainer):
raise CommandError(_("Limit must be an integer"))
for row in results:
contact, time, kind, show, message, subject = row
contact, time, kind, show, message, subject, log_line_id = row
if not contact:
if kind == lc.KIND_CHAT_MSG_SENT:
if kind == KindConstant.CHAT_MSG_SENT:
contact = gajim.nicks[self.account]
else:
contact = self.contact.name
......
......@@ -72,7 +72,12 @@ def create_log_db():
show INTEGER,
message TEXT,
subject TEXT,
additional_data TEXT DEFAULT '{}'
additional_data TEXT DEFAULT '{}',
stanza_id TEXT,
mam_id TEXT,
encryption TEXT,
encryption_state TEXT,
marker INTEGER
);
CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC);
......
......@@ -279,7 +279,6 @@ class Config:
'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')],
'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')],
'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ],
'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
......@@ -468,6 +467,7 @@ class Config:
'bannerbgcolor': [ opt_color, '', '', True ],
'bannerfont': [ opt_str, '', '', True ],
'bannerfontattrs': [ opt_str, 'B', '', True ],
'msgcorrectingcolor': [opt_color, '#eee8aa'],
# http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html
'state_inactive_color': [ opt_color, 'grey62' ],
......
......@@ -1733,16 +1733,6 @@ class Connection(CommonConnection, ConnectionHandlers):
iq = self.build_invisible_rule()
self.connection.send(iq)
def activate_privacy_rule(self, name):
"""
Activate a privacy rule
"""
if not gajim.account_is_connected(self.name):
return
iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVACY, xmlns='')
iq.setQuery().setTag('active', {'name': name})
self.connection.send(iq)
def get_max_blocked_list_order(self):
max_order = 0
for rule in self.blocked_list:
......@@ -1752,6 +1742,8 @@ class Connection(CommonConnection, ConnectionHandlers):
return max_order
def block_contacts(self, contact_list, message):
if self.privacy_default_list is None:
self.privacy_default_list = 'block'
if not self.privacy_rules_supported:
if self.blocking_supported: #XEP-0191
iq = nbxmpp.Iq('set', xmlns='')
......@@ -1764,16 +1756,15 @@ class Connection(CommonConnection, ConnectionHandlers):
for contact in contact_list:
self.send_custom_status('offline', message, contact.jid)
max_order = self.get_max_blocked_list_order()
new_rule = {'order': str(max_order + 1), 'type': 'jid', 'action': 'deny',
'value' : contact.jid, 'child': ['message', 'iq',
'presence-out']}
new_rule = {'order': str(max_order + 1),
'type': 'jid',
'action': 'deny',
'value': contact.jid}
self.blocked_list.append(new_rule)
self.blocked_contacts.append(contact.jid)
self.set_privacy_list('block', self.blocked_list)
self.set_privacy_list(self.privacy_default_list, self.blocked_list)
if len(self.blocked_list) == 1:
self.set_active_list('block')
self.set_default_list('block')
self.get_privacy_list('block')
self.set_default_list(self.privacy_default_list)
def unblock_contacts(self, contact_list):
if not self.privacy_rules_supported:
......@@ -1795,15 +1786,14 @@ class Connection(CommonConnection, ConnectionHandlers):
if rule['action'] != 'deny' or rule['type'] != 'jid' \
or rule['value'] not in self.to_unblock:
self.new_blocked_list.append(rule)
self.set_privacy_list('block', self.new_blocked_list)
self.get_privacy_list('block')
if len(self.new_blocked_list) == 0:
self.blocked_list = []
self.blocked_contacts = []
self.blocked_groups = []
self.set_default_list('')
self.set_active_list('')
self.del_privacy_list('block')
self.del_privacy_list(self.privacy_default_list)
else:
self.set_privacy_list(self.privacy_default_list, self.new_blocked_list)
if not gajim.interface.roster.regroup:
show = gajim.SHOW_LIST[self.connected]
else: # accounts merged
......@@ -1820,14 +1810,14 @@ class Connection(CommonConnection, ConnectionHandlers):
for contact in contact_list:
self.send_custom_status('offline', message, contact.jid)
max_order = self.get_max_blocked_list_order()
new_rule = {'order': str(max_order + 1), 'type': 'group', 'action': 'deny',
'value' : group, 'child': ['message', 'iq', 'presence-out']}
new_rule = {'order': str(max_order + 1),
'type': 'group',
'action': 'deny',
'value': group}
self.blocked_list.append(new_rule)
self.set_privacy_list('block', self.blocked_list)
self.set_privacy_list(self.privacy_default_list, self.blocked_list)
if len(self.blocked_list) == 1:
self.set_active_list('block')
self.set_default_list('block')
self.get_privacy_list('block')
self.set_default_list(self.privacy_default_list)
def unblock_group(self, group, contact_list):
if not self.privacy_rules_supported:
......@@ -1839,15 +1829,14 @@ class Connection(CommonConnection, ConnectionHandlers):
if rule['action'] != 'deny' or rule['type'] != 'group' or \
rule['value'] != group:
self.new_blocked_list.append(rule)
self.set_privacy_list('block', self.new_blocked_list)
self.get_privacy_list('block')
if len(self.new_blocked_list) == 0:
self.blocked_list = []
self.blocked_contacts = []
self.blocked_groups = []
self.set_default_list('')
self.set_active_list('')
self.del_privacy_list('block')
self.del_privacy_list(self.privacy_default_list)
else:
self.set_privacy_list(self.privacy_default_list, self.new_blocked_list)
if not gajim.interface.roster.regroup:
show = gajim.SHOW_LIST[self.connected]
else: # accounts merged
......@@ -1888,7 +1877,7 @@ class Connection(CommonConnection, ConnectionHandlers):
if iq_obj.getType() == 'error': # server doesn't support privacy lists
return
# active the privacy rule
self.activate_privacy_rule('invisible')
self.set_active_list('invisible')
self.connected = gajim.SHOW_LIST.index('invisible')
self.status = msg
priority = gajim.get_priority(self.name, 'invisible')
......@@ -2123,12 +2112,7 @@ class Connection(CommonConnection, ConnectionHandlers):
def _change_from_invisible(self):
if self.privacy_rules_supported:
if self.blocked_list:
self.activate_privacy_rule('block')
else:
iq = self.build_privacy_rule('visible', 'allow')
self.connection.send(iq)
self.activate_privacy_rule('visible')
self.set_active_list('')
def _update_status(self, show, msg):
xmpp_show = helpers.get_xmpp_show(show)
......@@ -2446,9 +2430,10 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connection.send(iq)
def _nec_privacy_list_received(self, obj):
roster = gajim.interface.roster
if obj.conn.name != self.name:
return
if obj.list_name != 'block':
if obj.list_name != self.privacy_default_list:
return
self.blocked_contacts = []
self.blocked_groups = []
......@@ -2475,6 +2460,12 @@ class Connection(CommonConnection, ConnectionHandlers):
self.blocked_groups.append(rule['value'])
self.blocked_list.append(rule)
if 'type' in rule:
if rule['type'] == 'jid':
roster.draw_contact(rule['value'], self.name)
if rule['type'] == 'group':
roster.draw_group(rule['value'], self.name)
def _request_bookmarks_xml(self):
if not gajim.account_is_connected(self.name):
return
......
......@@ -568,7 +568,11 @@ class ConnectionVcard:
elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
del self.awaiting_answers[id_]
if iq_obj.getType() != 'error':
self.get_privacy_list('block')
for list_ in iq_obj.getQueryPayload():
if list_.getName() == 'default':
self.privacy_default_list = list_.getAttr('name')
self.get_privacy_list(self.privacy_default_list)
break
# Ask metacontacts before roster
self.get_metacontacts()
else:
......@@ -1443,6 +1447,8 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
self.awaiting_xmpp_ping_id = None
self.continue_connect_info = None
self.privacy_default_list = None
try:
self.sleeper = common.sleepy.Sleepy()
HAS_IDLE = True
......@@ -2055,6 +2061,11 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
if q:
result.delChild(q)
self.connection.send(result)
for list_ in iq_obj.getQueryPayload():
if list_.getName() == 'list':
self.get_privacy_list(list_.getAttr('name'))
raise nbxmpp.NodeProcessed
def _getRoster(self):
......
......@@ -30,7 +30,7 @@ import os.path
docdir = '../'
basedir = '../'
localedir = '../po'
version = '0.16.10.2'
version = '0.16.10.3'
try:
node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
......
......@@ -202,7 +202,7 @@ class JingleContent:
# if the file is less than 10 mb, then it is small
# lets calculate it right away
if self.file_props.size < 10000000 and not self.file_props.hash_:
hash_data = content._compute_hash()
hash_data = self._compute_hash()
if hash_data:
file_tag.addChild(node=hash_data)
pjid = gajim.get_jid_without_resource(self.session.peerjid)
......
......@@ -490,7 +490,8 @@ class Logger:
all_messages.append(results[0])
return all_messages
def write(self, kind, jid, message=None, show=None, tim=None, subject=None, additional_data=None):
def write(self, kind, jid, message=None, show=None, tim=None, subject=None,
additional_data=None, mam_query=False):
"""
Write a row (status, gcstatus, message etc) to logs database
......@@ -566,7 +567,7 @@ class Logger:
except exceptions.PysqliteOperationalError as e:
raise exceptions.PysqliteOperationalError(str(e))
if kind == 'chat_msg_recv':
if not self.jid_is_from_pm(jid):
if not self.jid_is_from_pm(jid) and not mam_query:
# Save in unread table only if it's not a pm
write_unread = True
......@@ -1143,7 +1144,8 @@ class Logger:
log.debug('Log already in DB, ignoring it')
return
log.debug('New log received from server archives, storing it')
self.write(type_, with_, message=msg, tim=tim, additional_data=additional_data)
self.write(type_, with_, message=msg, tim=tim,
additional_data=additional_data, mam_query=True)
def _nec_gc_message_received(self, obj):
tim_f = float(obj.timestamp)
......
......@@ -234,6 +234,8 @@ class OptionsParser:
self.update_config_to_016101()
if old < [0, 16, 10, 2] and new >= [0, 16, 10, 2]:
self.update_config_to_016102()
if old < [0, 16, 10, 3] and new >= [0, 16, 10, 3]:
self.update_config_to_016103()
gajim.logger.init_vars()
gajim.logger.attach_cache_database()
......@@ -975,3 +977,25 @@ class OptionsParser:
con.close()
gajim.config.set('version', '0.16.10.2')
def update_config_to_016103(self):
back = os.getcwd()
os.chdir(logger.LOG_DB_FOLDER)
con = sqlite.connect(logger.LOG_DB_FILE)
os.chdir(back)
cur = con.cursor()
try:
cur.executescript(
'''
ALTER TABLE logs ADD COLUMN 'stanza_id' TEXT;
ALTER TABLE logs ADD COLUMN 'mam_id' TEXT;
ALTER TABLE logs ADD COLUMN 'encryption' TEXT;
ALTER TABLE logs ADD COLUMN 'encryption_state' TEXT;
ALTER TABLE logs ADD COLUMN 'marker' INTEGER;
'''
)
con.commit()
except sqlite.OperationalError:
pass
con.close()
gajim.config.set('version', '0.16.10.3')
......@@ -750,6 +750,7 @@ class PreferencesWindow:
# begin repainting themed widgets throughout
gajim.interface.roster.repaint_themed_widgets()
gajim.interface.roster.change_roster_style(None)
gtkgui_helpers.load_css()
def update_theme_list(self):
theme_combobox = self.xml.get_object('theme_combobox')
......@@ -880,14 +881,7 @@ class PreferencesWindow:
else:
font = ''
gajim.config.set(text, font)
self.update_text_font()
def update_text_font(self):
"""
Update text font in opened chat windows
"""
for ctrl in self._get_all_controls():
ctrl.update_font()
gtkgui_helpers.load_css()
def on_incoming_nick_colorbutton_color_set(self, widget):
self.on_preference_widget_color_set(widget, 'inmsgcolor')
......
This diff is collapsed.
......@@ -1671,7 +1671,7 @@ class YesNoDialog(HigDialog):
on_response_no=self.on_response_no)
if checktext:
self.checkbutton = Gtk.CheckButton(label=checktext)
self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
self.vbox.pack_start(self.checkbutton, False, True, 0)
else:
self.checkbutton = None
......@@ -1764,7 +1764,7 @@ class ConfirmationDialogCheck(ConfirmationDialog):
ok_button = self.action_area.get_children()[0] # right to left
ok_button.grab_focus()
self.checkbutton = Gtk.CheckButton(label=checktext)
self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
self.vbox.pack_start(self.checkbutton, False, True, 0)
self.set_modal(is_modal)
self.popup()
......@@ -1820,14 +1820,14 @@ class ConfirmationDialogDoubleCheck(ConfirmationDialog):
ok_button.grab_focus()
if checktext1:
self.checkbutton1 = Gtk.CheckButton(label=checktext1)
self.checkbutton1 = Gtk.CheckButton.new_with_mnemonic(checktext1)
if tooltip1:
self.checkbutton1.set_tooltip_text(tooltip1)
self.vbox.pack_start(self.checkbutton1, False, True, 0)
else:
self.checkbutton1 = None
if checktext2:
self.checkbutton2 = Gtk.CheckButton(label=checktext2)
self.checkbutton2 = Gtk.CheckButton.new_with_mnemonic(checktext2)
if tooltip2:
self.checkbutton2.set_tooltip_text(tooltip2)
self.vbox.pack_start(self.checkbutton2, False, True, 0)
......@@ -2098,7 +2098,7 @@ class InputDialogCheck(InputDialog):
self.input_entry.select_region(0, -1) # select all
if checktext:
self.checkbutton = Gtk.CheckButton(label=checktext)
self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
self.vbox.pack_start(self.checkbutton, False, True, 0)
self.checkbutton.show()
......@@ -3379,10 +3379,6 @@ class XMLConsoleWindow:
self.enabled = True
self.xml.get_object('enable_checkbutton').set_active(True)
col = Gdk.RGBA()
Gdk.RGBA.parse(col, color)
self.input_textview.override_color(Gtk.StateType.NORMAL, col)
if len(gajim.connections) > 1:
title = _('XML Console for %s') % self.account
else:
......@@ -5727,5 +5723,5 @@ class BigAvatarWindow(Gtk.Window):
"""
Just moved the mouse so show the cursor
"""
cursor = Gdk.Cursor.new(Gdk.CursorType.LEFT_PTR)
cursor = gtkgui_helpers.get_cursor('LEFT_PTR')
self.get_window().set_cursor(cursor)
......@@ -561,7 +561,6 @@ _('Without a connection, you can not browse available services'))
self.banner_eventbox = self.xml.get_object('banner_agent_eventbox')
self.style_event_id = 0
self.banner.realize()
self.paint_banner()
self.action_buttonbox = self.xml.get_object('action_buttonbox')
# Address combobox
......@@ -660,63 +659,6 @@ _('Without a connection, you can not browse available services'))
(markup, font.to_string(), text_after)
self.banner.set_markup(markup)
def paint_banner(self):
"""
Repaint the banner with theme color
"""
theme = gajim.config.get('roster_theme')
bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor')
textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor')
self.disconnect_style_event()
if bgcolor:
color = Gdk.RGBA()
Gdk.RGBA.parse(color, bgcolor)
self.banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
color)
default_bg = False
else:
default_bg = True
if textcolor:
color = Gdk.RGBA()
Gdk.RGBA.parse(color, textcolor)
self.banner.override_color(Gtk.StateType.NORMAL, color)
default_fg = False
else:
default_fg = True
if default_fg or default_bg:
self._on_style_set_event(self.banner, None, default_fg, default_bg)
if self.browser:
self.browser.update_theme()
def disconnect_style_event(self):
if self.style_event_id:
self.banner.disconnect(self.style_event_id)
self.style_event_id = 0
def connect_style_event(self, set_fg = False, set_bg = False):
self.disconnect_style_event()
self.style_event_id = self.banner.connect('style-set',
self._on_style_set_event, set_fg, set_bg)
def _on_style_set_event(self, widget, style, *opts):
"""
Set style of widget from style class *.Frame.Eventbox
opts[0] == True -> set fg color
opts[1] == True -> set bg color
"""
self.disconnect_style_event()
context = widget.get_style_context()
if opts[1]:
bg_color = context.get_background_color(Gtk.StateFlags.SELECTED)
self.banner_eventbox.override_background_color(Gtk.StateType.NORMAL,
bg_color)
if opts[0]:
fg_color = context.get_color(Gtk.StateFlags.SELECTED)
self.banner.override_color(Gtk.StateType.NORMAL, fg_color)
self.banner.ensure_style()
self.connect_style_event(opts[0], opts[1])
def destroy(self, chain = False):
"""
Close the browser. This can optionally close its children and propagate
......
......@@ -94,6 +94,9 @@ class GajimApplication(Gtk.Application):
self.add_main_option('loglevel', ord('l'), GLib.OptionFlags.NONE,
GLib.OptionArg.STRING,
_('Configure logging system'), 'LEVEL')
self.add_main_option('warnings', ord('w'), GLib.OptionFlags.NONE,
GLib.OptionArg.NONE,
_('Show all warnings'))
self.profile = ''
self.config_path = None
......@@ -242,7 +245,9 @@ class GajimApplication(Gtk.Application):
def do_activate(self):
Gtk.Application.do_activate(self)
from gui_interface import Interface
import gtkgui_helpers
self.interface = Interface()
gtkgui_helpers.load_css()
self.interface.run(self)
self.add_actions()
import gui_menu_builder
......@@ -286,6 +291,8 @@ class GajimApplication(Gtk.Application):
if options.contains('loglevel'):
loglevel = options.lookup_value('loglevel').get_string()
logging_helpers.set_loglevels(loglevel)
if options.contains('warnings'):
self.show_warnings()
return -1
def do_command_line(self, command_line: Gio.ApplicationCommandLine) -> int:
......@@ -294,6 +301,19 @@ class GajimApplication(Gtk.Application):
self.activate()
return 0
def show_warnings(self):
import traceback
import warnings
def warn_with_traceback(message, category, filename, lineno,
file=None, line=None):
traceback.print_stack(file=sys.stderr)
sys.stderr.write(warnings.formatwarning(message, category,
filename, lineno, line))
warnings.showwarning = warn_with_traceback
warnings.filterwarnings(action="always")
def frozen_logging(self, path):
import warnings
if not os.path.exists(path):
......
......@@ -709,29 +709,17 @@ class GroupchatControl(ChatControlBase):
has_focus = self.parent_win.window.get_property('has-toplevel-focus')
current_tab = self.parent_win.get_active_control() == self
color_name = None
color = None
theme = gajim.config.get('roster_theme')
context = self.parent_win.notebook.get_style_context()
if chatstate == 'attention' and (not has_focus or not current_tab):
self.attention_flag = True
color_name = gajim.config.get_per('themes', theme,
'state_muc_directed_msg_color')
elif chatstate:
if chatstate == 'active' or (current_tab and has_focus):
self.attention_flag = False
# get active color from gtk
color = context.get_color(Gtk.StateFlags.ACTIVE)
elif chatstate == 'newmsg' and (not has_focus or not current_tab) \
and not self.attention_flag:
color_name = gajim.config.get_per('themes', theme,
'state_muc_msg_color')
if color_name:
color = Gdk.RGBA()
ok = Gdk.RGBA.parse(color, color_name)
if not ok:
del color
color = context.get_color(Gtk.StateFlags.ACTIVE)
color = 'state_muc_directed_msg_color'
elif chatstate == 'active' or (current_tab and has_focus):
self.attention_flag = False
# get active color from gtk
color = 'active'
elif chatstate == 'newmsg' and (not has_focus or not current_tab) \
and not self.attention_flag:
color = 'state_muc_msg_color'
if self.is_continued:
# if this is a continued conversation
......@@ -1996,8 +1984,8 @@ class GroupchatControl(ChatControlBase):
self.last_sent_msg = msg
if self.correcting:
self.correcting = False
self.msg_textview.override_background_color(
Gtk.StateType.NORMAL, self.old_message_tv_color)
gtkgui_helpers.remove_css_class(
self.msg_textview, 'msgcorrectingcolor')
if self.correcting and self.last_sent_msg:
correction_msg = self.last_sent_msg
......@@ -2781,6 +2769,7 @@ class GroupchatControl(ChatControlBase):
def on_block(self, widget, nick):
fjid = self.room_jid + '/' + nick
connection = gajim.connections[self.account]
default = connection.privacy_default_list
if fjid in connection.blocked_contacts:
return
max_order = connection.get_max_blocked_list_order()
......@@ -2790,15 +2779,14 @@ class GroupchatControl(ChatControlBase):
connection.blocked_list.append(new_rule)
connection.blocked_contacts.append(fjid)
self.draw_contact(nick)
connection.set_privacy_list('block', connection.blocked_list)
connection.set_privacy_list(default, connection.blocked_list)
if len(connection.blocked_list) == 1:
connection.set_active_list('block')
connection.set_default_list('block')
connection.get_privacy_list('block')
connection.set_default_list(default)
def on_unblock(self, widget, nick):
fjid = self.room_jid + '/' + nick
connection = gajim.connections[self.account]
default = connection.privacy_default_list
connection.new_blocked_list = []
# needed for draw_contact:
if fjid in connection.blocked_contacts:
......@@ -2809,18 +2797,17 @@ class GroupchatControl(ChatControlBase):
or rule['value'] != fjid:
connection.new_blocked_list.append(rule)
connection.set_privacy_list('block', connection.new_blocked_list)
connection.get_privacy_list('block')
if len(connection.new_blocked_list) == 0:
connection.blocked_list = []
connection.blocked_contacts = []
connection.blocked_groups = []
connection.set_default_list('')
connection.set_active_list('')
connection.del_privacy_list('block')
connection.del_privacy_list(default)
if 'privay_list_block' in gajim.interface.instances[self.account]:
del gajim.interface.instances[self.account]\
['privay_list_block']
else:
connection.set_privacy_list(default, connection.new_blocked_list)
def on_voice_checkmenuitem_activate(self, widget, nick):
if widget.get_active():
......
......@@ -48,6 +48,7 @@ log = logging.getLogger('gajim.gtkgui_helpers')
from common import i18n
from common import gajim
from common import pep
from common import configpaths
gtk_icon_theme = Gtk.IconTheme.get_default()
gtk_icon_theme.append_search_path(gajim.ICONS_DIR)
......@@ -493,6 +494,11 @@ def get_invisible_cursor():
cursor_pixbuf, 0, 0)
return cursor
def get_cursor(attr):
display = Gdk.Display.get_default()
cursor = getattr(Gdk.CursorType, attr)
return Gdk.Cursor.new_for_display(display, cursor)
def get_current_desktop(window):
"""
Return the current virtual desktop for given window
......@@ -1088,3 +1094,94 @@ def __label_size_allocate(widget, allocation):
def get_action(action):
return gajim.app.lookup_action(action)
def load_css():
path = os.path.join(configpaths.get('DATA'), 'style', 'gajim.css')
try:
with open(path, "r") as f:
css = f.read()
except Exception as exc:
print('Error loading css: %s', exc)
return
provider = Gtk.CssProvider()
css = "\n".join((css, convert_config_to_css()))
provider.load_from_data(bytes(css.encode()))
Gtk.StyleContext.add_provider_for_screen(
Gdk.Screen.get_default(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
def convert_config_to_css():
css = ''
themed_widgets = {
'ChatControl-BannerEventBox': ('bannerbgcolor', 'background'),
'ChatControl-BannerNameLabel': ('bannertextcolor', 'color'),
'ChatControl-BannerLabel': ('bannertextcolor', 'color'),
'GroupChatControl-BannerEventBox': ('bannerbgcolor', 'background'),
'GroupChatControl-BannerNameLabel': ('bannertextcolor', 'color'),
'GroupChatControl-BannerLabel': ('bannertextcolor', 'color'),
'Discovery-BannerEventBox': ('bannerbgcolor', 'background'),
'Discovery-BannerLabel': ('bannertextcolor', 'color')}
classes = {'state_composing_color': ('', 'color'),
'state_inactive_color': ('', 'color'),
'state_gone_color': ('', 'color'),
'state_paused_color': ('', 'color'),
'msgcorrectingcolor': ('text', 'background'),
'state_muc_directed_msg_color': ('', 'color'),
'state_muc_msg_color': ('', 'color')}
theme = gajim.config.get('roster_theme')
for key, values in themed_widgets.items():
config, attr = values
css += '#{} {{'.format(key)
value = gajim.config.get_per('themes', theme, config)
if value:
css += '{attr}: {color};\n'.format(attr=attr, color=value)
css += '}\n'
for key, values in classes.items():
node, attr = values
value = gajim.config.get_per('themes', theme, key)
if value:
css += '.theme_{cls} {node} {{ {attr}: {color}; }}\n'.format(
cls=key, node=node, attr=attr, color=value)
css += add_css_font()
return css
def add_css_class(widget, class_name):
style = widget.get_style_context()
for css_cls in style.list_classes():
if css_cls.startswith('theme_'):
style.remove_class(css_cls)
if class_name:
style.add_class('theme_' + class_name)
def remove_css_class(widget, class_name):
style = widget.get_style_context()
style.remove_class('theme_' + class_name)
def add_css_font():
conversation_font = gajim.config.get('conversation_font')
if not conversation_font:
return ''
font = Pango.FontDescription(conversation_font)
unit = "pt" if Gtk.check_version(3, 22, 0) is None else "px"
css = """
.font_custom {{
font-family: {family};
font-size: {size}{unit};
font-weight: {weight};
}}""".format(
family=font.get_family(),
size=int(round(font.get_size() / Pango.SCALE)),
unit=unit,
weight=int(font.get_weight()))
css = css.replace("font-size: 0{unit};".format(unit=unit), "")
css = css.replace("font-weight: 0;", "")
css = "\n".join(filter(lambda x: x.strip(), css.splitlines()))
return css
......@@ -1831,7 +1831,7 @@ class Interface:
# Using isinstance here because we want to catch all derived types
if isinstance(ctrl, ChatControlBase):
tv = ctrl.conv_textview
tv.scroll_to_end()
tv.scroll_to_end_iter()
################################################################################
### Methods dealing with emoticons
......
......@@ -52,6 +52,7 @@ if __name__ == '__main__':
import common.configpaths
common.configpaths.gajimpaths.init(None)
from common import gajim
import gtkgui_helpers
from gtkgui_helpers import get_icon_pixmap
from common import helpers
from common.exceptions import GajimGeneralException
......@@ -841,21 +842,20 @@ class HtmlTextView(Gtk.TextView):
self.set_wrap_mode(Gtk.WrapMode.CHAR)
self.set_editable(False)
self._changed_cursor = False
self.connect('destroy', self.__destroy_event)
self.connect('motion-notify-event', self.__motion_notify_event)
self.connect('leave-notify-event', self.__leave_event)
self.connect('enter-notify-event', self.__motion_notify_event)
self.set_has_tooltip(True)
self.connect('realize', self.on_html_text_view_realized)
self.connect('unrealize', self.on_html_text_view_unrealized)
self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
self.id_ = self.connect('button-release-event',
self.on_left_mouse_button_release)
self.get_buffer().eol_tag = self.get_buffer().create_tag('eol')
self.tooltip = tooltips.BaseTooltip()
self.config = gajim.config
self.interface = gajim.interface
# end big hack
def connect_tooltip(self, func=None):
self.connect('query-tooltip', func or self.__query_tooltip)
def create_tags(self):
buffer_ = self.get_buffer()
......@@ -880,65 +880,27 @@ class HtmlTextView(Gtk.TextView):
self.tagSthAtSth.set_property('underline', Pango.Underline.SINGLE)
self.tagSthAtSth.connect('event', self.hyperlink_handler, 'sth_at_sth')
def __destroy_event(self, widget):
if self.tooltip.timeout != 0 or self.tooltip.shown:
self.tooltip.hide_tooltip()
def __leave_event(self, widget, event):
def __query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
window = widget.get_window(Gtk.TextWindowType.TEXT)
x_pos, y_pos = self.window_to_buffer_coords(
Gtk.TextWindowType.TEXT, x_pos, y_pos)
if Gtk.MINOR_VERSION > 18:
iter_ = self.get_iter_at_position(x_pos, y_pos)[1]
else:
iter_ = self.get_iter_at_position(x_pos, y_pos)[0]
for tag in iter_.get_tags():
if getattr(tag, 'is_anchor', False):
text = getattr(tag, 'title', False)
if text:
if len(text) > 50:
text