Merge branch 'history-navigation' into 'master'
Add navigation for chat history See merge request gajim/gajim!202
This commit is contained in:
		
						commit
						7b1bdc5591
					
				
					 3 changed files with 233 additions and 12 deletions
				
			
		| 
						 | 
				
			
			@ -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.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<!-- Generated with glade 3.20.0 -->
 | 
			
		||||
<!-- Generated with glade 3.20.2 -->
 | 
			
		||||
<interface>
 | 
			
		||||
  <requires lib="gtk+" version="3.20"/>
 | 
			
		||||
  <object class="GtkApplicationWindow" id="history_window">
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +35,7 @@
 | 
			
		|||
                  <object class="GtkEntry" id="query_entry">
 | 
			
		||||
                    <property name="can_focus">True</property>
 | 
			
		||||
                    <property name="invisible_char">●</property>
 | 
			
		||||
                    <property name="placeholder_text">Enter name / JID of contact or groupchat</property>
 | 
			
		||||
                    <property name="placeholder_text" translatable="yes">Enter name / JID of contact or groupchat</property>
 | 
			
		||||
                  </object>
 | 
			
		||||
                  <packing>
 | 
			
		||||
                    <property name="expand">True</property>
 | 
			
		||||
| 
						 | 
				
			
			@ -102,11 +102,13 @@
 | 
			
		|||
                  <object class="GtkPaned" id="hpaned">
 | 
			
		||||
                    <property name="visible">True</property>
 | 
			
		||||
                    <property name="can_focus">True</property>
 | 
			
		||||
                    <property name="margin_bottom">5</property>
 | 
			
		||||
                    <property name="position">165</property>
 | 
			
		||||
                    <child>
 | 
			
		||||
                      <object class="GtkBox" id="vbox2">
 | 
			
		||||
                        <property name="visible">True</property>
 | 
			
		||||
                        <property name="can_focus">False</property>
 | 
			
		||||
                        <property name="margin_right">5</property>
 | 
			
		||||
                        <property name="orientation">vertical</property>
 | 
			
		||||
                        <property name="spacing">6</property>
 | 
			
		||||
                        <child>
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +125,98 @@
 | 
			
		|||
                          </packing>
 | 
			
		||||
                        </child>
 | 
			
		||||
                        <child>
 | 
			
		||||
                          <placeholder/>
 | 
			
		||||
                          <object class="GtkButtonBox">
 | 
			
		||||
                            <property name="visible">True</property>
 | 
			
		||||
                            <property name="can_focus">False</property>
 | 
			
		||||
                            <property name="halign">center</property>
 | 
			
		||||
                            <property name="spacing">5</property>
 | 
			
		||||
                            <property name="layout_style">expand</property>
 | 
			
		||||
                            <child>
 | 
			
		||||
                              <object class="GtkButton" id="button_first_day">
 | 
			
		||||
                                <property name="visible">True</property>
 | 
			
		||||
                                <property name="can_focus">True</property>
 | 
			
		||||
                                <property name="receives_default">True</property>
 | 
			
		||||
                                <signal name="clicked" handler="_change_date" swapped="no"/>
 | 
			
		||||
                                <child>
 | 
			
		||||
                                  <object class="GtkImage">
 | 
			
		||||
                                    <property name="visible">True</property>
 | 
			
		||||
                                    <property name="can_focus">False</property>
 | 
			
		||||
                                    <property name="icon_name">go-first-symbolic</property>
 | 
			
		||||
                                  </object>
 | 
			
		||||
                                </child>
 | 
			
		||||
                              </object>
 | 
			
		||||
                              <packing>
 | 
			
		||||
                                <property name="expand">True</property>
 | 
			
		||||
                                <property name="fill">True</property>
 | 
			
		||||
                                <property name="position">0</property>
 | 
			
		||||
                              </packing>
 | 
			
		||||
                            </child>
 | 
			
		||||
                            <child>
 | 
			
		||||
                              <object class="GtkButton" id="button_previous_day">
 | 
			
		||||
                                <property name="visible">True</property>
 | 
			
		||||
                                <property name="can_focus">True</property>
 | 
			
		||||
                                <property name="receives_default">True</property>
 | 
			
		||||
                                <signal name="clicked" handler="_change_date" swapped="no"/>
 | 
			
		||||
                                <child>
 | 
			
		||||
                                  <object class="GtkImage">
 | 
			
		||||
                                    <property name="visible">True</property>
 | 
			
		||||
                                    <property name="can_focus">False</property>
 | 
			
		||||
                                    <property name="icon_name">go-previous-symbolic</property>
 | 
			
		||||
                                  </object>
 | 
			
		||||
                                </child>
 | 
			
		||||
                              </object>
 | 
			
		||||
                              <packing>
 | 
			
		||||
                                <property name="expand">True</property>
 | 
			
		||||
                                <property name="fill">True</property>
 | 
			
		||||
                                <property name="position">1</property>
 | 
			
		||||
                              </packing>
 | 
			
		||||
                            </child>
 | 
			
		||||
                            <child>
 | 
			
		||||
                              <object class="GtkButton" id="button_next_day">
 | 
			
		||||
                                <property name="visible">True</property>
 | 
			
		||||
                                <property name="can_focus">True</property>
 | 
			
		||||
                                <property name="receives_default">True</property>
 | 
			
		||||
                                <signal name="clicked" handler="_change_date" swapped="no"/>
 | 
			
		||||
                                <child>
 | 
			
		||||
                                  <object class="GtkImage">
 | 
			
		||||
                                    <property name="visible">True</property>
 | 
			
		||||
                                    <property name="can_focus">False</property>
 | 
			
		||||
                                    <property name="icon_name">go-next-symbolic</property>
 | 
			
		||||
                                  </object>
 | 
			
		||||
                                </child>
 | 
			
		||||
                              </object>
 | 
			
		||||
                              <packing>
 | 
			
		||||
                                <property name="expand">True</property>
 | 
			
		||||
                                <property name="fill">True</property>
 | 
			
		||||
                                <property name="position">2</property>
 | 
			
		||||
                              </packing>
 | 
			
		||||
                            </child>
 | 
			
		||||
                            <child>
 | 
			
		||||
                              <object class="GtkButton" id="button_last_day">
 | 
			
		||||
                                <property name="visible">True</property>
 | 
			
		||||
                                <property name="can_focus">True</property>
 | 
			
		||||
                                <property name="receives_default">True</property>
 | 
			
		||||
                                <signal name="clicked" handler="_change_date" swapped="no"/>
 | 
			
		||||
                                <child>
 | 
			
		||||
                                  <object class="GtkImage">
 | 
			
		||||
                                    <property name="visible">True</property>
 | 
			
		||||
                                    <property name="can_focus">False</property>
 | 
			
		||||
                                    <property name="icon_name">go-last-symbolic</property>
 | 
			
		||||
                                  </object>
 | 
			
		||||
                                </child>
 | 
			
		||||
                              </object>
 | 
			
		||||
                              <packing>
 | 
			
		||||
                                <property name="expand">True</property>
 | 
			
		||||
                                <property name="fill">True</property>
 | 
			
		||||
                                <property name="position">3</property>
 | 
			
		||||
                              </packing>
 | 
			
		||||
                            </child>
 | 
			
		||||
                          </object>
 | 
			
		||||
                          <packing>
 | 
			
		||||
                            <property name="expand">False</property>
 | 
			
		||||
                            <property name="fill">True</property>
 | 
			
		||||
                            <property name="position">2</property>
 | 
			
		||||
                          </packing>
 | 
			
		||||
                        </child>
 | 
			
		||||
                      </object>
 | 
			
		||||
                      <packing>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			@ -309,17 +313,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()
 | 
			
		||||
| 
						 | 
				
			
			@ -339,6 +351,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)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -377,6 +393,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'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue