root/branches/gajim_0.11/src/history_window.py

Revision 7940, 14.2 kB (checked in by asterix, 19 months ago)

merge diff from trunk

  • Property svn:eol-style set to LF
Line 
1##      history_window.py
2##
3## Contributors for this file:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Nikos Kouremenos <kourem@gmail.com>
6##
7## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
8##                         Vincent Hanquez <tab@snarc.org>
9## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
10##                    Vincent Hanquez <tab@snarc.org>
11##                    Nikos Kouremenos <kourem@gmail.com>
12##                    Dimitur Kirov <dkirov@gmail.com>
13##                    Travis Shirk <travis@pobox.com>
14##                    Norman Rasmussen <norman@rasmussen.co.za>
15##
16## This program is free software; you can redistribute it and/or modify
17## it under the terms of the GNU General Public License as published
18## by the Free Software Foundation; version 2 only.
19##
20## This program is distributed in the hope that it will be useful,
21## but WITHOUT ANY WARRANTY; without even the implied warranty of
22## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23## GNU General Public License for more details.
24##
25
26import gtk
27import gobject
28import time
29import calendar
30
31import gtkgui_helpers
32import conversation_textview
33
34from common import gajim
35from common import helpers
36
37from common.logger import Constants
38
39constants = Constants()
40
41# contact_name, date, message, time
42(
43C_CONTACT_NAME,
44C_UNIXTIME,
45C_MESSAGE,
46C_TIME
47) = range(4)
48
49class HistoryWindow:
50        '''Class for browsing logs of conversations with contacts'''
51
52        def __init__(self, jid, account):
53                self.jid = jid
54                self.account = account
55                self.mark_days_idle_call_id = None
56               
57                xml = gtkgui_helpers.get_glade('history_window.glade')
58                self.window = xml.get_widget('history_window')
59               
60                self.calendar = xml.get_widget('calendar')
61                scrolledwindow = xml.get_widget('scrolledwindow')
62                self.history_textview = conversation_textview.ConversationTextview(
63                        account, used_in_history_window = True)
64                scrolledwindow.add(self.history_textview.tv)
65                self.history_buffer = self.history_textview.tv.get_buffer()
66                self.history_buffer.create_tag('highlight', background = 'yellow')
67                self.query_entry = xml.get_widget('query_entry')
68                self.search_button = xml.get_widget('search_button')
69                query_builder_button = xml.get_widget('query_builder_button')
70                query_builder_button.hide()
71                query_builder_button.set_no_show_all(True)
72                self.expander_vbox = xml.get_widget('expander_vbox')
73               
74                self.results_treeview = xml.get_widget('results_treeview')
75                # contact_name, date, message, time
76                model = gtk.ListStore(str, str, str, str)
77                self.results_treeview.set_model(model)
78                col = gtk.TreeViewColumn(_('Name'))
79                self.results_treeview.append_column(col)
80                renderer = gtk.CellRendererText()
81                col.pack_start(renderer)
82                col.set_attributes(renderer, text = C_CONTACT_NAME)
83                col.set_sort_column_id(C_CONTACT_NAME) # user can click this header and sort
84                col.set_resizable(True)
85               
86                col = gtk.TreeViewColumn(_('Date'))
87                self.results_treeview.append_column(col)
88                renderer = gtk.CellRendererText()
89                col.pack_start(renderer)
90                col.set_attributes(renderer, text = C_UNIXTIME)
91                col.set_sort_column_id(C_UNIXTIME) # user can click this header and sort
92                col.set_resizable(True)
93               
94                col = gtk.TreeViewColumn(_('Message'))
95                self.results_treeview.append_column(col)
96                renderer = gtk.CellRendererText()
97                col.pack_start(renderer)
98                col.set_attributes(renderer, text = C_MESSAGE)
99                col.set_resizable(True)
100               
101                contact = gajim.contacts.get_first_contact_from_jid(account, jid)
102                if contact:
103                        title = _('Conversation History with %s') % contact.get_shown_name()
104                else:
105                        title = _('Conversation History with %s') % jid
106                self.window.set_title(title)
107               
108                xml.signal_autoconnect(self)
109               
110                # fake event so we start mark days procedure for selected month
111                # selected month is current month as calendar defaults to selecting
112                # current date
113                self.calendar.emit('month-changed')
114
115                # select and show logs for last date we have logs with contact
116                # and if we don't have logs at all, default to today
117                result = gajim.logger.get_last_date_that_has_logs(self.jid, self.account)
118                if result is None:
119                        date = time.localtime()
120                else:
121                        tim = result
122                        date = time.localtime(tim)
123
124                y, m, d = date[0], date[1], date[2]
125                gtk_month = gtkgui_helpers.make_python_month_gtk_month(m)
126                self.calendar.select_month(gtk_month, y)
127                self.calendar.select_day(d)
128                self.add_lines_for_date(y, m, d)
129               
130                self.window.show_all()
131
132        def on_history_window_destroy(self, widget):
133                if self.mark_days_idle_call_id:
134                        # if user destroys the window, and we have a generator filling mark days
135                        # stop him!
136                        gobject.source_remove(self.mark_days_idle_call_id)
137                self.history_textview.del_handlers()
138                del gajim.interface.instances['logs'][self.jid]
139
140        def on_close_button_clicked(self, widget):
141                self.window.destroy()
142
143        def on_calendar_day_selected(self, widget):
144                year, month, day = widget.get_date() # integers
145                month = gtkgui_helpers.make_gtk_month_python_month(month)
146                self.add_lines_for_date(year, month, day)
147               
148        def do_possible_mark_for_days_in_this_month(self, widget, year, month):
149                '''this is a generator and does pseudo-threading via idle_add()
150                so it runs progressively! yea :)
151                asks for days in this month if they have logs it bolds them (marks them)'''
152                weekday, days_in_this_month = calendar.monthrange(year, month)
153                log_days = gajim.logger.get_days_with_logs(self.jid, year,
154                        month, days_in_this_month, self.account)
155                for day in log_days:
156                        widget.mark_day(day)
157                        yield True
158                yield False
159       
160        def on_calendar_month_changed(self, widget):
161                year, month, day = widget.get_date() # integers
162                # in gtk January is 1, in python January is 0,
163                # I want the second
164                # first day of month is 1 not 0
165                if self.mark_days_idle_call_id:
166                        # if user changed month, and we have a generator filling mark days
167                        # stop him from marking dates for the previously selected month
168                        gobject.source_remove(self.mark_days_idle_call_id)
169                widget.clear_marks()
170                month = gtkgui_helpers.make_gtk_month_python_month(month)
171                self.mark_days_idle_call_id = gobject.idle_add(
172                        self.do_possible_mark_for_days_in_this_month(widget, year, month).next)
173
174        def get_string_show_from_constant_int(self, show):
175                if show == constants.SHOW_ONLINE:
176                        show = 'online'
177                elif show == constants.SHOW_CHAT:
178                        show = 'chat'
179                elif show == constants.SHOW_AWAY:
180                        show = 'away'
181                elif show == constants.SHOW_XA:
182                        show = 'xa'
183                elif show == constants.SHOW_DND:
184                        show = 'dnd'
185                elif show == constants.SHOW_OFFLINE:
186                        show = 'offline'
187
188                return show
189
190        def add_lines_for_date(self, year, month, day):
191                '''adds all the lines for given date in textbuffer'''
192                self.history_buffer.set_text('') # clear the buffer first
193                self.last_time_printout = 0
194
195                lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account)
196                # lines holds list with tupples that have:
197                # contact_name, time, kind, show, message
198                for line in lines:
199                        # line[0] is contact_name, line[1] is time of message
200                        # line[2] is kind, line[3] is show, line[4] is message
201                        self.add_new_line(line[0], line[1], line[2], line[3], line[4])
202       
203        def add_new_line(self, contact_name, tim, kind, show, message):
204                '''add a new line in textbuffer'''
205                if not message and kind not in (constants.KIND_STATUS,
206                        constants.KIND_GCSTATUS):
207                        return
208                buf = self.history_buffer
209                end_iter = buf.get_end_iter()
210               
211                if gajim.config.get('print_time') == 'always':
212                        timestamp_str = gajim.config.get('time_stamp')
213                        timestamp_str = helpers.from_one_line(timestamp_str)
214                        tim = time.strftime(timestamp_str, time.localtime(float(tim)))
215                        buf.insert(end_iter, tim) # add time
216                elif gajim.config.get('print_time') == 'sometimes':
217                        every_foo_seconds = 60 * gajim.config.get(
218                                'print_ichat_every_foo_minutes')
219                        seconds_passed = tim - self.last_time_printout
220                        if seconds_passed > every_foo_seconds:
221                                self.last_time_printout = tim
222                                tim = time.strftime('%X ', time.localtime(float(tim)))
223                                buf.insert_with_tags_by_name(end_iter, tim + '\n',
224                                        'time_sometimes')
225
226                tag_name = ''
227                tag_msg = ''
228               
229                show = self.get_string_show_from_constant_int(show)
230               
231                if kind == constants.KIND_GC_MSG:
232                        tag_name = 'incoming'
233                elif kind in (constants.KIND_SINGLE_MSG_RECV,
234                constants.KIND_CHAT_MSG_RECV):
235                        contact = gajim.contacts.get_first_contact_from_jid(self.account,
236                                self.jid)
237                        if contact:
238                                # he is in our roster, use the name
239                                contact_name = contact.get_shown_name()
240                        else:
241                                room_jid, nick = gajim.get_room_and_nick_from_fjid(self.jid)
242                                # do we have him as gc_contact?
243                                gc_contact = gajim.contacts.get_gc_contact(self.account, room_jid,
244                                        nick)
245                                if gc_contact:
246                                        # so yes, it's pm!
247                                        contact_name = nick
248                                else:
249                                        contact_name = self.jid.split('@')[0]
250                        tag_name = 'incoming'
251                elif kind in (constants.KIND_SINGLE_MSG_SENT,
252                constants.KIND_CHAT_MSG_SENT):
253                        contact_name = gajim.nicks[self.account]
254                        tag_name = 'outgoing'
255                elif kind == constants.KIND_GCSTATUS:
256                        # message here (if not None) is status message
257                        if message:
258                                message = _('%(nick)s is now %(status)s: %(status_msg)s') %\
259                                        {'nick': contact_name, 'status': helpers.get_uf_show(show),
260                                        'status_msg': message }
261                        else:
262                                message = _('%(nick)s is now %(status)s') % {'nick': contact_name,
263                                        'status': helpers.get_uf_show(show) }
264                        tag_msg = 'status'
265                else: # 'status'
266                        # message here (if not None) is status message
267                        if message:
268                                message = _('Status is now: %(status)s: %(status_msg)s') % \
269                                        {'status': helpers.get_uf_show(show), 'status_msg': message}
270                        else:
271                                message = _('Status is now: %(status)s') % { 'status':
272                                        helpers.get_uf_show(show) }
273                        tag_msg = 'status'
274
275                # do not do this if gcstats, avoid dupping contact_name
276                # eg. nkour: nkour is now Offline
277                if contact_name and kind != constants.KIND_GCSTATUS:
278                        # add stuff before and after contact name
279                        before_str = gajim.config.get('before_nickname')
280                        before_str = helpers.from_one_line(before_str)
281                        after_str = gajim.config.get('after_nickname')
282                        after_str = helpers.from_one_line(after_str)
283                        format = before_str + contact_name + after_str + ' '
284                        buf.insert_with_tags_by_name(end_iter, format, tag_name)
285
286                message = message + '\n'
287                if tag_msg:
288                        self.history_textview.print_real_text(message, [tag_msg])
289                else:
290                        self.history_textview.print_real_text(message)
291
292        def set_unset_expand_on_expander(self, widget):
293                '''expander has to have expand to TRUE so scrolledwindow resizes properly
294                and does not have a static size. when expander is not expanded we set
295                expand property (note the Box one) to FALSE
296                to do this, we first get the box and then apply to expander widget
297                the True/False thingy depending if it's expanded or not
298                this function is called in a timeout just after expanded state changes'''
299                parent = widget.get_parent() # vbox
300                if not parent:
301                        # Windows closed since we launch timeout
302                        return
303                expanded = widget.get_expanded()
304                w, h = self.window.get_size()
305                if expanded: # resize to larger in height the window
306                        self.window.resize(w, int(h*1.3))
307                else: # resize to smaller in height the window
308                        self.window.resize(w, int(h/1.3))
309                # now set expand so if manually resizing scrolledwindow resizes too
310                parent.child_set_property(widget, 'expand', expanded)
311       
312        def on_search_expander_activate(self, widget):
313                if widget.get_expanded(): # it's the OPPOSITE!, it's not expanded
314                        gobject.timeout_add(200, self.set_unset_expand_on_expander, widget)
315                else:
316                        gobject.timeout_add(200, self.set_unset_expand_on_expander, widget)
317                        self.search_button.grab_default()
318                        self.query_entry.grab_focus()
319       
320        def on_search_button_clicked(self, widget):
321                text = self.query_entry.get_text()
322                model = self.results_treeview.get_model()
323                model.clear()
324                if text == '':
325                        return
326                # contact_name, time, kind, show, message, subject
327                results = gajim.logger.get_search_results_for_query(
328                                                self.jid, text, self.account)
329                #FIXME:
330                # add "subject:  | message: " in message column if kind is single
331                # also do we need show at all? (we do not search on subject)
332                for row in results:
333                        contact_name = row[0]
334                        if not contact_name:
335                                kind = row[2]
336                                if kind == constants.KIND_CHAT_MSG_SENT: # it's us! :)
337                                        contact_name = gajim.nicks[self.account]
338                                else:
339                                        contact = gajim.contacts.get_first_contact_from_jid(self.account,
340                                                self.jid)
341                                        if contact:
342                                                contact_name = contact.get_shown_name()
343                                        else:
344                                                contact_name = self.jid
345                        tim = row[1]
346                        message = row[4]
347                        local_time = time.localtime(tim)
348                        date = time.strftime('%x', local_time)
349                        # name, date, message, time (full unix time)
350                        model.append((contact_name, date, message, tim))
351                       
352        def on_results_treeview_row_activated(self, widget, path, column):
353                '''a row was double clicked, get date from row, and select it in calendar
354                which results to showing conversation logs for that date'''
355                # get currently selected date
356                cur_year, cur_month, cur_day = self.calendar.get_date()
357                cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month)
358                model = widget.get_model()
359                # make it a tupple (Y, M, D, 0, 0, 0...)
360                tim = time.strptime(model[path][C_UNIXTIME], '%x')
361                year = tim[0]
362                gtk_month = tim[1]
363                month = gtkgui_helpers.make_python_month_gtk_month(gtk_month)
364                day = tim[2]
365               
366                # avoid reruning mark days algo if same month and year!
367                if year != cur_year or gtk_month != cur_month:
368                        self.calendar.select_month(month, year)
369               
370                self.calendar.select_day(day)
371                unix_time = model[path][C_TIME]
372                self.scroll_to_result(unix_time)
373                #FIXME: one day do not search just for unix_time but the whole and user
374                # specific format of the textbuffer line [time] nick: message
375                # and highlight all that
376
377        def scroll_to_result(self, unix_time):
378                '''scrolls to the result using unix_time and highlight line'''
379                start_iter = self.history_buffer.get_start_iter()
380                local_time = time.localtime(float(unix_time))
381                tim = time.strftime('%X', local_time)
382                result = start_iter.forward_search(tim, gtk.TEXT_SEARCH_VISIBLE_ONLY,
383                        None)
384                if result is not None:
385                        match_start_iter, match_end_iter = result
386                        match_start_iter.backward_char() # include '[' or other character before time
387                        match_end_iter.forward_line() # highlight all message not just time
388                        self.history_buffer.apply_tag_by_name('highlight', match_start_iter,
389                                match_end_iter)
390                               
391                        match_start_mark = self.history_buffer.create_mark('match_start',
392                                match_start_iter, True)
393                        self.history_textview.tv.scroll_to_mark(match_start_mark, 0, True)
Note: See TracBrowser for help on using the browser.