root/branches/gajim_0.9.1/src/dialogs.py

Revision 4735, 48.4 kB (checked in by asterix, 3 years ago)

single message now has conversation_textview so it prints urls and smeileys

  • Property svn:eol-style set to LF
Line 
1##      dialogs.py
2##
3## Contributors for this file:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Nikos Kouremenos <kourem@gmail.com>
6##      - Dimitur Kirov <dkirov@gmail.com>
7##
8## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
9##                         Vincent Hanquez <tab@snarc.org>
10## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
11##                    Vincent Hanquez <tab@snarc.org>
12##                    Nikos Kouremenos <nkour@jabber.org>
13##                    Dimitur Kirov <dkirov@gmail.com>
14##                    Travis Shirk <travis@pobox.com>
15##                    Norman Rasmussen <norman@rasmussen.co.za>
16##
17## This program is free software; you can redistribute it and/or modify
18## it under the terms of the GNU General Public License as published
19## by the Free Software Foundation; version 2 only.
20##
21## This program is distributed in the hope that it will be useful,
22## but WITHOUT ANY WARRANTY; without even the implied warranty of
23## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24## GNU General Public License for more details.
25##
26
27import gtk
28import gtk.glade
29import gobject
30import os
31import Queue
32
33import gtkgui_helpers
34import vcard
35import conversation_textview
36
37try:
38        import gtkspell
39        HAS_GTK_SPELL = True
40except:
41        HAS_GTK_SPELL = False
42
43from filetransfers_window import FileTransfersWindow
44from gajim_themes_window import GajimThemesWindow
45from advanced import AdvancedConfigurationWindow
46from gajim import Contact
47from common import gajim
48from common import helpers
49from common import i18n
50
51_ = i18n._
52APP = i18n.APP
53gtk.glade.bindtextdomain (APP, i18n.DIR)
54gtk.glade.textdomain (APP)
55
56GTKGUI_GLADE = 'gtkgui.glade'
57
58class EditGroupsDialog:
59        '''Class for the edit group dialog window'''
60        def __init__(self, user, account):
61                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'edit_groups_dialog', APP)
62                self.dialog = self.xml.get_widget('edit_groups_dialog')
63                self.account = account
64                self.user = user
65                self.changes_made = False
66                self.list = self.xml.get_widget('groups_treeview')
67                self.xml.get_widget('nickname_label').set_markup(
68                        _("Contact's name: <i>%s</i>") % user.name)
69                self.xml.get_widget('jid_label').set_markup(
70                        _('JID: <i>%s</i>') % user.jid)
71               
72                self.xml.signal_autoconnect(self)
73                self.init_list()
74
75        def run(self):
76                self.dialog.show_all()
77                if self.changes_made:
78                        gajim.connections[self.account].update_contact(self.user.jid,
79                                self.user.name, self.user.groups)
80
81        def on_edit_groups_dialog_response(self, widget, response_id):
82                if response_id == gtk.RESPONSE_CLOSE:
83                        self.dialog.destroy()
84
85        def update_contact(self):
86                gajim.interface.roster.remove_contact(self.user, self.account)
87                gajim.interface.roster.add_contact_to_roster(self.user.jid, self.account)
88                gajim.connections[self.account].update_contact(self.user.jid,
89                        self.user.name, self.user.groups)
90
91        def on_add_button_clicked(self, widget):
92                group = self.xml.get_widget('group_entry').get_text().decode('utf-8')
93                if not group:
94                        return
95                # check if it already exists
96                model = self.list.get_model()
97                iter = model.get_iter_root()
98                while iter:
99                        if model.get_value(iter, 0).decode('utf-8') == group:
100                                return
101                        iter = model.iter_next(iter)
102                self.changes_made = True
103                model.append((group, True))
104                self.user.groups.append(group)
105                self.update_contact()
106
107        def group_toggled_cb(self, cell, path):
108                self.changes_made = True
109                model = self.list.get_model()
110                if model[path][1] and len(self.user.groups) == 1: # we try to remove
111                                                                                                                                                  # the last group
112                        ErrorDialog(_('Cannot remove last group'),
113                                        _('At least one contact group must be present.')).get_response()
114                        return
115                model[path][1] = not model[path][1]
116                if model[path][1]:
117                        self.user.groups.append(model[path][0].decode('utf-8'))
118                else:
119                        self.user.groups.remove(model[path][0].decode('utf-8'))
120                self.update_contact()
121
122        def init_list(self):
123                store = gtk.ListStore(str, bool)
124                self.list.set_model(store)
125                for g in gajim.groups[self.account].keys():
126                        if g in (_('Transports'), _('not in the roster')):
127                                continue
128                        iter = store.append()
129                        store.set(iter, 0, g)
130                        if g in self.user.groups:
131                                store.set(iter, 1, True)
132                        else:
133                                store.set(iter, 1, False)
134                column = gtk.TreeViewColumn(_('Group'))
135                column.set_expand(True)
136                self.list.append_column(column)
137                renderer = gtk.CellRendererText()
138                column.pack_start(renderer)
139                column.set_attributes(renderer, text = 0)
140               
141                column = gtk.TreeViewColumn(_('In the group'))
142                column.set_expand(False)
143                self.list.append_column(column)
144                renderer = gtk.CellRendererToggle()
145                column.pack_start(renderer)
146                renderer.set_property('activatable', True)
147                renderer.connect('toggled', self.group_toggled_cb)
148                column.set_attributes(renderer, active = 1)
149
150class PassphraseDialog:
151        '''Class for Passphrase dialog'''
152        def run(self):
153                '''Wait for OK button to be pressed and return passphrase/password'''
154                rep = self.window.run()
155                if rep == gtk.RESPONSE_OK:
156                        passphrase = self.passphrase_entry.get_text().decode('utf-8')
157                else:
158                        passphrase = -1
159                save_passphrase_checkbutton = self.xml.\
160                        get_widget('save_passphrase_checkbutton')
161                self.window.destroy()
162                return passphrase, save_passphrase_checkbutton.get_active()
163
164        def __init__(self, titletext, labeltext, checkbuttontext):
165                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'passphrase_dialog', APP)
166                self.window = self.xml.get_widget('passphrase_dialog')
167                self.passphrase_entry = self.xml.get_widget('passphrase_entry')
168                self.passphrase = -1
169                self.window.set_title(titletext)
170                self.xml.get_widget('message_label').set_text(labeltext)
171                self.xml.get_widget('save_passphrase_checkbutton').set_label(
172                        checkbuttontext)
173                self.xml.signal_autoconnect(self)
174                self.window.show_all()
175
176class ChooseGPGKeyDialog:
177        '''Class for GPG key dialog'''
178        def __init__(self, title_text, prompt_text, secret_keys, selected = None):
179                #list : {keyID: userName, ...}
180                xml = gtk.glade.XML(GTKGUI_GLADE, 'choose_gpg_key_dialog', APP)
181                self.window = xml.get_widget('choose_gpg_key_dialog')
182                self.window.set_title(title_text)
183                self.keys_treeview = xml.get_widget('keys_treeview')
184                prompt_label = xml.get_widget('prompt_label')
185                prompt_label.set_text(prompt_text)
186                model = gtk.ListStore(str, str)
187                model.set_sort_column_id(1, gtk.SORT_ASCENDING)
188                self.keys_treeview.set_model(model)
189                #columns
190                renderer = gtk.CellRendererText()
191                self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'),
192                        renderer, text = 0)
193                renderer = gtk.CellRendererText()
194                self.keys_treeview.insert_column_with_attributes(-1, _('Contact name'),
195                        renderer, text = 1)
196                self.fill_tree(secret_keys, selected)
197                self.window.show_all()
198
199        def run(self):
200                rep = self.window.run()
201                if rep == gtk.RESPONSE_OK:
202                        selection = self.keys_treeview.get_selection()
203                        (model, iter) = selection.get_selected()
204                        keyID = [ model[iter][0].decode('utf-8'),
205                                model[iter][1].decode('utf-8') ]
206                else:
207                        keyID = None
208                self.window.destroy()
209                return keyID
210
211        def fill_tree(self, list, selected):
212                model = self.keys_treeview.get_model()
213                for keyID in list.keys():
214                        iter = model.append((keyID, list[keyID]))
215                        if keyID == selected:
216                                path = model.get_path(iter)
217                                self.keys_treeview.set_cursor(path)
218
219
220class ChangeStatusMessageDialog:
221        def __init__(self, show = None):
222                self.show = show
223                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'change_status_message_dialog', APP)
224                self.window = self.xml.get_widget('change_status_message_dialog')
225                if show:
226                        uf_show = helpers.get_uf_show(show)
227                        title_text = _('%s Status Message') % uf_show
228                else:
229                        title_text = _('Status Message')
230                self.window.set_title(title_text)
231               
232                message_textview = self.xml.get_widget('message_textview')
233                self.message_buffer = message_textview.get_buffer()
234                msg = None
235                if show:
236                        msg = gajim.config.get('last_status_msg_' + show)
237                if not msg:
238                        msg = ''
239                msg = helpers.from_one_line(msg)
240                self.message_buffer.set_text(msg)
241                self.values = {'':''} # have an empty string selectable, so user can clear msg
242                for msg in gajim.config.get_per('statusmsg'):
243                        val = gajim.config.get_per('statusmsg', msg, 'message')
244                        val = helpers.from_one_line(val)
245                        self.values[msg] = val
246                sorted_keys_list = helpers.get_sorted_keys(self.values)
247                liststore = gtk.ListStore(str, str)
248                message_comboboxentry = self.xml.get_widget('message_comboboxentry')
249                message_comboboxentry.set_model(liststore)
250                message_comboboxentry.set_text_column(0)
251                message_comboboxentry.child.set_property('editable', False)
252                for val in sorted_keys_list:
253                        message_comboboxentry.append_text(val)
254                self.xml.signal_autoconnect(self)
255                self.window.show_all()
256
257        def run(self):
258                '''Wait for OK or Cancel button to be pressed and return status messsage
259                (None if users pressed Cancel or x button of WM'''
260                rep = self.window.run()
261                if rep == gtk.RESPONSE_OK:
262                        beg, end = self.message_buffer.get_bounds()
263                        message = self.message_buffer.get_text(beg, end).decode('utf-8').strip()
264                        msg = helpers.to_one_line(message)
265                        if self.show:
266                                gajim.config.set('last_status_msg_' + self.show, msg)
267                else:
268                        message = None # user pressed Cancel button or X wm button
269                self.window.destroy()
270                return message
271
272        def on_message_comboboxentry_changed(self, widget, data = None):
273                model = widget.get_model()
274                active = widget.get_active()
275                if active < 0:
276                        return None
277                name = model[active][0].decode('utf-8')
278                self.message_buffer.set_text(self.values[name])
279       
280        def on_change_status_message_dialog_key_press_event(self, widget, event):
281                if event.keyval == gtk.keysyms.Return or \
282                event.keyval == gtk.keysyms.KP_Enter:  # catch CTRL+ENTER
283                        if (event.state & gtk.gdk.CONTROL_MASK):
284                                self.window.response(gtk.RESPONSE_OK)
285
286class AddNewContactWindow:
287        '''Class for AddNewContactWindow'''
288        def __init__(self, account, jid = None):
289                self.account = account
290                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'add_new_contact_window', APP)
291                self.window = self.xml.get_widget('add_new_contact_window')
292                self.uid_entry = self.xml.get_widget('uid_entry')
293                self.protocol_combobox = self.xml.get_widget('protocol_combobox')
294                self.jid_entry = self.xml.get_widget('jid_entry')
295                self.nickname_entry = self.xml.get_widget('nickname_entry')
296                if len(gajim.connections) >= 2:
297                        prompt_text =\
298_('Please fill in the data of the contact you want to add in account %s') %account
299                else:
300                        prompt_text = _('Please fill in the data of the contact you want to add')
301                self.xml.get_widget('prompt_label').set_text(prompt_text)
302                self.old_uid_value = ''
303                liststore = gtk.ListStore(str, str)
304                liststore.append(['Jabber', ''])
305                self.agents = ['Jabber']
306                jid_agents = []
307                for j in gajim.contacts[account]:
308                        user = gajim.contacts[account][j][0]
309                        if _('Transports') in user.groups and user.show != 'offline' and \
310                                        user.show != 'error':
311                                jid_agents.append(j)
312                for a in jid_agents:
313                        if a.find('aim') > -1:
314                                name = 'AIM'
315                        elif a.find('icq') > -1:
316                                name = 'ICQ'
317                        elif a.find('msn') > -1:
318                                name = 'MSN'
319                        elif a.find('yahoo') > -1:
320                                name = 'Yahoo!'
321                        else:
322                                name = a
323                        iter = liststore.append([name, a])
324                        self.agents.append(name)
325               
326                self.protocol_combobox.set_model(liststore)
327                self.protocol_combobox.set_active(0)
328                self.fill_jid()
329                if jid:
330                        self.jid_entry.set_text(jid)
331                        self.uid_entry.set_sensitive(False)
332                        jid_splited = jid.split('@')
333                        if jid_splited[1] in jid_agents:
334                                uid = jid_splited[0].replace('%', '@')
335                                self.uid_entry.set_text(uid)
336                                self.protocol_combobox.set_active(jid_agents.index(jid_splited[1]) + 1)
337                        else:
338                                self.uid_entry.set_text(jid)
339                                self.protocol_combobox.set_active(0)
340                        self.set_nickname()
341                        self.nickname_entry.grab_focus()
342
343                self.group_comboboxentry = self.xml.get_widget('group_comboboxentry')
344                liststore = gtk.ListStore(str)
345                self.group_comboboxentry.set_model(liststore)
346                for g in gajim.groups[account].keys():
347                        if g != _('not in the roster') and g != _('Transports'):
348                                self.group_comboboxentry.append_text(g)
349
350                if not jid_agents:
351                        # There are no transports, so hide the protocol combobox and label
352                        self.protocol_combobox.hide()
353                        self.protocol_combobox.set_no_show_all(True)
354                        protocol_label = self.xml.get_widget('protocol_label')
355                        protocol_label.hide()
356                        protocol_label.set_no_show_all(True)
357
358                self.xml.signal_autoconnect(self)
359                self.window.show_all()
360
361        def on_add_new_contact_window_key_press_event(self, widget, event):
362                if event.keyval == gtk.keysyms.Escape: # ESCAPE
363                        self.window.destroy()
364
365        def on_cancel_button_clicked(self, widget):
366                '''When Cancel button is clicked'''
367                self.window.destroy()
368
369        def on_subscribe_button_clicked(self, widget):
370                '''When Subscribe button is clicked'''
371                jid = self.jid_entry.get_text().decode('utf-8')
372                nickname = self.nickname_entry.get_text().decode('utf-8')
373                if not jid:
374                        return
375       
376                # check if jid is conform to RFC and stringprep it
377                try:
378                        jid = helpers.parse_jid(jid)
379                except helpers.InvalidFormat, s:
380                        pritext = _('Invalid User ID')
381                        ErrorDialog(pritext, str(s)).get_response()
382                        return
383
384                # Check if jid is already in roster
385                if jid in gajim.contacts[self.account] and _('not in the roster') not in \
386                        gajim.contacts[self.account][jid][0].groups:
387                        ErrorDialog(_('Contact already in roster'),
388                        _('This contact is already listed in your roster.')).get_response()
389                        return
390
391                message_buffer = self.xml.get_widget('message_textview').get_buffer()
392                start_iter = message_buffer.get_start_iter()
393                end_iter = message_buffer.get_end_iter()
394                message = message_buffer.get_text(start_iter, end_iter).decode('utf-8')
395                group = self.group_comboboxentry.child.get_text().decode('utf-8')
396                gajim.interface.roster.req_sub(self, jid, message, self.account,
397                        group = group, pseudo = nickname)
398                if self.xml.get_widget('auto_authorize_checkbutton').get_active():
399                        gajim.connections[self.account].send_authorization(jid)
400                self.window.destroy()
401               
402        def fill_jid(self):
403                model = self.protocol_combobox.get_model()
404                index = self.protocol_combobox.get_active()
405                jid = self.uid_entry.get_text().decode('utf-8').strip()
406                if index > 0: # it's not jabber but a transport
407                        jid = jid.replace('@', '%')
408                agent = model[index][1].decode('utf-8')
409                if agent:
410                        jid += '@' + agent
411                self.jid_entry.set_text(jid)
412
413        def on_protocol_combobox_changed(self, widget):
414                self.fill_jid()
415
416        def guess_agent(self):
417                uid = self.uid_entry.get_text().decode('utf-8')
418                model = self.protocol_combobox.get_model()
419               
420                #If login contains only numbers, it's probably an ICQ number
421                if uid.isdigit():
422                        if 'ICQ' in self.agents:
423                                self.protocol_combobox.set_active(self.agents.index('ICQ'))
424                                return
425
426        def set_nickname(self):
427                uid = self.uid_entry.get_text().decode('utf-8')
428                nickname = self.nickname_entry.get_text().decode('utf-8')
429                if nickname == self.old_uid_value:
430                        self.nickname_entry.set_text(uid.split('@')[0])
431                       
432        def on_uid_entry_changed(self, widget):
433                uid = self.uid_entry.get_text().decode('utf-8')
434                self.guess_agent()
435                self.set_nickname()
436                self.fill_jid()
437                self.old_uid_value = uid.split('@')[0]
438
439class AboutDialog:
440        '''Class for about dialog'''
441        def __init__(self):
442                dlg = gtk.AboutDialog()
443                dlg.set_name('Gajim')
444                dlg.set_version(gajim.version)
445                s = u'Copyright \xa9 2003-2005 Gajim Team'
446                dlg.set_copyright(s)
447                text = open('../COPYING').read()
448                dlg.set_license(text)
449
450                dlg.set_comments(_('A GTK+ jabber client'))
451                dlg.set_website('http://www.gajim.org')
452
453                authors = [
454                        'Current Developers:',
455                        'Yann Le Boulanger <asterix@lagaule.org>',
456                        'Nikos Kouremenos <kourem@gmail.com>',
457                        'Dimitur Kirov <dkirov@gmail.com>',
458                        'Travis Shirk <travis@pobox.com>',
459                        '',
460                        'Read AUTHORS file for full list including past developers',
461                        'Read THANKS file for contributors',
462                ]
463                #FIXME: make See authors setence and Current devs sentence translatable
464               
465                dlg.set_authors(authors)
466               
467                if gtk.pygtk_version >= (2, 8, 0) and gtk.gtk_version >= (2, 8, 0):
468                        dlg.props.wrap_license = True
469
470                pixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(
471                        gajim.DATA_DIR, 'pixmaps', 'gajim_about.png'))                 
472
473                dlg.set_logo(pixbuf)
474                #here you write your name in the form Name FamilyName <someone@somewhere>
475                dlg.set_translator_credits(_('translator-credits'))
476               
477                artists = ['Christophe Got', 'Dennis Craven', 'Guillaume Morin',
478                        'Membris Khan']
479                dlg.set_artists(artists)
480
481                rep = dlg.run()
482                dlg.destroy()
483
484class Dialog(gtk.Dialog):
485        def __init__(self, parent, title, buttons, default = None):
486                gtk.Dialog.__init__(self, title, parent, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR)
487
488                self.set_border_width(6)
489                self.vbox.set_spacing(12)
490                self.set_resizable(False)
491
492                for stock, response in buttons:
493                        self.add_button(stock, response)
494
495                if default is not None:
496                        self.set_default_response(default)
497                else:
498                        self.set_default_response(buttons[-1][1])
499
500        def get_button(self, index):
501                buttons = self.action_area.get_children()
502                return index < len(buttons) and buttons[index] or None
503
504class HigDialog(gtk.MessageDialog):
505        def __init__(self, parent, type, buttons, pritext, sectext):
506                gtk.MessageDialog.__init__(self, parent, 
507                                gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL,
508                                type, buttons, message_format = pritext)
509
510                self.format_secondary_text(sectext)
511
512        def get_response(self):
513                self.show_all()
514                response = self.run()
515                self.destroy()
516                return response
517
518class ConfirmationDialog(HigDialog):
519        '''HIG compliant confirmation dialog.'''
520        def __init__(self, pritext, sectext=''):
521                HigDialog.__init__(self, None, 
522                        gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, pritext, sectext)
523               
524                self.set_default_response(gtk.RESPONSE_OK)
525               
526                ok_button = self.action_area.get_children()[0] # right to left
527                ok_button.grab_focus()
528                       
529class WarningDialog(HigDialog):
530        def __init__(self, pritext, sectext=''):
531                '''HIG compliant warning dialog.'''
532                HigDialog.__init__( self, None, 
533                                gtk.MESSAGE_WARNING, gtk.BUTTONS_OK, pritext, sectext)
534
535class InformationDialog(HigDialog):
536        def __init__(self, pritext, sectext=''):
537                '''HIG compliant info dialog.'''
538                HigDialog.__init__( self, None, 
539                                gtk.MESSAGE_INFO, gtk.BUTTONS_OK, pritext, sectext)
540                ok_button = self.action_area.get_children()[0]
541                ok_button.connect('clicked', self.on_ok_button_clicked)
542                self.show_all()
543
544        def on_ok_button_clicked(self, widget):
545                self.destroy()
546
547class ErrorDialog(HigDialog):
548        def __init__(self, pritext, sectext=''):
549                '''HIG compliant error dialog.'''
550                HigDialog.__init__( self, None, 
551                                gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, pritext, sectext)
552
553class ConfirmationDialogCheck(ConfirmationDialog):
554        '''HIG compliant confirmation dialog with checkbutton.'''
555        def __init__(self, pritext, sectext='', checktext = ''):
556                HigDialog.__init__(self, None, gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,
557                        pritext, sectext)
558
559                self.set_default_response(gtk.RESPONSE_OK)
560               
561                ok_button = self.action_area.get_children()[0] # right to left
562                ok_button.grab_focus()
563               
564                self.checkbutton = gtk.CheckButton(checktext)
565                self.vbox.pack_start(self.checkbutton, expand = False, fill = True)
566       
567        def is_checked(self):
568                ''' Get active state of the checkbutton '''
569                return self.checkbutton.get_active()
570               
571class InputDialog:
572        '''Class for Input dialog'''
573        def __init__(self, title, label_str, input_str = None, is_modal = True,