From b0bf74337c66a5a20d18380f5629d32c2106397a Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 29 Jan 2018 14:00:15 +0100 Subject: [PATCH] Add navigation for chat history --- gajim/common/logger.py | 58 +++++++++++++++++++ gajim/data/gui/history_window.ui | 99 +++++++++++++++++++++++++++++++- gajim/history_window.py | 88 +++++++++++++++++++++++++--- 3 files changed, 233 insertions(+), 12 deletions(-) diff --git a/gajim/common/logger.py b/gajim/common/logger.py index cb2b3a2de..5cadd2518 100644 --- a/gajim/common/logger.py +++ b/gajim/common/logger.py @@ -732,6 +732,64 @@ class Logger: # attributes set to None because of the MAX() function return self.con.execute(sql, tuple(jids)).fetchone().time + def get_first_date_that_has_logs(self, account, jid): + """ + Get the timestamp of the first message we received for the jid. + + :param account: The account + + :param jid: The jid for which we request the first timestamp + + returns a timestamp or None + """ + jids = self._get_family_jids(account, jid) + + kinds = map(str, [KindConstant.STATUS, + KindConstant.GCSTATUS]) + + sql = ''' + SELECT MIN(time) as time FROM logs + NATURAL JOIN jids WHERE jid IN ({jids}) + AND kind NOT IN ({kinds}) + '''.format(jids=', '.join('?' * len(jids)), + kinds=', '.join(kinds)) + + # fetchone() returns always at least one Row with all + # attributes set to None because of the MIN() function + return self.con.execute(sql, tuple(jids)).fetchone().time + + def get_date_has_logs(self, account, jid, date): + """ + Get single timestamp of a message we received for the jid + in the time range of one day. + + :param account: The account + + :param jid: The jid for which we request the first timestamp + + :param date: datetime.datetime instance + example: datetime.datetime(year, month, day) + + returns a timestamp or None + """ + jids = self._get_family_jids(account, jid) + + kinds = map(str, [KindConstant.STATUS, + KindConstant.GCSTATUS]) + + delta = datetime.timedelta( + hours=23, minutes=59, seconds=59, microseconds=999999) + + sql = ''' + SELECT time + FROM logs NATURAL JOIN jids WHERE jid IN ({jids}) + AND time BETWEEN ? AND ? + '''.format(jids=', '.join('?' * len(jids))) + + return self.con.execute(sql, tuple(jids) + + (date.timestamp(), + (date + delta).timestamp())).fetchone() + def get_room_last_message_time(self, account, jid): """ Get the timestamp of the last message we received in a room. diff --git a/gajim/data/gui/history_window.ui b/gajim/data/gui/history_window.ui index b1911ca7d..65e400157 100644 --- a/gajim/data/gui/history_window.ui +++ b/gajim/data/gui/history_window.ui @@ -1,5 +1,5 @@ - + @@ -35,7 +35,7 @@ True - Enter name / JID of contact or groupchat + Enter name / JID of contact or groupchat True @@ -102,11 +102,13 @@ True True + 5 165 True False + 5 vertical 6 @@ -123,7 +125,98 @@ - + + True + False + center + 5 + expand + + + True + True + True + + + + True + False + go-first-symbolic + + + + + True + True + 0 + + + + + True + True + True + + + + True + False + go-previous-symbolic + + + + + True + True + 1 + + + + + True + True + True + + + + True + False + go-next-symbolic + + + + + True + True + 2 + + + + + True + True + True + + + + True + False + go-last-symbolic + + + + + True + True + 3 + + + + + False + True + 2 + diff --git a/gajim/history_window.py b/gajim/history_window.py index 7823044dc..30026e7f0 100644 --- a/gajim/history_window.py +++ b/gajim/history_window.py @@ -71,6 +71,10 @@ class HistoryWindow: self.window = xml.get_object('history_window') self.window.set_application(app.app) self.calendar = xml.get_object('calendar') + self.button_first_day = xml.get_object('button_first_day') + self.button_previous_day = xml.get_object('button_previous_day') + self.button_next_day = xml.get_object('button_next_day') + self.button_last_day = xml.get_object('button_last_day') scrolledwindow = xml.get_object('scrolledwindow') self.history_textview = conversation_textview.ConversationTextview( account, used_in_history_window = True) @@ -311,17 +315,25 @@ class HistoryWindow: self.jids_to_search = [info_jid] - # select logs for last date we have logs with contact + # Get first/last date we have logs with contact (for log navigation) + self.first_log = app.logger.get_first_date_that_has_logs( + self.account, self.jid) + self.first_day = self._get_date_from_timestamp(self.first_log) + self.last_log = app.logger.get_last_date_that_has_logs( + self.account, self.jid) + self.last_day = self._get_date_from_timestamp(self.last_log) + + # Select logs for last date we have logs with contact self.calendar.set_sensitive(True) - last_log = \ - app.logger.get_last_date_that_has_logs(self.account, self.jid) + gtk_month = gtkgui_helpers.make_python_month_gtk_month( + self.last_day.month) + self.calendar.select_month(gtk_month, self.last_day.year) + self.calendar.select_day(self.last_day.day) - 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.button_previous_day.set_sensitive(True) + self.button_next_day.set_sensitive(True) + self.button_first_day.set_sensitive(True) + self.button_last_day.set_sensitive(True) self.search_entry.set_sensitive(True) self.search_entry.grab_focus() @@ -341,6 +353,10 @@ class HistoryWindow: self.checkbutton.set_sensitive(False) self.calendar.set_sensitive(False) self.calendar.clear_marks() + self.button_previous_day.set_sensitive(False) + self.button_next_day.set_sensitive(False) + self.button_first_day.set_sensitive(False) + self.button_last_day.set_sensitive(False) self.results_window.set_property('visible', False) @@ -379,6 +395,60 @@ class HistoryWindow: for date in log_days: widget.mark_day(date.day) + def _get_date_from_timestamp(self, timestamp): + # Conversion from timestamp to date + log = time.localtime(timestamp) + y, m, d = log[0], log[1], log[2] + date = datetime.datetime(y, m, d) + return(date) + + def _change_date(self, widget): + # Get day selected in calendar + y, m, d = self.calendar.get_date() + py_m = gtkgui_helpers.make_gtk_month_python_month(m) + _date = datetime.datetime(y, py_m, d) + + if widget is self.button_first_day: + gtk_m = gtkgui_helpers.make_python_month_gtk_month( + self.first_day.month) + self.calendar.select_month(gtk_m, self.first_day.year) + self.calendar.select_day(self.first_day.day) + return + elif widget is self.button_last_day: + gtk_m = gtkgui_helpers.make_python_month_gtk_month( + self.last_day.month) + self.calendar.select_month(gtk_m, self.last_day.year) + self.calendar.select_day(self.last_day.day) + return + elif widget is self.button_previous_day: + end_date = self.first_day + timedelta = datetime.timedelta(days=-1) + if end_date >= _date: + return + elif widget is self.button_next_day: + end_date = self.last_day + timedelta = datetime.timedelta(days=1) + if end_date <= _date: + return + + # Iterate through days until log entry found or + # supplied end_date (first_log / last_log) reached + logs = None + while logs is None: + _date = _date + timedelta + if _date == end_date: + break + try: + logs = app.logger.get_date_has_logs( + self.account, self.jid, _date) + except exceptions.PysqliteOperationalError as e: + dialogs.ErrorDialog(_('Disk Error'), str(e)) + return + + gtk_month = gtkgui_helpers.make_python_month_gtk_month(_date.month) + self.calendar.select_month(gtk_month, _date.year) + self.calendar.select_day(_date.day) + def _get_string_show_from_constant_int(self, show): if show == ShowConstant.ONLINE: show = 'online'