set transient for roster windows to error / warning dialogs. Fixes #6942
[gajim.git] / src / history_manager.py
blob9e7f57ec1e29448e62f985064fdfff9440690e7c
1 # -*- coding:utf-8 -*-
2 ## src/history_manager.py
3 ##
4 ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
5 ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
6 ## Nikos Kouremenos <kourem AT gmail.com>
7 ## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org>
8 ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
9 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
11 ## This file is part of Gajim.
13 ## Gajim is free software; you can redistribute it and/or modify
14 ## it under the terms of the GNU General Public License as published
15 ## by the Free Software Foundation; version 3 only.
17 ## Gajim is distributed in the hope that it will be useful,
18 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ## GNU General Public License for more details.
22 ## You should have received a copy of the GNU General Public License
23 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
26 ## NOTE: some method names may match those of logger.py but that's it
27 ## someday (TM) should have common class
28 ## that abstracts db connections and helpers on it
29 ## the same can be said for history_window.py
31 import os
33 if os.name == 'nt':
34 import warnings
35 warnings.filterwarnings(action='ignore')
37 if os.path.isdir('gtk'):
38 # Used to create windows installer with GTK included
39 paths = os.environ['PATH']
40 list_ = paths.split(';')
41 new_list = []
42 for p in list_:
43 if p.find('gtk') < 0 and p.find('GTK') < 0:
44 new_list.append(p)
45 new_list.insert(0, 'gtk/lib')
46 new_list.insert(0, 'gtk/bin')
47 os.environ['PATH'] = ';'.join(new_list)
48 os.environ['GTK_BASEPATH'] = 'gtk'
50 import sys
51 import signal
52 import gtk
53 import gobject
54 import time
55 import locale
57 import getopt
58 from common import i18n
60 def parseOpts():
61 config_path = None
63 try:
64 shortargs = 'hc:'
65 longargs = 'help config_path='
66 opts = getopt.getopt(sys.argv[1:], shortargs, longargs.split())[0]
67 except getopt.error, msg:
68 print str(msg)
69 print 'for help use --help'
70 sys.exit(2)
71 for o, a in opts:
72 if o in ('-h', '--help'):
73 print 'history_manager [--help] [--config-path]'
74 sys.exit()
75 elif o in ('-c', '--config-path'):
76 config_path = a
77 return config_path
79 config_path = parseOpts()
80 del parseOpts
82 import common.configpaths
83 common.configpaths.gajimpaths.init(config_path)
84 del config_path
85 common.configpaths.gajimpaths.init_profile()
86 from common import exceptions
87 from common import gajim
88 import gtkgui_helpers
89 from common.logger import LOG_DB_PATH, constants
91 #FIXME: constants should implement 2 way mappings
92 status = dict((constants.__dict__[i], i[5:].lower()) for i in \
93 constants.__dict__.keys() if i.startswith('SHOW_'))
94 from common import helpers
95 import dialogs
97 # time, message, subject
99 C_UNIXTIME,
100 C_MESSAGE,
101 C_SUBJECT,
102 C_NICKNAME
103 ) = range(2, 6)
106 import sqlite3 as sqlite
109 class HistoryManager:
110 def __init__(self):
111 pix = gtkgui_helpers.get_icon_pixmap('gajim')
112 # set the icon to all newly opened windows
113 gtk.window_set_default_icon(pix)
115 if not os.path.exists(LOG_DB_PATH):
116 dialogs.ErrorDialog(_('Cannot find history logs database'),
117 '%s does not exist.' % LOG_DB_PATH)
118 sys.exit()
120 xml = gtkgui_helpers.get_gtk_builder('history_manager.ui')
121 self.window = xml.get_object('history_manager_window')
122 self.jids_listview = xml.get_object('jids_listview')
123 self.logs_listview = xml.get_object('logs_listview')
124 self.search_results_listview = xml.get_object('search_results_listview')
125 self.search_entry = xml.get_object('search_entry')
126 self.logs_scrolledwindow = xml.get_object('logs_scrolledwindow')
127 self.search_results_scrolledwindow = xml.get_object(
128 'search_results_scrolledwindow')
129 self.welcome_vbox = xml.get_object('welcome_vbox')
131 self.jids_already_in = [] # holds jids that we already have in DB
132 self.AT_LEAST_ONE_DELETION_DONE = False
134 self.con = sqlite.connect(LOG_DB_PATH, timeout=20.0,
135 isolation_level='IMMEDIATE')
136 self.cur = self.con.cursor()
138 self._init_jids_listview()
139 self._init_logs_listview()
140 self._init_search_results_listview()
142 self._fill_jids_listview()
144 self.search_entry.grab_focus()
146 self.window.show_all()
148 xml.connect_signals(self)
150 def _init_jids_listview(self):
151 self.jids_liststore = gtk.ListStore(str, str) # jid, jid_id
152 self.jids_listview.set_model(self.jids_liststore)
153 self.jids_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
155 renderer_text = gtk.CellRendererText() # holds jid
156 col = gtk.TreeViewColumn(_('Contacts'), renderer_text, text=0)
157 self.jids_listview.append_column(col)
159 self.jids_listview.get_selection().connect('changed',
160 self.on_jids_listview_selection_changed)
162 def _init_logs_listview(self):
163 # log_line_id(HIDDEN), jid_id(HIDDEN), time, message, subject, nickname
164 self.logs_liststore = gtk.ListStore(str, str, str, str, str, str)
165 self.logs_listview.set_model(self.logs_liststore)
166 self.logs_listview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
168 renderer_text = gtk.CellRendererText() # holds time
169 col = gtk.TreeViewColumn(_('Date'), renderer_text, text=C_UNIXTIME)
170 # user can click this header and sort
171 col.set_sort_column_id(C_UNIXTIME)
172 col.set_resizable(True)
173 self.logs_listview.append_column(col)
175 renderer_text = gtk.CellRendererText() # holds nickname
176 col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text=C_NICKNAME)
177 # user can click this header and sort
178 col.set_sort_column_id(C_NICKNAME)
179 col.set_resizable(True)
180 col.set_visible(False)
181 self.nickname_col_for_logs = col
182 self.logs_listview.append_column(col)
184 renderer_text = gtk.CellRendererText() # holds message
185 col = gtk.TreeViewColumn(_('Message'), renderer_text, markup=C_MESSAGE)
186 # user can click this header and sort
187 col.set_sort_column_id(C_MESSAGE)
188 col.set_resizable(True)
189 self.message_col_for_logs = col
190 self.logs_listview.append_column(col)
192 renderer_text = gtk.CellRendererText() # holds subject
193 col = gtk.TreeViewColumn(_('Subject'), renderer_text, text=C_SUBJECT)
194 col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
195 col.set_resizable(True)
196 col.set_visible(False)
197 self.subject_col_for_logs = col
198 self.logs_listview.append_column(col)
200 def _init_search_results_listview(self):
201 # log_line_id (HIDDEN), jid, time, message, subject, nickname
202 self.search_results_liststore = gtk.ListStore(str, str, str, str, str,
203 str)
204 self.search_results_listview.set_model(self.search_results_liststore)
206 renderer_text = gtk.CellRendererText() # holds JID (who said this)
207 col = gtk.TreeViewColumn(_('JID'), renderer_text, text=1)
208 col.set_sort_column_id(1) # user can click this header and sort
209 col.set_resizable(True)
210 self.search_results_listview.append_column(col)
212 renderer_text = gtk.CellRendererText() # holds time
213 col = gtk.TreeViewColumn(_('Date'), renderer_text, text=C_UNIXTIME)
214 # user can click this header and sort
215 col.set_sort_column_id(C_UNIXTIME)
216 col.set_resizable(True)
217 self.search_results_listview.append_column(col)
219 renderer_text = gtk.CellRendererText() # holds message
220 col = gtk.TreeViewColumn(_('Message'), renderer_text, text=C_MESSAGE)
221 col.set_sort_column_id(C_MESSAGE) # user can click this header and sort
222 col.set_resizable(True)
223 self.search_results_listview.append_column(col)
225 renderer_text = gtk.CellRendererText() # holds subject
226 col = gtk.TreeViewColumn(_('Subject'), renderer_text, text=C_SUBJECT)
227 col.set_sort_column_id(C_SUBJECT) # user can click this header and sort
228 col.set_resizable(True)
229 self.search_results_listview.append_column(col)
231 renderer_text = gtk.CellRendererText() # holds nickname
232 col = gtk.TreeViewColumn(_('Nickname'), renderer_text, text=C_NICKNAME)
233 # user can click this header and sort
234 col.set_sort_column_id(C_NICKNAME)
235 col.set_resizable(True)
236 self.search_results_listview.append_column(col)
238 def on_history_manager_window_delete_event(self, widget, event):
239 if not self.AT_LEAST_ONE_DELETION_DONE:
240 gtk.main_quit()
241 return
243 def on_yes(clicked):
244 self.cur.execute('VACUUM')
245 self.con.commit()
246 gtk.main_quit()
248 def on_no():
249 gtk.main_quit()
251 dialog = dialogs.YesNoDialog(
252 _('Do you want to clean up the database? '
253 '(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'),
254 _('Normally allocated database size will not be freed, '
255 'it will just become reusable. If you really want to reduce '
256 'database filesize, click YES, else click NO.'
257 '\n\nIn case you click YES, please wait...'),
258 on_response_yes=on_yes, on_response_no=on_no)
259 dialog.set_title(_('Database Cleanup'))
260 button_box = dialog.get_children()[0].get_children()[1]
261 button_box.get_children()[0].grab_focus()
263 def _fill_jids_listview(self):
264 # get those jids that have at least one entry in logs
265 self.cur.execute('SELECT jid, jid_id FROM jids WHERE jid_id IN ('
266 'SELECT distinct logs.jid_id FROM logs) ORDER BY jid')
267 # list of tupples: [(u'aaa@bbb',), (u'cc@dd',)]
268 rows = self.cur.fetchall()
269 for row in rows:
270 self.jids_already_in.append(row[0]) # jid
271 self.jids_liststore.append(row) # jid, jid_id
273 def on_jids_listview_selection_changed(self, widget, data=None):
274 liststore, list_of_paths = self.jids_listview.get_selection()\
275 .get_selected_rows()
276 paths_len = len(list_of_paths)
277 if paths_len == 0: # nothing is selected
278 return
280 self.logs_liststore.clear() # clear the store
282 self.welcome_vbox.hide()
283 self.search_results_scrolledwindow.hide()
284 self.logs_scrolledwindow.show()
286 list_of_rowrefs = []
287 for path in list_of_paths: # make them treerowrefs (it's needed)
288 list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
290 for rowref in list_of_rowrefs: # FILL THE STORE, for all rows selected
291 path = rowref.get_path()
292 if path is None:
293 continue
294 jid = liststore[path][0].decode('utf-8') # jid
295 self._fill_logs_listview(jid)
297 def _get_jid_id(self, jid):
299 jids table has jid and jid_id
300 logs table has log_id, jid_id, contact_name, time, kind, show, message
302 So to ask logs we need jid_id that matches our jid in jids table this
303 method wants jid and returns the jid_id for later sql-ing on logs
305 if jid.find('/') != -1: # if it has a /
306 jid_is_from_pm = self._jid_is_from_pm(jid)
307 if not jid_is_from_pm: # it's normal jid with resource
308 jid = jid.split('/', 1)[0] # remove the resource
309 self.cur.execute('SELECT jid_id FROM jids WHERE jid = ?', (jid,))
310 jid_id = self.cur.fetchone()[0]
311 return str(jid_id)
313 def _get_jid_from_jid_id(self, jid_id):
315 jids table has jid and jid_id
317 This method accepts jid_id and returns the jid for later sql-ing on logs
319 self.cur.execute('SELECT jid FROM jids WHERE jid_id = ?', (jid_id,))
320 jid = self.cur.fetchone()[0]
321 return jid
323 def _jid_is_from_pm(self, jid):
325 If jid is gajim@conf/nkour it's likely a pm one, how we know gajim@conf
326 is not a normal guy and nkour is not his resource? We ask if gajim@conf
327 is already in jids (with type room jid). This fails if user disables
328 logging for room and only enables for pm (so higly unlikely) and if we
329 fail we do not go chaos (user will see the first pm as if it was message
330 in room's public chat) and after that everything is ok
332 possible_room_jid = jid.split('/', 1)[0]
334 self.cur.execute('SELECT jid_id FROM jids WHERE jid = ? AND type = ?',
335 (possible_room_jid, constants.JID_ROOM_TYPE))
336 row = self.cur.fetchone()
337 if row is None:
338 return False
339 else:
340 return True
342 def _jid_is_room_type(self, jid):
344 Return True/False if given id is room type or not eg. if it is room
346 self.cur.execute('SELECT type FROM jids WHERE jid = ?', (jid,))
347 row = self.cur.fetchone()
348 if row is None:
349 raise
350 elif row[0] == constants.JID_ROOM_TYPE:
351 return True
352 else: # normal type
353 return False
355 def _fill_logs_listview(self, jid):
357 Fill the listview with all messages that user sent to or received from
360 # no need to lower jid in this context as jid is already lowered
361 # as we use those jids from db
362 jid_id = self._get_jid_id(jid)
363 self.cur.execute('''
364 SELECT log_line_id, jid_id, time, kind, message, subject, contact_name, show
365 FROM logs
366 WHERE jid_id = ?
367 ORDER BY time
368 ''', (jid_id,))
370 results = self.cur.fetchall()
372 if self._jid_is_room_type(jid): # is it room?
373 self.nickname_col_for_logs.set_visible(True)
374 self.subject_col_for_logs.set_visible(False)
375 else:
376 self.nickname_col_for_logs.set_visible(False)
377 self.subject_col_for_logs.set_visible(True)
379 for row in results:
380 # exposed in UI (TreeViewColumns) are only
381 # time, message, subject, nickname
382 # but store in liststore
383 # log_line_id, jid_id, time, message, subject, nickname
384 log_line_id, jid_id, time_, kind, message, subject, nickname, \
385 show = row
386 try:
387 time_ = time.strftime('%x', time.localtime(float(time_))
388 ).decode(locale.getpreferredencoding())
389 except ValueError:
390 pass
391 else:
392 color = None
393 if kind in (constants.KIND_SINGLE_MSG_RECV,
394 constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG):
395 # it is the other side
396 color = gajim.config.get('inmsgcolor') # so incoming color
397 elif kind in (constants.KIND_SINGLE_MSG_SENT,
398 constants.KIND_CHAT_MSG_SENT): # it is us
399 color = gajim.config.get('outmsgcolor') # so outgoing color
400 elif kind in (constants.KIND_STATUS,
401 constants.KIND_GCSTATUS): # is is statuses
402 # so status color
403 color = gajim.config.get('statusmsgcolor')
404 # include status into (status) message
405 if message is None:
406 message = ''
407 else:
408 message = ' : ' + message
409 message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + \
410 message
412 message_ = '<span'
413 if color:
414 message_ += ' foreground="%s"' % color
415 message_ += '>%s</span>' % \
416 gobject.markup_escape_text(message)
417 self.logs_liststore.append((log_line_id, jid_id, time_,
418 message_, subject, nickname))
420 def _fill_search_results_listview(self, text):
422 Ask db and fill listview with results that match text
424 self.search_results_liststore.clear()
425 like_sql = '%' + text + '%'
426 self.cur.execute('''
427 SELECT log_line_id, jid_id, time, message, subject, contact_name
428 FROM logs
429 WHERE message LIKE ? OR subject LIKE ?
430 ORDER BY time
431 ''', (like_sql, like_sql))
433 results = self.cur.fetchall()
434 for row in results:
435 # exposed in UI (TreeViewColumns) are only
436 # JID, time, message, subject, nickname
437 # but store in liststore
438 # log_line_id, jid (from jid_id), time, message, subject, nickname
439 log_line_id, jid_id, time_, message, subject, nickname = row
440 try:
441 time_ = time.strftime('%x', time.localtime(float(time_))
442 ).decode(locale.getpreferredencoding())
443 except ValueError:
444 pass
445 else:
446 jid = self._get_jid_from_jid_id(jid_id)
448 self.search_results_liststore.append((log_line_id, jid, time_,
449 message, subject, nickname))
451 def on_logs_listview_key_press_event(self, widget, event):
452 liststore, list_of_paths = self.logs_listview.get_selection()\
453 .get_selected_rows()
454 if event.keyval == gtk.keysyms.Delete:
455 self._delete_logs(liststore, list_of_paths)
457 def on_listview_button_press_event(self, widget, event):
458 if event.button == 3: # right click
459 xml = gtkgui_helpers.get_gtk_builder('history_manager.ui',
460 'context_menu')
461 if widget.name != 'jids_listview':
462 xml.get_object('export_menuitem').hide()
463 xml.get_object('delete_menuitem').connect('activate',
464 self.on_delete_menuitem_activate, widget)
466 xml.connect_signals(self)
467 xml.get_object('context_menu').popup(None, None, None,
468 event.button, event.time)
469 return True
471 def on_export_menuitem_activate(self, widget):
472 xml = gtkgui_helpers.get_gtk_builder('history_manager.ui',
473 'filechooserdialog')
474 xml.connect_signals(self)
476 dlg = xml.get_object('filechooserdialog')
477 dlg.set_title(_('Exporting History Logs...'))
478 dlg.set_current_folder(gajim.HOME_DIR)
479 dlg.props.do_overwrite_confirmation = True
480 response = dlg.run()
482 if response == gtk.RESPONSE_OK: # user want us to export ;)
483 liststore, list_of_paths = self.jids_listview.get_selection()\
484 .get_selected_rows()
485 path_to_file = dlg.get_filename()
486 self._export_jids_logs_to_file(liststore, list_of_paths,
487 path_to_file)
489 dlg.destroy()
491 def on_delete_menuitem_activate(self, widget, listview):
492 widget_name = gtk.Buildable.get_name(listview)
493 liststore, list_of_paths = listview.get_selection().get_selected_rows()
494 if widget_name == 'jids_listview':
495 self._delete_jid_logs(liststore, list_of_paths)
496 elif widget_name in ('logs_listview', 'search_results_listview'):
497 self._delete_logs(liststore, list_of_paths)
498 else: # Huh ? We don't know this widget
499 return
501 def on_jids_listview_key_press_event(self, widget, event):
502 liststore, list_of_paths = self.jids_listview.get_selection()\
503 .get_selected_rows()
504 if event.keyval == gtk.keysyms.Delete:
505 self._delete_jid_logs(liststore, list_of_paths)
507 def _export_jids_logs_to_file(self, liststore, list_of_paths, path_to_file):
508 paths_len = len(list_of_paths)
509 if paths_len == 0: # nothing is selected
510 return
512 list_of_rowrefs = []
513 for path in list_of_paths: # make them treerowrefs (it's needed)
514 list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
516 for rowref in list_of_rowrefs:
517 path = rowref.get_path()
518 if path is None:
519 continue
520 jid_id = liststore[path][1]
521 self.cur.execute('''
522 SELECT time, kind, message, contact_name FROM logs
523 WHERE jid_id = ?
524 ORDER BY time
525 ''', (jid_id,))
527 # FIXME: we may have two contacts selected to export. fix that
528 # AT THIS TIME FIRST EXECUTE IS LOST! WTH!!!!!
529 results = self.cur.fetchall()
530 #print results[0]
531 file_ = open(path_to_file, 'w')
532 for row in results:
533 # in store: time, kind, message, contact_name FROM logs
534 # in text: JID or You or nickname (if it's gc_msg), time, message
535 time_, kind, message, nickname = row
536 if kind in (constants.KIND_SINGLE_MSG_RECV,
537 constants.KIND_CHAT_MSG_RECV):
538 who = self._get_jid_from_jid_id(jid_id)
539 elif kind in (constants.KIND_SINGLE_MSG_SENT,
540 constants.KIND_CHAT_MSG_SENT):
541 who = _('You')
542 elif kind == constants.KIND_GC_MSG:
543 who = nickname
544 else: # status or gc_status. do not save
545 #print kind
546 continue
548 try:
549 time_ = time.strftime('%c', time.localtime(float(time_))
550 ).decode(locale.getpreferredencoding())
551 except ValueError:
552 pass
554 file_.write(_('%(who)s on %(time)s said: %(message)s\n') % {
555 'who': who, 'time': time_, 'message': message})
557 def _delete_jid_logs(self, liststore, list_of_paths):
558 paths_len = len(list_of_paths)
559 if paths_len == 0: # nothing is selected
560 return
562 def on_ok(liststore, list_of_paths):
563 # delete all rows from db that match jid_id
564 list_of_rowrefs = []
565 for path in list_of_paths: # make them treerowrefs (it's needed)
566 list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
568 for rowref in list_of_rowrefs:
569 path = rowref.get_path()
570 if path is None:
571 continue
572 jid_id = liststore[path][1]
573 del liststore[path] # remove from UI
574 # remove from db
575 self.cur.execute('''
576 DELETE FROM logs
577 WHERE jid_id = ?
578 ''', (jid_id,))
580 # now delete "jid, jid_id" row from jids table
581 self.cur.execute('''
582 DELETE FROM jids
583 WHERE jid_id = ?
584 ''', (jid_id,))
586 self.con.commit()
588 self.AT_LEAST_ONE_DELETION_DONE = True
590 if paths_len == 1:
591 jid_id = '<i>%s</i>' % liststore[list_of_paths[0]][0]
592 pri_text = _('Do you really want to delete the logs of %(jid)s?') \
593 % {'jid': jid_id}
594 else:
595 pri_text = _(
596 'Do you really want to delete logs of the selected contacts?')
597 dialog = dialogs.ConfirmationDialog('',
598 _('This is an irreversible operation.'), on_response_ok=(on_ok,
599 liststore, list_of_paths))
600 dialog.set_title(_('Deletion Confirmation'))
601 dialog.set_markup(pri_text)
602 ok_button = dialog.get_children()[0].get_children()[1].get_children()[0]
603 ok_button.grab_focus()
604 dialog.set_transient_for(self.window)
606 def _delete_logs(self, liststore, list_of_paths):
607 paths_len = len(list_of_paths)
608 if paths_len == 0: # nothing is selected
609 return
611 def on_ok(liststore, list_of_paths):
612 # delete rows from db that match log_line_id
613 list_of_rowrefs = []
614 for path in list_of_paths: # make them treerowrefs (it's needed)
615 list_of_rowrefs.append(gtk.TreeRowReference(liststore, path))
617 for rowref in list_of_rowrefs:
618 path = rowref.get_path()
619 if path is None:
620 continue
621 log_line_id = liststore[path][0]
622 del liststore[path] # remove from UI
623 # remove from db
624 self.cur.execute('''
625 DELETE FROM logs
626 WHERE log_line_id = ?
627 ''', (log_line_id,))
629 self.con.commit()
631 self.AT_LEAST_ONE_DELETION_DONE = True
633 pri_text = i18n.ngettext(
634 'Do you really want to delete the selected message?',
635 'Do you really want to delete the selected messages?', paths_len)
636 dialog = dialogs.ConfirmationDialog(pri_text,
637 _('This is an irreversible operation.'), on_response_ok=(on_ok,
638 liststore, list_of_paths))
639 dialog.set_title(_('Deletion Confirmation'))
640 ok_button = dialog.get_children()[0].get_children()[1].get_children()[0]
641 ok_button.grab_focus()
642 dialog.set_transient_for(self.window)
644 def on_search_db_button_clicked(self, widget):
645 text = self.search_entry.get_text().decode('utf-8')
646 if not text:
647 return
649 self.welcome_vbox.hide()
650 self.logs_scrolledwindow.hide()
651 self.search_results_scrolledwindow.show()
653 self._fill_search_results_listview(text)
655 def on_search_results_listview_row_activated(self, widget, path, column):
656 # get log_line_id, jid_id from row we double clicked
657 log_line_id = self.search_results_liststore[path][0]
658 jid = self.search_results_liststore[path][1].decode('utf-8')
659 # make it string as in gtk liststores I have them all as strings
660 # as this is what db returns so I don't have to fight with types
661 jid_id = self._get_jid_id(jid)
663 iter_ = self.jids_liststore.get_iter_root()
664 while iter_:
665 # self.jids_liststore[iter_][1] holds jid_ids
666 if self.jids_liststore[iter_][1] == jid_id:
667 break
668 iter_ = self.jids_liststore.iter_next(iter_)
670 if iter_ is None:
671 return
673 path = self.jids_liststore.get_path(iter_)
674 self.jids_listview.set_cursor(path)
676 iter_ = self.logs_liststore.get_iter_root()
677 while iter_:
678 # self.logs_liststore[iter_][0] holds lon_line_ids
679 if self.logs_liststore[iter_][0] == log_line_id:
680 break
681 iter_ = self.logs_liststore.iter_next(iter_)
683 path = self.logs_liststore.get_path(iter_)
684 self.logs_listview.scroll_to_cell(path)
686 if __name__ == '__main__':
687 signal.signal(signal.SIGINT, signal.SIG_DFL) # ^C exits the application
688 HistoryManager()
689 gtk.main()