root/tags/gajim-0.11.4/src/history_manager.py

Revision 9103, 21.0 kB (checked in by asterix, 9 months ago)

add windows specific stuff in sources

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2## history_manager.py
3##
4## Copyright (C) 2006 Nikos Kouremenos <kourem@gmail.com>
5##
6## This program is free software; you can redistribute it and/or modify
7## it under the terms of the GNU General Public License as published
8## by the Free Software Foundation; version 2 only.
9##
10## This program is distributed in the hope that it will be useful,
11## but WITHOUT ANY WARRANTY; without even the implied warranty of
12## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13## GNU General Public License for more details.
14##
15
16## NOTE: some method names may match those of logger.py but that's it
17## someday (TM) should have common class that abstracts db connections and helpers on it
18## the same can be said for history_window.py
19
20import os
21
22if os.name == 'nt':
23        import warnings
24        warnings.filterwarnings(action='ignore')
25
26# Used to create windows installer with GTK included
27#       paths = os.environ['PATH']
28#       list_ = paths.split(';')
29#       new_list = []
30#       for p in list_:
31#               if p.find('gtk') < 0 and p.find('GTK') < 0:
32#                       new_list.append(p)
33#       new_list.insert(0, 'gtk/lib')
34#       new_list.insert(0, 'gtk/bin')
35#       os.environ['PATH'] = ';'.join(new_list)
36#       os.environ['GTK_BASEPATH'] = 'gtk'
37
38import sys
39import signal
40import gtk
41import time
42import locale
43
44from common import i18n
45import exceptions
46import dialogs
47import gtkgui_helpers
48from common.logger import LOG_DB_PATH, constants
49
50#FIXME: constants should implement 2 way mappings
51status = dict((constants.__dict__[i], i[5:].lower()) for i in \
52        constants.__dict__.keys() if i.startswith('SHOW_'))
53from common import gajim
54from common import helpers
55
56# time, message, subject
57(
58C_UNIXTIME,
59C_MESSAGE,
60C_SUBJECT,
61C_NICKNAME
62) = range(2, 6)
63
64
65try:
66        import sqlite3 as sqlite # python 2.5
67except ImportError:
68        try:
69                from pysqlite2 import dbapi2 as sqlite
70        except ImportError:
71                raise exceptions.PysqliteNotAvailable
72
73
74class HistoryManager:
75        def __init__(self):
76                path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
77                pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
78                gtk.window_set_default_icon(pix) # set the icon to all newly opened windows
79               
80                if not os.path.exists(LOG_DB_PATH):
81                        dialogs.ErrorDialog(_('Cannot find history logs database'),
82                                '%s does not exist.' % LOG_DB_PATH)
83                        sys.exit()
84               
85                xml = gtkgui_helpers.get_glade('history_manager.glade')
86                self.window = xml.get_widget('history_manager_window')
87                self.jids_listview = xml.get_widget('jids_listview')
88                self.logs_listview = xml.get_widget('logs_listview')
89                self.search_results_listview = xml.get_widget('search_results_listview')
90                self.search_entry = xml.get_widget('search_entry')
91                self.logs_scrolledwindow = xml.get_widget('logs_scrolledwindow')
92                self.search_results_scrolledwindow = xml.get_widget(
93                        'search_results_scrolledwindow')
94                self.welcome_label = xml.get_widget('welcome_label')
95                       
96                self.logs_scrolledwindow.set_no_show_all(True)
97                self.search_results_scrolledwindow.set_no_show_all(True)
98               
99                self.jids_already_in = [] # holds jids that we already have in DB
100                self.AT_LEAST_ONE_DELETION_DONE = False
101               
102                self.con = sqlite.connect(LOG_DB_PATH, timeout = 20.0,
103                        isolation_level = 'IMMEDIATE')
104                self.cur = self.con.cursor()
105
106                self._init_jids_listview()
107                self._init_logs_listview()
108                self._init_search_results_listview()
109               
110                self._fill_jids_listview()
111               
112                self.search_entry.grab_focus()
113
114                self.window.show_all()
115               
116                xml.signal_autoconnect(self)
117       
118        def _init_jids_listview(self):
119                self.jids_liststore = gtk.ListStore(str, str) # jid, jid_id
120                self.jids_listview.set_model(self.jids_liststore)
121                self.jids_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
122
123                renderer_text = gtk.CellRendererText() # holds jid
124                col = gtk.TreeViewColumn(_('Contacts'), renderer_text, text = 0)
125                self.jids_listview.append_column(col)
126               
127                self.jids_listview.get_selection().connect('changed',
128                        self.on_jids_listview_selection_changed)
129
130        def _init_logs_listview(self):
131                # log_line_id (HIDDEN), jid_id (HIDDEN), time, message, subject, nickname
132                self.logs_liststore = gtk.ListStore(str, str, str, str, str, str)
133                self.logs_listview.set_model(self.logs_liststore)
134                self.logs_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
135
136                renderer_text = gtk.CellRendererText() # holds time
137                col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME)
138                col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
139                col.set_resizable(True)
140                self.logs_listview.append_column(col)
141               
142                renderer_text = gtk.CellRendererText() # holds nickname
143                col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME)
144                col.set_sort_column_id(C_NICKNAME) # user can click this header and sort
145                col.set_resizable(True)
146                col.set_visible(False)
147                self.nickname_col_for_logs = col
148                self.logs_listview.append_column(col)
149
150                renderer_text = gtk.CellRendererText() # holds message
151                col = gtk.TreeViewColumn(_('Message'), renderer_text, markup = C_MESSAGE)
152                col.set_sort_column_id(C_MESSAGE) # user can click this header and sort
153                col.set_resizable(True)
154                self.message_col_for_logs = col
155                self.logs_listview.append_column(col)
156
157                renderer_text = gtk.CellRendererText() # holds subject
158                col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT)
159                col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
160                col.set_resizable(True)
161                col.set_visible(False)
162                self.subject_col_for_logs = col
163                self.logs_listview.append_column(col)
164
165        def _init_search_results_listview(self):
166                # log_line_id (HIDDEN), jid, time, message, subject, nickname
167                self.search_results_liststore = gtk.ListStore(str, str, str, str, str, str)
168                self.search_results_listview.set_model(self.search_results_liststore)
169               
170                renderer_text = gtk.CellRendererText() # holds JID (who said this)
171                col = gtk.TreeViewColumn(_('JID'), renderer_text, text = 1)
172                col.set_sort_column_id(1) # user can click this header and sort
173                col.set_resizable(True)
174                self.search_results_listview.append_column(col)
175               
176                renderer_text = gtk.CellRendererText() # holds time
177                col = gtk.TreeViewColumn(_('Date'), renderer_text, text = C_UNIXTIME)
178                col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
179                col.set_resizable(True)
180                self.search_results_listview.append_column(col)
181
182                renderer_text = gtk.CellRendererText() # holds message
183                col = gtk.TreeViewColumn(_('Message'), renderer_text, text = C_MESSAGE)
184                col.set_sort_column_id(C_MESSAGE) # user can click this header and sort
185                col.set_resizable(True)
186                self.search_results_listview.append_column(col)
187
188                renderer_text = gtk.CellRendererText() # holds subject
189                col = gtk.TreeViewColumn(_('Subject'), renderer_text, text = C_SUBJECT)
190                col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
191                col.set_resizable(True)
192                self.search_results_listview.append_column(col)
193               
194                renderer_text = gtk.CellRendererText() # holds nickname
195                col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text = C_NICKNAME)
196                col.set_sort_column_id(C_NICKNAME) # user can click this header and sort
197                col.set_resizable(True)
198                self.search_results_listview.append_column(col)
199       
200        def on_history_manager_window_delete_event(self, widget, event):
201                if self.AT_LEAST_ONE_DELETION_DONE:
202                        dialog = dialogs.YesNoDialog(
203                                _('Do you want to clean up the database? '
204                                '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'),
205                                _('Normally allocated database size will not be freed, '
206                                        'it will just become reusable. If you really want to reduce '
207                                        'database filesize, click YES, else click NO.'
208                                        '\n\nIn case you click YES, please wait...'))
209                        if dialog.get_response() == gtk.RESPONSE_YES:
210                                self.cur.execute('VACUUM')
211                                self.con.commit()
212                                                       
213                gtk.main_quit()
214       
215        def _fill_jids_listview(self):
216                # get those jids that have at least one entry in logs
217                self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN (SELECT '
218                        'distinct logs.jid_id FROM logs) ORDER BY jid')
219                rows = self.cur.fetchall() # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
220                for row in rows:
221                        self.jids_already_in.append(row[0]) # jid
222                        self.jids_liststore.append(row) # jid, jid_id
223       
224        def on_jids_listview_selection_changed(self, widget, data = None):
225                liststore, list_of_paths = self.jids_listview.get_selection()\
226                        .get_selected_rows()
227                paths_len = len(list_of_paths)
228                if paths_len == 0: # nothing is selected
229                        return
230
231                self.logs_liststore.clear() # clear the store
232               
233                self.welcome_label.hide()
234                self.search_results_scrolledwindow.hide()
235                self.logs_scrolledwindow.show()
236
237                list_of_rowrefs = []
238                for path in list_of_paths: # make them treerowrefs (it's needed)
239                        list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
240               
241                for rowref in list_of_rowrefs: # FILL THE STORE, for all rows selected
242                        path = rowref.get_path()
243                        if path is None:
244                                continue
245                        jid = liststore[path][0] # jid
246                        self._fill_logs_listview(jid)
247       
248        def _get_jid_id(self, jid):
249                '''jids table has jid and jid_id
250                logs table has log_id, jid_id, contact_name, time, kind, show, message
251                so to ask logs we need jid_id that matches our jid in jids table
252                this method wants jid and returns the jid_id for later sql-ing on logs
253                '''
254                if jid.find('/') != -1: # if it has a /
255                        jid_is_from_pm = self._jid_is_from_pm(jid)
256                        if not jid_is_from_pm: # it's normal jid with resource
257                                jid = jid.split('/', 1)[0] # remove the resource
258                self.cur.execute('SELECT jid_id FROM jids WHERE jid = ?', (jid,))
259                jid_id = self.cur.fetchone()[0]
260                return str(jid_id)
261
262        def _get_jid_from_jid_id(self, jid_id):
263                '''jids table has jid and jid_id
264                this method accepts jid_id and returns the jid for later sql-ing on logs
265                '''
266                self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,))
267                jid = self.cur.fetchone()[0]
268                return jid
269
270        def _jid_is_from_pm(self, jid):
271                '''if jid is gajim@conf/nkour it's likely a pm one, how we know
272                gajim@conf is not a normal guy and nkour is not his resource?
273                we ask if gajim@conf is already in jids (with type room jid)
274                this fails if user disables logging for room and only enables for
275                pm (so higly unlikely) and if we fail we do not go chaos
276                (user will see the first pm as if it was message in room's public chat)
277                and after that all okay'''
278               
279                possible_room_jid, possible_nick = jid.split('/', 1)
280               
281                self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?',
282                        (possible_room_jid, constants.JID_ROOM_TYPE))
283                row = self.cur.fetchone()
284                if row is None:
285                        return False
286                else:
287                        return True
288
289        def _jid_is_room_type(self, jid):
290                '''returns True/False if given id is room type or not
291                eg. if it is room'''
292                self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,))
293                row = self.cur.fetchone()
294                if row is None:
295                        raise
296                elif row[0] == constants.JID_ROOM_TYPE:
297                        return True
298                else: # normal type
299                        return False
300       
301        def _fill_logs_listview(self, jid):
302                '''fill the listview with all messages that user sent to or
303                received from JID'''
304                # no need to lower jid in this context as jid is already lowered
305                # as we use those jids from db
306                jid_id = self._get_jid_id(jid)
307                self.cur.execute('''
308                        SELECT log_line_id, jid_id, time, kind, message, subject, contact_name, show
309                        FROM logs
310                        WHERE jid_id = ?
311                        ORDER BY time
312                        ''', (jid_id,))
313
314                results = self.cur.fetchall()
315               
316                if self._jid_is_room_type(jid): # is it room?
317                        self.nickname_col_for_logs.set_visible(True)
318                        self.subject_col_for_logs.set_visible(False)
319                else:
320                        self.nickname_col_for_logs.set_visible(False)
321                        self.subject_col_for_logs.set_visible(True)
322
323                for row in results:
324                        # exposed in UI (TreeViewColumns) are only
325                        # time, message, subject, nickname
326                        # but store in liststore
327                        # log_line_id, jid_id, time, message, subject, nickname
328                        log_line_id, jid_id, time_, kind, message, subject, nickname, show = row
329                        try:
330                                time_ = time.strftime('%x', time.localtime(float(time_))).decode(
331                                        locale.getpreferredencoding())
332                        except ValueError:
333                                pass
334                        else:
335                                color = None
336                                if kind in (constants.KIND_SINGLE_MSG_RECV,
337                                constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG):
338                                        # it is the other side
339                                        color = gajim.config.get('inmsgcolor') # so incoming color
340                                elif kind in (constants.KIND_SINGLE_MSG_SENT,
341                                constants.KIND_CHAT_MSG_SENT): # it is us
342                                        color = gajim.config.get('outmsgcolor') # so outgoing color
343                                elif kind in (constants.KIND_STATUS,
344                                constants.KIND_GCSTATUS): # is is statuses
345                                        color = gajim.config.get('statusmsgcolor') # so status color
346                                        # include status into (status) message
347                                        if message is None:
348                                                message = ''
349                                        else:
350                                                message = ' : ' + message
351                                        message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message
352
353                                message_ = '<span'
354                                if color:
355                                        message_ += ' foreground="%s"' % color
356                                message_ += '>%s</span>' % \
357                                        gtkgui_helpers.escape_for_pango_markup(message)
358                                self.logs_liststore.append((log_line_id, jid_id, time_, message_,
359                                        subject, nickname))
360
361        def _fill_search_results_listview(self, text):
362                '''ask db and fill listview with results that match text'''
363                self.search_results_liststore.clear()
364                like_sql = '%' + text + '%'
365                self.cur.execute('''
366                        SELECT log_line_id, jid_id, time, message, subject, contact_name
367                        FROM logs
368                        WHERE message LIKE ? OR subject LIKE ?
369                        ORDER BY time
370                        ''', (like_sql, like_sql))
371               
372                results = self.cur.fetchall()
373                for row in results:
374                        # exposed in UI (TreeViewColumns) are only
375                        # JID, time, message, subject, nickname
376                        # but store in liststore
377                        # log_line_id, jid (from jid_id), time, message, subject, nickname
378                        log_line_id, jid_id, time_, message, subject, nickname = row
379                        try:
380                                time_ = time.strftime('%x', time.localtime(float(time_))).decode(
381                                        locale.getpreferredencoding())
382                        except ValueError:
383                                pass
384                        else:
385                                jid = self._get_jid_from_jid_id(jid_id)
386                               
387                                self.search_results_liststore.append((log_line_id, jid, time_,
388                                        message, subject, nickname))
389
390        def on_logs_listview_key_press_event(self, widget, event):
391                liststore, list_of_paths = self.logs_listview.get_selection()\
392                        .get_selected_rows()
393                if event.keyval == gtk.keysyms.Delete:
394                        self._delete_logs(liststore, list_of_paths)
395                       
396        def on_listview_button_press_event(self, widget, event):
397                if event.button == 3: # right click
398                        xml = gtkgui_helpers.get_glade('history_manager.glade', 'context_menu')
399                        if widget.name != 'jids_listview':
400                                xml.get_widget('export_menuitem').hide()
401                        xml.get_widget('delete_menuitem').connect('activate',
402                                self.on_delete_menuitem_activate, widget)
403                       
404                        liststore, list_of_paths = self.jids_listview.get_selection()\
405                                .get_selected_rows()
406                       
407                        xml.signal_autoconnect(self)
408                        xml.get_widget('context_menu').popup(None, None, None,
409                                event.button, event.time)
410                        return True
411
412        def on_export_menuitem_activate(self, widget):
413                xml = gtkgui_helpers.get_glade('history_manager.glade', 'filechooserdialog')
414                xml.signal_autoconnect(self)
415               
416                dlg = xml.get_widget('filechooserdialog')
417                dlg.set_title(_('Exporting History Logs...'))
418                dlg.set_current_folder(gajim.HOME_DIR)
419                if gtk.pygtk_version > (2, 8, 0):
420                        dlg.props.do_overwrite_confirmation = True
421                response = dlg.run()
422               
423                if response == gtk.RESPONSE_OK: # user want us to export ;)
424                        liststore, list_of_paths = self.jids_listview.get_selection()\
425                                .get_selected_rows()
426                        path_to_file = dlg.get_filename()
427                        self._export_jids_logs_to_file(liststore, list_of_paths, path_to_file)
428               
429                dlg.destroy()   
430       
431        def on_delete_menuitem_activate(self, widget, listview):
432                liststore, list_of_paths = listview.get_selection().get_selected_rows()
433                if listview.name == 'jids_listview':
434                        self._delete_jid_logs(liststore, list_of_paths)
435                elif listview.name in ('logs_listview', 'search_results_listview'):
436                        self._delete_logs(liststore, list_of_paths)
437                else: # Huh ? We don't know this widget
438                        return
439
440        def on_jids_listview_key_press_event(self, widget, event):
441                liststore, list_of_paths = self.jids_listview.get_selection()\
442                        .get_selected_rows()
443                if event.keyval == gtk.keysyms.Delete:
444                        self._delete_jid_logs(liststore, list_of_paths)
445
446        def _export_jids_logs_to_file(self, liststore, list_of_paths, path_to_file):
447                paths_len = len(list_of_paths)
448                if paths_len == 0: # nothing is selected
449                        return
450
451                list_of_rowrefs = []
452                for path in list_of_paths: # make them treerowrefs (it's needed)
453                        list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
454               
455                for rowref in list_of_rowrefs:
456                        path = rowref.get_path()
457                        if path is None:
458                                continue
459                        jid_id = liststore[path][1]
460                        self.cur.execute('''
461                                SELECT time, kind, message, contact_name FROM logs
462                                WHERE jid_id = ?
463                                ORDER BY time
464                                ''', (jid_id,))
465
466                # FIXME: we may have two contacts selected to export. fix that
467                # AT THIS TIME FIRST EXECUTE IS LOST! WTH!!!!!
468                results = self.cur.fetchall()
469                #print results[0]
470                file_ = open(path_to_file, 'w')
471                for row in results:
472                        # in store: time, kind, message, contact_name FROM logs
473                        # in text: JID or You or nickname (if it's gc_msg), time, message
474                        time_, kind, message, nickname = row
475                        if kind in (constants.KIND_SINGLE_MSG_RECV,
476                                constants.KIND_CHAT_MSG_RECV):
477                                who = self._get_jid_from_jid_id(jid_id)
478                        elif kind in (constants.KIND_SINGLE_MSG_SENT,
479                                constants.KIND_CHAT_MSG_SENT):
480                                who = _('You')
481                        elif kind == constants.KIND_GC_MSG:
482                                who = nickname
483                        else: # status or gc_status. do not save
484                                #print kind
485                                continue
486
487                        try:
488                                time_ = time.strftime('%x', time.localtime(float(time_))).decode(
489                                        locale.getpreferredencoding())
490                        except ValueError:
491                                pass
492
493                        file_.write(_('%(who)s on %(time)s said: %(message)s\n') % {'who': who,
494                                'time': time_, 'message': message})
495       
496        def _delete_jid_logs(self, liststore, list_of_paths):
497                paths_len = len(list_of_paths)
498                if paths_len == 0: # nothing is selected
499                        return
500
501                def on_ok(widget, liststore, list_of_paths):
502                        # delete all rows from db that match jid_id
503                        self.dialog.destroy()
504                        list_of_rowrefs = []
505                        for path in list_of_paths: # make them treerowrefs (it's needed)
506                                list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
507
508                        for rowref in list_of_rowrefs:
509                                path = rowref.get_path()
510                                if path is None:
511                                        continue
512                                jid_id = liststore[path][1]
513                                del liststore[path] # remove from UI
514                                # remove from db
515                                self.cur.execute('''
516                                        DELETE FROM logs
517                                        WHERE jid_id = ?
518                                        ''', (jid_id,))
519
520                                # now delete "jid, jid_id" row from jids table
521                                self.cur.execute('''
522                                                DELETE FROM jids
523                                                WHERE jid_id = ?
524                                                ''', (jid_id,))
525
526                        self.con.commit()
527
528                        self.AT_LEAST_ONE_DELETION_DONE = True
529
530                pri_text = i18n.ngettext(
531                        'Do you really want to delete logs of the selected contact?',
532                        'Do you really want to delete logs of the selected contacts?',
533                        paths_len)
534                self.dialog = dialogs.ConfirmationDialog(pri_text,
535                        _('This is an irreversible operation.'), on_response_ok = (on_ok,
536                        liststore, list_of_paths))
537
538        def _delete_logs(self, liststore, list_of_paths):
539                paths_len = len(list_of_paths)
540                if paths_len == 0: # nothing is selected
541                        return
542
543                def on_ok(widget, liststore, list_of_paths):
544                        self.dialog.destroy()
545                        # delete rows from db that match log_line_id
546                        list_of_rowrefs = []
547                        for path in list_of_paths: # make them treerowrefs (it's needed)
548                                list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
549
550                        for rowref in list_of_rowrefs:
551                                path = rowref.get_path()
552                                if path is None:
553                                        continue
554                                log_line_id = liststore[path][0]
555                                del liststore[path] # remove from UI
556                                # remove from db
557                                self.cur.execute('''
558                                        DELETE FROM logs
559                                        WHERE log_line_id = ?
560                                        ''', (log_line_id,))
561
562                        self.con.commit()
563
564                        self.AT_LEAST_ONE_DELETION_DONE = True
565
566                       
567                pri_text = i18n.ngettext(
568                        'Do you really want to delete the selected message?',
569                        'Do you really want to delete the selected messages?', paths_len)
570                self.dialog = dialogs.ConfirmationDialog(pri_text,
571                        _('This is an irreversible operation.'), on_response_ok = (on_ok,
572                        liststore, list_of_paths))
573
574        def on_search_db_button_clicked(self, widget):
575                text = self.search_entry.get_text()
576                if text == '':
577                        return
578
579                self.welcome_label.hide()
580                self.logs_scrolledwindow.hide()
581                self.search_results_scrolledwindow.show()
582               
583                self._fill_search_results_listview(text)
584
585        def on_search_results_listview_row_activated(self, widget, path, column):
586                # get log_line_id, jid_id from row we double clicked
587                log_line_id = self.search_results_liststore[path][0]
588                jid = self.search_results_liststore[path][1]
589                # make it string as in gtk liststores I have them all as strings
590                # as this is what db returns so I don't have to fight with types
591                jid_id = self._get_jid_id(jid)
592               
593               
594                iter_ = self.jids_liststore.get_iter_root()
595                while iter_:
596                        # self.jids_liststore[iter_][1] holds jid_ids
597                        if self.jids_liststore[iter_][1] == jid_id:
598                                break
599                        iter_ = self.jids_liststore.iter_next(iter_)
600               
601                if iter_ is None:
602                        return
603
604                path = self.jids_liststore.get_path(iter_)
605                self.jids_listview.set_cursor(path)
606               
607                iter_ = self.logs_liststore.get_iter_root()
608                while iter_:
609                        # self.logs_liststore[iter_][0] holds lon_line_ids
610                        if self.logs_liststore[iter_][0] == log_line_id:
611                                break
612                        iter_ = self.logs_liststore.iter_next(iter_)
613               
614                path = self.logs_liststore.get_path(iter_)
615                self.logs_listview.scroll_to_cell(path)
616
617if __name__ == '__main__':
618        signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
619        HistoryManager()
620        gtk.main()
Note: See TracBrowser for help on using the browser.