...
 
Commits (53)
......@@ -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
......@@ -7,7 +7,7 @@ dist_gajimplugins_PYTHON =
pluginsdirs = ${sort ${dir ${wildcard ${srcdir}/*/ ${srcdir}/*/*/}}}
pluginsfiles = $(wildcard ${p}/*.py ${p}/manifest.ini ${p}/*.ui ${p}/*.png)
pluginsfiles = $(wildcard ${p}/*.py ${p}/manifest.ini ${p}/*.ui ${p}/*.png ${p}/*.pem)
nobase_gajimplugins_DATA = $(foreach p, ${pluginsdirs}, $(pluginsfiles))
......
......@@ -23,7 +23,7 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
from enum import IntEnum
from enum import IntEnum, unique
from gi.repository import Gtk
import gtkgui_helpers
......@@ -32,6 +32,7 @@ from gi.repository import Pango
from common import gajim
@unique
class Column(IntEnum):
PREFERENCE_NAME = 0
VALUE = 1
......
......@@ -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
......@@ -91,8 +87,7 @@ class StandardCommonCommands(CommandContainer):
@command('lastlog', overlap=True)
@doc(_("Show logged messages which mention given text"))
def grep(self, text, limit=None):
results = gajim.logger.get_search_results_for_query(self.contact.jid,
text, self.account)
results = gajim.logger.search_log(self.contact.jid, text, self.account)
if not results:
raise CommandError(_("%s: Nothing found") % text)
......@@ -104,23 +99,22 @@ class StandardCommonCommands(CommandContainer):
raise CommandError(_("Limit must be an integer"))
for row in results:
contact, time, kind, show, message, subject = row
contact = row.contact_name
if not contact:
if kind == lc.KIND_CHAT_MSG_SENT:
if row.kind == KindConstant.CHAT_MSG_SENT:
contact = gajim.nicks[self.account]
else:
contact = self.contact.name
time_obj = localtime(time)
date_obj = date.fromtimestamp(time)
time_obj = localtime(row.time)
date_obj = date.fromtimestamp(row.time)
date_ = strftime('%Y-%m-%d', time_obj)
time_ = strftime('%H:%M:%S', time_obj)
if date_obj == date.today():
formatted = "[%s] %s: %s" % (time_, contact, message)
formatted = "[%s] %s: %s" % (time_, contact, row.message)
else:
formatted = "[%s, %s] %s: %s" % (date_, time_, contact, message)
formatted = "[%s, %s] %s: %s" % (date_, time_, contact, row.message)
self.echo(formatted)
......
......@@ -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);
......
......@@ -35,8 +35,9 @@
import re
from common import defs
from gi.repository import GLib
from enum import IntEnum
from enum import IntEnum, unique
@unique
class Option(IntEnum):
TYPE = 0
VAL = 1
......@@ -279,7 +280,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 +468,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' ],
......
......@@ -26,8 +26,9 @@ import os
import sys
import tempfile
from common import defs
from enum import Enum
from enum import Enum, unique
@unique
class Type(Enum):
CONFIG = 0
CACHE = 1
......
......@@ -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):
......
......@@ -1150,10 +1150,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
try:
self.get_jid_resource()
except helpers.InvalidFormat:
gajim.nec.push_incoming_event(InformationEvent(None, conn=self.conn,
level='error', pri_txt=_('Invalid JID'),
sec_txt=_('A message from a non-valid JID arrived, it has been '
'ignored.')))
log.warning('Invalid JID: %s, ignoring it',
self.stanza.getFrom())
return
address_tag = self.stanza.getTag('addresses',
......@@ -1165,8 +1163,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
try:
self.fjid = helpers.parse_jid(address.getAttr('jid'))
except helpers.InvalidFormat:
log.warning('Invalid JID: %s, ignoring it' % address.getAttr(
'jid'))
log.warning('Invalid JID: %s, ignoring it',
address.getAttr('jid'))
return
self.jid = gajim.get_jid_without_resource(self.fjid)
......@@ -1195,11 +1193,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
try:
self.get_jid_resource()
except helpers.InvalidFormat:
gajim.nec.push_incoming_event(InformationEvent(None,
conn=self.conn, level='error',
pri_txt=_('Invalid JID'),
sec_txt=_('A message from a non-valid JID arrived, it '
'has been ignored.')))
log.warning('Invalid JID: %s, ignoring it',
self.stanza.getFrom())
return
self.forwarded = True
......
......@@ -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)
......
......@@ -23,7 +23,7 @@ import hashlib
import logging
import os
import threading
from enum import IntEnum
from enum import IntEnum, unique
import nbxmpp
from common import gajim
from common import configpaths
......@@ -39,6 +39,7 @@ from common.jingle_ftstates import (
log = logging.getLogger('gajim.c.jingle_ft')
@unique
class State(IntEnum):
NOT_STARTED = 0
INITIALIZED = 1
......
......@@ -29,7 +29,7 @@ Handles Jingle sessions (XEP 0166)
# * timeout
import logging
from enum import Enum
from enum import Enum, unique
import nbxmpp
from common import gajim
from common.jingle_transport import get_jingle_transport, JingleTransportIBB
......@@ -43,6 +43,7 @@ from common.connection_handlers_events import (
log = logging.getLogger("gajim.c.jingle_session")
# FIXME: Move it to JingleSession.States?
@unique
class JingleStates(Enum):
"""
States in which jingle session may exist
......
......@@ -19,7 +19,7 @@ Handles Jingle Transports (currently only ICE-UDP)
import logging
import socket
from enum import IntEnum
from enum import IntEnum, unique
import nbxmpp
from common import gajim
......@@ -34,6 +34,7 @@ def get_jingle_transport(node):
return transports[namespace](node)
@unique
class TransportType(IntEnum):
"""
Possible types of a JingleTransport
......
......@@ -33,10 +33,11 @@ import sys
import time
import datetime
import json
from collections import namedtuple
from gzip import GzipFile
from io import BytesIO
from gi.repository import GLib
from enum import IntEnum
from enum import IntEnum, unique
from common import exceptions
from common import gajim
......@@ -51,10 +52,12 @@ CACHE_DB_PATH = gajim.gajimpaths['CACHE_DB']
import logging
log = logging.getLogger('gajim.c.logger')
@unique
class JIDConstant(IntEnum):
NORMAL_TYPE = 0
ROOM_TYPE = 1
@unique
class KindConstant(IntEnum):
STATUS = 0
GCSTATUS = 1
......@@ -65,6 +68,7 @@ class KindConstant(IntEnum):
CHAT_MSG_SENT = 6
ERROR = 7
@unique
class ShowConstant(IntEnum):
ONLINE = 0
CHAT = 1
......@@ -73,6 +77,7 @@ class ShowConstant(IntEnum):
DND = 4
OFFLINE = 5
@unique
class TypeConstant(IntEnum):
AIM = 0
GG = 1
......@@ -90,6 +95,7 @@ class TypeConstant(IntEnum):
MRIM = 13
NO_TRANSPORT = 14
@unique
class SubscriptionConstant(IntEnum):
NONE = 0
TO = 1
......@@ -182,7 +188,7 @@ class Logger:
def get_jids_already_in_db(self):
try:
self.cur.execute('SELECT jid FROM jids')
# list of tupples: [('aaa@bbb',), ('cc@dd',)]
# list of tuples: [('aaa@bbb',), ('cc@dd',)]
rows = self.cur.fetchall()
except sqlite.DatabaseError:
raise exceptions.DatabaseMalformed
......@@ -490,7 +496,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 +573,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
......@@ -582,8 +589,8 @@ class Logger:
"""
Accept how many rows to restore and when to time them out (in minutes)
(mark them as too old) and number of messages that are in queue and are
already logged but pending to be viewed, returns a list of tupples
containg time, kind, message, sibject list with empty tupple if nothing
already logged but pending to be viewed, returns a list of tuples
containg time, kind, message, subject list with empty tuple if nothing
found to meet our demands
"""
try:
......@@ -608,30 +615,43 @@ class Logger:
restore_how_many_rows, pending_how_many), jid_tuple)
results = self.cur.fetchall()
messages = []
for entry in results:
entry = list(entry)
entry[4] = json.loads(entry[4])
additional_data = json.loads(entry[4])
parsed_entry = entry[:4] + (additional_data, ) + entry[5:]
messages.append(parsed_entry)
except sqlite.DatabaseError:
raise exceptions.DatabaseMalformed
results.reverse()
return results
messages.reverse()
return messages
def get_unix_time_from_date(self, year, month, day):
# year (fe 2005), month (fe 11), day (fe 25)
# returns time in seconds for the second that starts that date since epoch
# gimme unixtime from year month day:
d = datetime.date(year, month, day)
local_time = d.timetuple() # time tupple (compat with time.localtime())
local_time = d.timetuple() # time tuple (compat with time.localtime())
# we have time since epoch baby :)
start_of_day = int(time.mktime(local_time))
return start_of_day
Message = namedtuple('Message',
['contact_name', 'time', 'kind', 'show', 'message', 'subject',
'additional_data', 'log_line_id'])
def get_conversation_for_date(self, jid, year, month, day, account):
"""
Return contact_name, time, kind, show, message, subject, additional_data, log_line_id
Load the complete conversation with a given jid on a specific date
The conversation contains all messages that were exchanged between
`account` and `jid` on the day specified by `year`, `month` and `day`,
where `month` and `day` are 1-based.
For each row in a list of tupples, returns list with empty tupple if we
found nothing to meet our demands
The conversation will be returned as a list of single messages of type
`Logger.Message`. Messages in the list are sorted chronologically. An
empty list will be returned if there are no messages in the log database
for the requested combination of `jid` and `account` on the given date.
"""
try:
self.get_jid_id(jid)
......@@ -653,19 +673,23 @@ class Logger:
ORDER BY time
''' % (where_sql, start_of_day, last_second_of_day), jid_tuple)
results = self.cur.fetchall()
for entry in results:
entry = list(entry)
entry[6] = json.loads(entry[6])
results = [self.Message(*row) for row in self.cur.fetchall()]
for message in results:
message._replace(additional_data=json.loads(message.additional_data))
return results
def get_search_results_for_query(self, jid, query, account, year=False,
month=False, day=False):
def search_log(self, jid, query, account, year=None, month=None, day=None):
"""
Returns contact_name, time, kind, show, message, subject, log_line_id
Search the conversation log for messages containing the `query` string.
The search can either span the complete log for the given `account` and
`jid` or be restriced to a single day by specifying `year`, `month` and
`day`, where `month` and `day` are 1-based.
For each row in a list of tupples, returns list with empty tupple if we
found nothing to meet our demands
All messages matching the specified criteria will be returned in a list
containing tuples of type `Logger.Message`. If no messages match the
criteria, an empty list will be returned.
"""
try:
self.get_jid_id(jid)
......@@ -675,12 +699,14 @@ class Logger:
where_sql, jid_tuple = self._build_contact_where(account, jid)
like_sql = '%' + query.replace("'", "''") + '%'
if year:
if year and month and day:
start_of_day = self.get_unix_time_from_date(year, month, day)
seconds_in_a_day = 86400 # 60 * 60 * 24
last_second_of_day = start_of_day + seconds_in_a_day - 1
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject, log_line_id FROM logs
SELECT contact_name, time, kind, show, message, subject,
additional_data, log_line_id
FROM logs
WHERE (%s) AND message LIKE '%s'
AND time BETWEEN %d AND %d
ORDER BY time
......@@ -688,12 +714,17 @@ class Logger:
jid_tuple)
else:
self.cur.execute('''
SELECT contact_name, time, kind, show, message, subject, log_line_id FROM logs
SELECT contact_name, time, kind, show, message, subject,
additional_data, log_line_id
FROM logs
WHERE (%s) AND message LIKE '%s'
ORDER BY time
''' % (where_sql, like_sql), jid_tuple)
results = self.cur.fetchall()
results = [self.Message(*row) for row in self.cur.fetchall()]
for message in results:
message._replace(additional_data=json.loads(message.additional_data))
return results
def get_days_with_logs(self, jid, year, month, max_day, account):
......@@ -1143,7 +1174,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')
......@@ -17,8 +17,9 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
from enum import IntEnum
from enum import IntEnum, unique
@unique
class Constant(IntEnum):
NAME = 0
DOMAIN = 1
......@@ -26,6 +27,7 @@ class Constant(IntEnum):
BARE_NAME = 3
TXT = 4
@unique
class ConstantRI(IntEnum):
INTERFACE = 0
PROTOCOL = 1
......
......@@ -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