# -*- coding:utf-8 -*- ## src/history_window.py ## ## Copyright (C) 2003-2012 Yann Leboulanger ## Copyright (C) 2005 Vincent Hanquez ## Copyright (C) 2005-2006 Nikos Kouremenos ## Copyright (C) 2006 Dimitur Kirov ## Travis Shirk ## Copyright (C) 2006-2008 Jean-Marie Traissard ## Copyright (C) 2007-2008 Stephan Erb ## Copyright (C) 2008 Brendan Taylor ## ## This file is part of Gajim. ## ## Gajim is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published ## by the Free Software Foundation; version 3 only. ## ## Gajim is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Gajim. If not, see . ## from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GObject import time import calendar import gtkgui_helpers import conversation_textview from common import gajim from common import helpers from common import exceptions from common.logger import Constants constants = Constants() # Completion dict ( C_INFO_JID, C_INFO_ACCOUNT, C_INFO_NAME, C_INFO_COMPLETION ) = range(4) # contact_name, date, message, time ( C_LOG_JID, C_CONTACT_NAME, C_UNIXTIME, C_MESSAGE, C_TIME ) = range(5) class HistoryWindow: """ Class for browsing logs of conversations with contacts """ def __init__(self, jid = None, account = None): xml = gtkgui_helpers.get_gtk_builder('history_window.ui') self.window = xml.get_object('history_window') self.calendar = xml.get_object('calendar') scrolledwindow = xml.get_object('scrolledwindow') self.history_textview = conversation_textview.ConversationTextview( account, used_in_history_window = True) scrolledwindow.add(self.history_textview.tv) self.history_buffer = self.history_textview.tv.get_buffer() self.history_buffer.create_tag('highlight', background = 'yellow') self.checkbutton = xml.get_object('log_history_checkbutton') self.checkbutton.connect('toggled', self.on_log_history_checkbutton_toggled) self.search_entry = xml.get_object('search_entry') self.query_liststore = xml.get_object('query_liststore') self.jid_entry = xml.get_object('query_entry') self.jid_entry.connect('activate', self.on_jid_entry_activate) self.results_treeview = xml.get_object('results_treeview') self.results_window = xml.get_object('results_scrolledwindow') self.search_in_date = xml.get_object('search_in_date') # contact_name, date, message, time model = Gtk.ListStore(str, str, str, str, str) self.results_treeview.set_model(model) col = Gtk.TreeViewColumn(_('Name')) self.results_treeview.append_column(col) renderer = Gtk.CellRendererText() col.pack_start(renderer, True) col.add_attribute(renderer, 'text', C_CONTACT_NAME) col.set_sort_column_id(C_CONTACT_NAME) # user can click this header and sort col.set_resizable(True) col = Gtk.TreeViewColumn(_('Date')) self.results_treeview.append_column(col) renderer = Gtk.CellRendererText() col.pack_start(renderer, True) col.add_attribute(renderer, 'text', C_UNIXTIME) col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort col.set_resizable(True) col = Gtk.TreeViewColumn(_('Message')) self.results_treeview.append_column(col) renderer = Gtk.CellRendererText() col.pack_start(renderer, True) col.add_attribute(renderer, 'text', C_MESSAGE) col.set_resizable(True) self.jid = None # The history we are currently viewing self.account = None self.completion_dict = {} self.accounts_seen_online = [] # Update dict when new accounts connect self.jids_to_search = [] # This will load history too task = self._fill_completion_dict() GObject.idle_add(next, task) if jid: self.jid_entry.set_text(jid) else: self._load_history(None) gtkgui_helpers.resize_window(self.window, gajim.config.get('history_window_width'), gajim.config.get('history_window_height')) gtkgui_helpers.move_window(self.window, gajim.config.get('history_window_x-position'), gajim.config.get('history_window_y-position')) xml.connect_signals(self) self.window.show_all() def _fill_completion_dict(self): """ Fill completion_dict for key auto completion. Then load history for current jid (by calling another function) Key will be either jid or full_completion_name (contact name or long description like "pm-contact from groupchat...."). {key : (jid, account, nick_name, full_completion_name} This is a generator and does pseudo-threading via idle_add(). """ liststore = gtkgui_helpers.get_completion_liststore(self.jid_entry) # Add all jids in logs.db: db_jids = gajim.logger.get_jids_in_db() completion_dict = dict.fromkeys(db_jids) self.accounts_seen_online = list(gajim.contacts.get_accounts()) # Enhance contacts of online accounts with contact. Needed for mapping below for account in self.accounts_seen_online: completion_dict.update(helpers.get_contact_dict_for_account(account)) muc_active_img = gtkgui_helpers.load_icon('muc_active') contact_img = gajim.interface.jabber_state_images['16']['online'] muc_active_pix = muc_active_img.get_pixbuf() contact_pix = contact_img.get_pixbuf() keys = list(completion_dict.keys()) # Move the actual jid at first so we load history faster actual_jid = self.jid_entry.get_text() if actual_jid in keys: keys.remove(actual_jid) keys.insert(0, actual_jid) if '' in keys: keys.remove('') # Map jid to info tuple # Warning : This for is time critical with big DB for key in keys: completed = key contact = completion_dict[completed] if contact: info_name = contact.get_shown_name() info_completion = info_name info_jid = contact.jid else: # Corrensponding account is offline, we know nothing info_name = completed.split('@')[0] info_completion = completed info_jid = completed info_acc = self._get_account_for_jid(info_jid) if gajim.logger.jid_is_room_jid(completed) or\ gajim.logger.jid_is_from_pm(completed): pix = muc_active_pix if gajim.logger.jid_is_from_pm(completed): # It's PM. Make it easier to find room, nick = gajim.get_room_and_nick_from_fjid(completed) info_completion = '%s from %s' % (nick, room) completed = info_completion info_name = nick else: pix = contact_pix liststore.append((pix, completed)) self.completion_dict[key] = (info_jid, info_acc, info_name, info_completion) self.completion_dict[completed] = (info_jid, info_acc, info_name, info_completion) if key == actual_jid: self._load_history(info_jid, info_acc) yield True keys.sort() yield False def _get_account_for_jid(self, jid): """ Return the corresponding account of the jid. May be None if an account could not be found """ accounts = gajim.contacts.get_accounts() account = None for acc in accounts: jid_list = gajim.contacts.get_jid_list(acc) gc_list = gajim.contacts.get_gc_list(acc) if jid in jid_list or jid in gc_list: account = acc break return account def on_history_window_destroy(self, widget): self.history_textview.del_handlers() del gajim.interface.instances['logs'] def on_history_window_key_press_event(self, widget, event): if event.keyval == Gdk.KEY_Escape: self.save_state() self.window.destroy() def on_close_button_clicked(self, widget): self.save_state() self.window.destroy() def on_jid_entry_activate(self, widget): jid = self.jid_entry.get_text() account = None # we don't know the account, could be any. Search for it! self._load_history(jid, account) self.results_window.set_property('visible', False) def on_jid_entry_focus(self, widget, event): widget.select_region(0, -1) # select text def _load_history(self, jid_or_name, account=None): """ Load history for the given jid/name and show it """ if jid_or_name and jid_or_name in self.completion_dict: # a full qualified jid or a contact name was entered info_jid, info_account, info_name, info_completion = self.completion_dict[jid_or_name] self.jids_to_search = [info_jid] self.jid = info_jid if account: self.account = account else: self.account = info_account if self.account is None: # We don't know account. Probably a gc not opened or an # account not connected. # Disable possibility to say if we want to log or not self.checkbutton.set_sensitive(False) else: # Are log disabled for account ? if self.account in gajim.config.get_per('accounts', self.account, 'no_log_for').split(' '): self.checkbutton.set_active(False) self.checkbutton.set_sensitive(False) else: # Are log disabled for jid ? log = True if self.jid in gajim.config.get_per('accounts', self.account, 'no_log_for').split(' '): log = False self.checkbutton.set_active(log) self.checkbutton.set_sensitive(True) self.jids_to_search = [info_jid] # select logs for last date we have logs with contact self.calendar.set_sensitive(True) last_log = \ gajim.logger.get_last_date_that_has_logs(self.jid, self.account) date = time.localtime(last_log) y, m, d = date[0], date[1], date[2] gtk_month = gtkgui_helpers.make_python_month_gtk_month(m) self.calendar.select_month(gtk_month, y) self.calendar.select_day(d) self.search_entry.set_sensitive(True) self.search_entry.grab_focus() title = _('Conversation History with %s') % info_name self.window.set_title(title) self.jid_entry.set_text(info_completion) else: # neither a valid jid, nor an existing contact name was entered # we have got nothing to show or to search in self.jid = None self.account = None self.history_buffer.set_text('') # clear the buffer self.search_entry.set_sensitive(False) self.checkbutton.set_sensitive(False) self.calendar.set_sensitive(False) self.calendar.clear_marks() self.results_window.set_property('visible', False) title = _('Conversation History') self.window.set_title(title) def on_calendar_day_selected(self, widget): if not self.jid: return year, month, day = widget.get_date() # integers month = gtkgui_helpers.make_gtk_month_python_month(month) self._add_lines_for_date(year, month, day) def on_calendar_month_changed(self, widget): """ Ask for days in this month, if they have logs it bolds them (marks them) """ if not self.jid: return year, month, day = widget.get_date() # integers if year < 1900: widget.select_month(0, 1900) widget.select_day(1) return # in gtk January is 1, in python January is 0, # I want the second # first day of month is 1 not 0 widget.clear_marks() month = gtkgui_helpers.make_gtk_month_python_month(month) days_in_this_month = calendar.monthrange(year, month)[1] try: log_days = gajim.logger.get_days_with_logs(self.jid, year, month, days_in_this_month, self.account) except exceptions.PysqliteOperationalError as e: dialogs.ErrorDialog(_('Disk Error'), str(e)) return for day in log_days: widget.mark_day(day) def _get_string_show_from_constant_int(self, show): if show == constants.SHOW_ONLINE: show = 'online' elif show == constants.SHOW_CHAT: show = 'chat' elif show == constants.SHOW_AWAY: show = 'away' elif show == constants.SHOW_XA: show = 'xa' elif show == constants.SHOW_DND: show = 'dnd' elif show == constants.SHOW_OFFLINE: show = 'offline' return show def _add_lines_for_date(self, year, month, day): """ Add all the lines for given date in textbuffer """ self.history_buffer.set_text('') # clear the buffer first self.last_time_printout = 0 lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account) # lines holds list with tupples that have: # contact_name, time, kind, show, message for line in lines: # line[0] is contact_name, line[1] is time of message # line[2] is kind, line[3] is show, line[4] is message self._add_new_line(line[0], line[1], line[2], line[3], line[4], line[5]) def _add_new_line(self, contact_name, tim, kind, show, message, subject): """ Add a new line in textbuffer """ if not message and kind not in (constants.KIND_STATUS, constants.KIND_GCSTATUS): return buf = self.history_buffer end_iter = buf.get_end_iter() if gajim.config.get('print_time') == 'always': timestamp_str = gajim.config.get('time_stamp') timestamp_str = helpers.from_one_line(timestamp_str) tim = time.strftime(timestamp_str, time.localtime(float(tim))) buf.insert(end_iter, tim) # add time elif gajim.config.get('print_time') == 'sometimes': every_foo_seconds = 60 * gajim.config.get( 'print_ichat_every_foo_minutes') seconds_passed = tim - self.last_time_printout if seconds_passed > every_foo_seconds: self.last_time_printout = tim tim = time.strftime('%X ', time.localtime(float(tim))) buf.insert_with_tags_by_name(end_iter, tim + '\n', 'time_sometimes') tag_name = '' tag_msg = '' show = self._get_string_show_from_constant_int(show) if kind == constants.KIND_GC_MSG: tag_name = 'incoming' elif kind in (constants.KIND_SINGLE_MSG_RECV, constants.KIND_CHAT_MSG_RECV): contact_name = self.completion_dict[self.jid][C_INFO_NAME] tag_name = 'incoming' tag_msg = 'incomingtxt' elif kind in (constants.KIND_SINGLE_MSG_SENT, constants.KIND_CHAT_MSG_SENT): if self.account: contact_name = gajim.nicks[self.account] else: # we don't have roster, we don't know our own nick, use first # account one (urk!) account = gajim.contacts.get_accounts()[0] contact_name = gajim.nicks[account] tag_name = 'outgoing' tag_msg = 'outgoingtxt' elif kind == constants.KIND_GCSTATUS: # message here (if not None) is status message if message: message = _('%(nick)s is now %(status)s: %(status_msg)s') %\ {'nick': contact_name, 'status': helpers.get_uf_show(show), 'status_msg': message } else: message = _('%(nick)s is now %(status)s') % {'nick': contact_name, 'status': helpers.get_uf_show(show) } tag_msg = 'status' else: # 'status' # message here (if not None) is status message if show is None: # it means error if message: message = _('Error: %s') % message else: message = _('Error') elif message: message = _('Status is now: %(status)s: %(status_msg)s') % \ {'status': helpers.get_uf_show(show), 'status_msg': message} else: message = _('Status is now: %(status)s') % { 'status': helpers.get_uf_show(show) } tag_msg = 'status' if message.startswith('/me ') or message.startswith('/me\n'): tag_msg = tag_name else: # do not do this if gcstats, avoid dupping contact_name # eg. nkour: nkour is now Offline if contact_name and kind != constants.KIND_GCSTATUS: # add stuff before and after contact name before_str = gajim.config.get('before_nickname') before_str = helpers.from_one_line(before_str) after_str = gajim.config.get('after_nickname') after_str = helpers.from_one_line(after_str) format = before_str + contact_name + after_str + ' ' buf.insert_with_tags_by_name(end_iter, format, tag_name) if subject: message = _('Subject: %s\n') % subject + message xhtml = None if message.startswith('