root/branches/gajim_0.5.1/plugins/gtkgui/gtkgui.py

Revision 464, 106.5 kB (checked in by asterix, 4 years ago)

Ctrl + PageUp? / PageDown? change the tab in tabbed chat window

  • Property svn:keywords set to LastChangedDate LastChangedRevision LastChangedBy HeadURL Id
Line 
1##      plugins/gtkgui.py
2##
3## Gajim Team:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Vincent Hanquez <tab@snarc.org>
6##
7##      Copyright (C) 2003-2005 Gajim Team
8##
9## This program is free software; you can redistribute it and/or modify
10## it under the terms of the GNU General Public License as published
11## by the Free Software Foundation; version 2 only.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18
19def usage():
20        #TODO: use i18n
21        print "usage :", sys.argv[0], ' [OPTION]'
22        print "  -p\tport on whitch the sock plugin listen"
23        print "  -h, --help\tdisplay this help and exit"
24
25if __name__ == "__main__":
26        import getopt, pickle, sys, socket
27        try:
28                opts, args = getopt.getopt(sys.argv[1:], "p:h", ["help"])
29        except getopt.GetoptError:
30                # print help information and exit:
31                usage()
32                sys.exit(2)
33        port = 8255
34        for o, a in opts:
35                if o == '-p':
36                        port = a
37                if o in ("-h", "--help"):
38                        usage()
39                        sys.exit()
40        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
41        try:
42                sock.connect(('', 8255))
43        except:
44                #TODO: use i18n
45                print "unable to connect to localhost on port "+str(port)
46        else:
47                evp = pickle.dumps(('EXEC_PLUGIN', '', 'gtkgui'))
48                sock.send('<'+evp+'>')
49                sock.close()
50        sys.exit()
51
52import pygtk
53pygtk.require('2.0')
54import gtk
55from gtk import TRUE, FALSE
56import gtk.glade,gobject
57import os,string,time,Queue, sys
58import common.optparser,common.sleepy
59from common import i18n
60_ = i18n._
61APP = i18n.APP
62gtk.glade.bindtextdomain (APP, i18n.DIR)
63gtk.glade.textdomain (APP)
64
65from dialogs import *
66from config import *
67
68GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade'
69
70USE_TABBED_CHAT = 0
71
72class ImageCellRenderer(gtk.GenericCellRenderer):
73
74        __gproperties__ = {
75                "image": (gobject.TYPE_OBJECT, "Image", 
76                "Image", gobject.PARAM_READWRITE),
77        }
78                     
79        def __init__(self):
80                self.__gobject_init__()
81                self.image = None
82
83        def do_set_property(self, pspec, value):
84                setattr(self, pspec.name, value)
85
86        def do_get_property(self, pspec):
87                return getattr(self, pspec.name)
88
89        def func(self, model, path, iter, (image, tree)):
90                if model.get_value(iter, 0) == image:
91                        self.redraw = 1
92                        cell_area = tree.get_cell_area(path, tree.get_column(0))
93                        tree.queue_draw_area(cell_area.x, cell_area.y, cell_area.width, cell_area.height)
94
95        def animation_timeout(self, tree, image):
96                if image.get_storage_type() == gtk.IMAGE_ANIMATION:
97                        self.redraw = 0
98                        image.get_data('iter').advance()
99                        model = tree.get_model()
100                        model.foreach(self.func, (image, tree))
101                        if self.redraw:
102                                gobject.timeout_add(image.get_data('iter').get_delay_time(), self.animation_timeout, tree, image)
103                        else:
104                                image.set_data('iter', None)
105                               
106        def on_render(self, window, widget, background_area,cell_area, \
107                expose_area, flags):
108                pix_rect = gtk.gdk.Rectangle()
109                pix_rect.x, pix_rect.y, pix_rect.width, pix_rect.height = self.on_get_size(widget, cell_area)
110
111                pix_rect.x += cell_area.x
112                pix_rect.y += cell_area.y
113                pix_rect.width  -= 2 * self.get_property("xpad")
114                pix_rect.height -= 2 * self.get_property("ypad")
115
116                draw_rect = cell_area.intersect(pix_rect)
117                draw_rect = expose_area.intersect(draw_rect)
118
119                if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
120                        if not self.image.get_data('iter'):
121                                animation = self.image.get_animation()
122                                self.image.set_data('iter', animation.get_iter())
123                                gobject.timeout_add(self.image.get_data('iter').get_delay_time(), self.animation_timeout, widget, self.image)
124
125                        pix = self.image.get_data('iter').get_pixbuf()
126                elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
127                        pix = self.image.get_pixbuf()
128                else:
129                        return
130                window.draw_pixbuf(widget.style.black_gc, pix, \
131                        draw_rect.x-pix_rect.x, draw_rect.y-pix_rect.y, draw_rect.x, \
132                        draw_rect.y+2, draw_rect.width, draw_rect.height, \
133                        gtk.gdk.RGB_DITHER_NONE, 0, 0)
134
135        def on_get_size(self, widget, cell_area):
136                if self.image.get_storage_type() == gtk.IMAGE_ANIMATION:
137                        animation = self.image.get_animation()
138                        pix = animation.get_iter().get_pixbuf()
139                elif self.image.get_storage_type() == gtk.IMAGE_PIXBUF:
140                        pix = self.image.get_pixbuf()
141                else:
142                        return 0, 0, 0, 0
143                pixbuf_width  = pix.get_width()
144                pixbuf_height = pix.get_height()
145                calc_width  = self.get_property("xpad") * 2 + pixbuf_width
146                calc_height = self.get_property("ypad") * 2 + pixbuf_height
147                x_offset = 0
148                y_offset = 0
149                if cell_area and pixbuf_width > 0 and pixbuf_height > 0:
150                        x_offset = self.get_property("xalign") * (cell_area.width - calc_width -  self.get_property("xpad"))
151                        y_offset = self.get_property("yalign") * (cell_area.height - calc_height -  self.get_property("ypad"))
152                return x_offset, y_offset, calc_width, calc_height
153
154gobject.type_register(ImageCellRenderer)
155
156
157class user:
158        """Informations concerning each users"""
159        def __init__(self, *args):
160                if len(args) == 0:
161                        self.jid = ''
162                        self.name = ''
163                        self.groups = []
164                        self.show = ''
165                        self.status = ''
166                        self.sub = ''
167                        self.ask = ''
168                        self.resource = ''
169                        self.priority = 1
170                        self.keyID = ''
171                elif len(args) == 10:
172                        self.jid = args[0]
173                        self.name = args[1]
174                        self.groups = args[2]
175                        self.show = args[3]
176                        self.status = args[4]
177                        self.sub = args[5]
178                        self.ask = args[6]
179                        self.resource = args[7]
180                        self.priority = args[8]
181                        self.keyID = args[9]
182                else: raise TypeError, _('bad arguments')
183
184class tabbed_chat_Window:
185        """Class for tabbed chat window"""
186        def __init__(self, user, plugin, account):
187                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'tabbed_chat', APP)
188                self.xml.get_widget('notebook').remove_page(0)
189                self.plugin = plugin
190                self.account = account
191                self.widgets = {}
192                self.tagIn = {}
193                self.tagOut = {}
194                self.tagStatus = {}
195                self.users = {user.jid: user}
196                self.nb_unread = {user.jid: 0}
197                self.window = self.xml.get_widget('tabbed_chat')
198                self.new_user(user)
199                self.show_title()
200                self.xml.signal_connect('gtk_widget_destroy', self.delete_event)
201                self.xml.signal_connect('on_focus', self.on_focus)
202                self.xml.signal_connect('on_chat_key_press_event', \
203                        self.on_chat_key_press_event)
204                self.xml.signal_connect('on_notebook_switch_page', \
205                        self.on_notebook_switch_page)
206               
207        def update_tags(self):
208                for jid in self.tagIn:
209                        self.tagIn[jid].set_property("foreground", \
210                                self.plugin.config['inmsgcolor'])
211                        self.tagOut[jid].set_property("foreground", \
212                                self.plugin.config['outmsgcolor'])
213                        self.tagStatus[jid].set_property("foreground", \
214                                self.plugin.config['statusmsgcolor'])
215
216        def show_title(self):
217                unread = 0
218                for jid in self.nb_unread:
219                        unread += self.nb_unread[jid]
220                start = ""
221                if unread > 1:
222                        start = "[" + str(unread) + "] "
223                elif unread == 1:
224                        start = "* "
225                self.window.set_title(start + "Chat (" + self.account + ")")
226
227        def draw_widgets(self, user):
228                widget_img = self.widgets[user.jid]['image_status']
229                image = self.plugin.roster.pixbufs[user.show]
230                if image.get_storage_type() == gtk.IMAGE_ANIMATION:
231                        widget_img.set_from_animation(image.get_animation())
232                elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
233                        widget_img.set_from_pixbuf(image.get_pixbuf())
234                self.widgets[user.jid]['button_contact'].set_label(\
235                        user.name + ' <' + user.jid + '>')
236                if not user.keyID:
237                        self.widgets[user.jid]['toggle_gpg'].set_sensitive(False)
238
239        def redraw_tab(self, jid):
240                start = ''
241                if self.nb_unread[jid] > 1:
242                        start = "[" + str(self.nb_unread[jid]) + "] "
243                elif self.nb_unread[jid] == 1:
244                        start = "* "
245                nb = self.xml.get_widget("notebook")
246                child = self.widgets[jid]['vbox_tab']
247                nb.set_tab_label_text(child, start + self.users[jid].name)
248
249        def set_image(self, image, jid):
250                if image.get_storage_type() == gtk.IMAGE_ANIMATION:
251                        self.widgets[jid]['image_status'].\
252                                set_from_animation(image.get_animation())
253                elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
254                        self.widgets[jid]['image_status'].\
255                                set_from_pixbuf(image.get_pixbuf())
256
257        def delete_event(self, widget):
258                """close window"""
259                #clean self.plugin.windows[self.account]['chats']
260                for jid in self.users:
261                        del self.plugin.windows[self.account]['chats'][jid]
262                del self.plugin.windows[self.account]['chats']['tabbed']
263
264        def get_active_jid(self):
265                nb = self.xml.get_widget("notebook")
266                child = nb.get_nth_page(nb.get_current_page())
267                jid = ''
268                for j in self.widgets:
269                        c = self.widgets[j]['vbox_tab']
270                        if c == child:
271                                jid = j
272                                break
273                return jid
274
275        def on_clear(self, widget):
276                """When clear button is pressed :
277                clear the conversation"""
278                jid = self.get_active_jid()
279                buffer = self.widgets[jid]['conversation'].get_buffer()
280                deb, end = buffer.get_bounds()
281                buffer.delete(deb, end)
282
283        def on_close_clicked(self, button):
284                """When close button is pressed :
285                close a tab"""
286                jid = self.get_active_jid()
287                if len(self.widgets) == 1:
288                        button.get_toplevel().destroy()
289                else:
290                        nb = self.xml.get_widget('notebook')
291                        nb.remove_page(nb.get_current_page())
292                        del self.plugin.windows[self.account]['chats'][jid]
293                        del self.users[jid]
294                        del self.nb_unread[jid]
295                        del self.widgets[jid]
296                        del self.tagIn[jid]
297                        del self.tagOut[jid]
298                        del self.tagStatus[jid]
299
300        def on_focus(self, widget, event):
301                """When window get focus"""
302                jid = self.get_active_jid()
303                if self.nb_unread[jid] > 0:
304                        self.nb_unread[jid] = 0
305                        self.redraw_tab(jid)
306                        self.show_title()
307                        self.plugin.systray.remove_jid(jid, self.account)
308
309        def on_history(self, widget):
310                """When history button is pressed : call log window"""
311                jid = self.get_active_jid()
312                if not self.plugin.windows['logs'].has_key(jid):
313                        self.plugin.windows['logs'][jid] = log_Window(self.plugin, jid)
314
315        def on_notebook_switch_page(self, nb, page, page_num):
316                child = nb.get_nth_page(page_num)
317                jid = ''
318                for j in self.widgets:
319                        c = self.widgets[j]['vbox_tab']
320                        if c == child:
321                                jid = j
322                                break
323                if self.nb_unread[jid] > 0:
324                        self.nb_unread[jid] = 0
325                        self.redraw_tab(jid)
326                        self.show_title()
327                        self.plugin.systray.remove_jid(jid, self.account)
328
329        def active_tab(self, jid):
330                child = self.widgets[jid]['vbox_tab']
331                nb = self.xml.get_widget("notebook")
332                nb.set_current_page(nb.page_num(child))
333                self.widgets[jid]['message'].grab_focus()
334
335        def new_user(self, user):
336                self.nb_unread[user.jid] = 0
337                self.users[user.jid] = user
338
339                self.widgets[user.jid] = {}
340                vb = gtk.VBox()
341                vb.set_border_width(5)
342                self.widgets[user.jid]['vbox_tab'] = vb
343                hb = gtk.HBox(spacing=5)
344                hb.set_border_width(5)
345                vb.pack_start(hb, expand=False)
346               
347                button = gtk.Button(stock=gtk.STOCK_JUSTIFY_FILL)
348                button.get_children()[0].get_children()[0].get_children()[1].set_text("History")
349                button.connect("clicked", self.on_history)
350                hb.pack_start(button, expand=False, fill=False)
351
352                button = gtk.Button(stock=gtk.STOCK_CLEAR)
353                button.connect("clicked", self.on_clear)
354                hb.pack_start(button, expand=False, fill=False)
355
356                img = gtk.Image()
357                hb.pack_start(img, expand=False, fill=False)
358                self.widgets[user.jid]['image_status'] = img
359
360                img = gtk.Image()
361                img.set_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_BUTTON)
362                button = gtk.ToggleButton()
363                button.add(img)
364                button.set_relief(gtk.RELIEF_NONE)
365                hb.pack_start(button, expand=False, fill=False)
366                self.widgets[user.jid]['toggle_gpg'] = button
367
368                fixed = gtk.Fixed()
369                fixed.set_size_request(20, -1)
370                hb.pack_start(fixed)
371                button = gtk.Button("Anonymous")
372                button.set_relief(gtk.RELIEF_NONE)
373                button.connect("clicked", self.on_button_contact_clicked)
374                button.set_use_underline(False)
375                fixed.put(button, 0, 0)
376                self.widgets[user.jid]['button_contact'] = button
377               
378                img = gtk.Image()
379                img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_BUTTON)
380                button = gtk.Button()
381                button.add(img)
382                button.connect("clicked", self.on_close_clicked)
383                hb.pack_start(button, expand=False, fill=False)
384
385                vp = gtk.VPaned()
386                vb.pack_start(vp)
387                vp.set_position(170)
388
389                sw = gtk.ScrolledWindow()
390                sw.set_shadow_type(gtk.SHADOW_IN)
391                vp.add1(sw)
392                sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
393                tv = gtk.TextView()
394                tv.set_wrap_mode(gtk.WRAP_WORD)
395                tv.set_editable(False)
396                tv.set_cursor_visible(False)
397                sw.add(tv)
398                self.widgets[user.jid]['conversation'] = tv
399                buffer = tv.get_buffer()
400                end_iter = buffer.get_end_iter()
401                buffer.create_mark('end', end_iter, 0)
402                self.tagIn[user.jid] = buffer.create_tag("incoming")
403                color = self.plugin.config['inmsgcolor']
404                self.tagIn[user.jid].set_property("foreground", color)
405                self.tagOut[user.jid] = buffer.create_tag("outgoing")
406                color = self.plugin.config['outmsgcolor']
407                self.tagOut[user.jid].set_property("foreground", color)
408                self.tagStatus[user.jid] = buffer.create_tag("status")
409                color = self.plugin.config['statusmsgcolor']
410                self.tagStatus[user.jid].set_property("foreground", color)
411
412                sw = gtk.ScrolledWindow()
413                sw.set_shadow_type(gtk.SHADOW_IN)
414                vp.add2(sw)
415                sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
416                tv = gtk.TextView()
417                tv.set_wrap_mode(gtk.WRAP_WORD)
418                sw.add(tv)
419                self.widgets[user.jid]['message'] = tv
420                tv.connect('key_press_event', self.on_msg_key_press_event)
421
422                vb.show_all()
423                nb = self.xml.get_widget("notebook")
424                nb.set_current_page(nb.append_page(vb))
425
426                self.redraw_tab(user.jid)
427                self.draw_widgets(user)
428                tv.grab_focus()
429
430                #print queued messages
431                if self.plugin.queues[self.account].has_key(user.jid):
432                        self.read_queue(self.plugin.queues[self.account][user.jid])
433                if user.show != 'online':
434                        self.print_conversation(_("%s is now %s (%s)") % (user.name, \
435                                user.show, user.status), user.jid, 'status')
436
437        def on_msg_key_press_event(self, widget, event):
438                """When a key is pressed :
439                if enter is pressed without the shit key, message (if not empty) is sent
440                and printed in the conversation"""
441                if event.keyval == gtk.keysyms.Return:
442                        if (event.state & gtk.gdk.SHIFT_MASK):
443                                return 0
444                        txt_buffer = widget.get_buffer()
445                        start_iter = txt_buffer.get_start_iter()
446                        end_iter = txt_buffer.get_end_iter()
447                        txt = txt_buffer.get_text(start_iter, end_iter, 0)
448                        if txt != '':
449                                keyID = ''
450                                jid = self.get_active_jid()
451                                if self.widgets[jid]['toggle_gpg'].get_active():
452                                        keyID = self.users[jid].keyID
453                                self.plugin.send('MSG', self.account, (jid, txt, keyID))
454                                txt_buffer.set_text('', -1)
455                                self.print_conversation(txt, jid, jid)
456                        return 1
457                return 0
458
459        def on_chat_key_press_event(self, widget, event):
460                nb = self.xml.get_widget("notebook")
461                st = "1234567890"
462                if event.keyval == gtk.keysyms.Escape:
463                        jid = self.get_active_jid()
464                        if len(self.widgets) == 1:
465                                widget.get_toplevel().destroy()
466                        else:
467                                nb.remove_page(nb.get_current_page())
468                                del self.plugin.windows[self.account]['chats'][jid]
469                                del self.users[jid]
470                                del self.nb_unread[jid]
471                                del self.widgets[jid]
472                                del self.tagIn[jid]
473                                del self.tagOut[jid]
474                                del self.tagStatus[jid]
475                elif event.string and event.string in st \
476                        and (event.state & gtk.gdk.MOD1_MASK):
477                        nb.set_current_page(st.index(event.string))
478                elif event.keyval == gtk.keysyms.Page_Down and \
479                        (event.state & gtk.gdk.CONTROL_MASK):
480                        current = nb.get_current_page()
481                        if current > 0:
482                                nb.set_current_page(current-1)
483                        else:
484                                nb.set_current_page(nb.get_n_pages()-1)
485                elif event.keyval == gtk.keysyms.Page_Up and \
486                        (event.state & gtk.gdk.CONTROL_MASK):
487                        current = nb.get_current_page()
488                        if current < (nb.get_n_pages()-1):
489                                nb.set_current_page(current+1)
490                        else:
491                                nb.set_current_page(0)
492
493        def on_button_contact_clicked(self, widget):
494                """When button contact is clicked"""
495                jid = self.get_active_jid()
496                user = self.users[jid]
497                self.plugin.roster.on_info(widget, user, self.account)
498
499        def read_queue(self, q):
500                """read queue and print messages containted in it"""
501                jid = self.get_active_jid()
502                user = self.users[jid]
503                while not q.empty():
504                        evt = q.get()
505                        self.print_conversation(evt[0], jid, tim = evt[1])
506                        self.plugin.roster.nb_unread -= 1
507                self.plugin.roster.show_title()
508                del self.plugin.queues[self.account][jid]
509                self.plugin.roster.redraw_jid(jid, self.account)
510                self.plugin.systray.remove_jid(jid, self.account)
511                showOffline = self.plugin.config['showoffline']
512                if (user.show == 'offline' or user.show == 'error') and \
513                        not showOffline:
514                        if len(self.plugin.roster.contacts[self.account][jid]) == 1:
515                                self.plugin.roster.remove_user(user, self.account)
516
517        def print_conversation(self, txt, jid, contact = None, tim = None):
518                """Print a line in the conversation :
519                if contact is set to status : it's a status message
520                if contact is set to another value : it's an outgoing message
521                if contact is not set : it's an incomming message"""
522                user = self.users[jid]
523                conversation = self.widgets[jid]['conversation']
524                buffer = conversation.get_buffer()
525                if not txt:
526                        txt = ""
527                end_iter = buffer.get_end_iter()
528                if not tim:
529                        tim = time.localtime()
530                tims = time.strftime("[%H:%M:%S]", tim)
531                buffer.insert(end_iter, tims + ' ')
532               
533                otxt = ''
534                ttxt = ''
535                if contact == 'status':
536                        tag = 'status'
537                        ttxt = txt + '\n'
538                else:
539                        if contact:
540                                tag = 'outgoing'
541                                name = self.plugin.nicks[self.account] 
542                        else:
543                                tag = 'incoming'
544                                name = user.name
545                               
546                        if string.find(txt, '/me ') == 0:
547                                ttxt = name + ' ' + txt[4:] + '\n'
548                        else:
549                                ttxt