root/trunk/src/dialogs.py

Revision 10720, 134.8 kB (checked in by js, 2 days ago)

Really fix #4517.

  • Property svn:eol-style set to LF
Line 
1# -*- coding: utf-8 -*-
2## src/dialogs.py
3##
4## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
5## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
6## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
7## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
8##                         Travis Shirk <travis AT pobox.com>
9## Copyright (C) 2005-2008 Nikos Kouremenos <kourem AT gmail.com>
10## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
11## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
12## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
13##                         Julien Pivotto <roidelapluie AT gmail.com>
14##                         Stephan Erb <steve-e AT h3c.de>
15## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
16##
17## This file is part of Gajim.
18##
19## Gajim is free software; you can redistribute it and/or modify
20## it under the terms of the GNU General Public License as published
21## by the Free Software Foundation; version 3 only.
22##
23## Gajim is distributed in the hope that it will be useful,
24## but WITHOUT ANY WARRANTY; without even the implied warranty of
25## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26## GNU General Public License for more details.
27##
28## You should have received a copy of the GNU General Public License
29## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
30##
31
32import gtk
33import gobject
34import os
35
36import gtkgui_helpers
37import vcard
38import conversation_textview
39import message_control
40import dataforms_widget
41
42from random import randrange
43from common import pep
44
45try:
46        import gtkspell
47        HAS_GTK_SPELL = True
48except Exception:
49        HAS_GTK_SPELL = False
50
51# those imports are not used in this file, but in files that 'import dialogs'
52# so they can do dialog.GajimThemesWindow() for example
53from filetransfers_window import FileTransfersWindow
54from gajim_themes_window import GajimThemesWindow
55from advanced import AdvancedConfigurationWindow
56
57from common import gajim
58from common import helpers
59from common import dataforms
60from common.exceptions import GajimGeneralException
61
62class EditGroupsDialog:
63        '''Class for the edit group dialog window'''
64        def __init__(self, list_):
65                '''list_ is a list of (contact, account) tuples'''
66                self.xml = gtkgui_helpers.get_glade('edit_groups_dialog.glade')
67                self.dialog = self.xml.get_widget('edit_groups_dialog')
68                self.dialog.set_transient_for(gajim.interface.roster.window)
69                self.list_ = list_
70                self.changes_made = False
71                self.treeview = self.xml.get_widget('groups_treeview')
72                if len(list_) == 1:
73                        contact = list_[0][0]
74                        self.xml.get_widget('nickname_label').set_markup(
75                                _('Contact name: <i>%s</i>') % contact.get_shown_name())
76                        self.xml.get_widget('jid_label').set_markup(
77                                _('Jabber ID: <i>%s</i>') % contact.jid)
78                else:
79                        self.xml.get_widget('nickname_label').set_no_show_all(True)
80                        self.xml.get_widget('nickname_label').hide()
81                        self.xml.get_widget('jid_label').set_no_show_all(True)
82                        self.xml.get_widget('jid_label').hide()
83
84                self.xml.signal_autoconnect(self)
85                self.init_list()
86
87                self.dialog.show_all()
88                if self.changes_made:
89                        for (contact, account) in self.list_:
90                                gajim.connections[account].update_contact(contact.jid, contact.name,
91                                        contact.groups)
92
93        def on_edit_groups_dialog_response(self, widget, response_id):
94                if response_id == gtk.RESPONSE_CLOSE:
95                        self.dialog.destroy()
96
97        def remove_group(self, group):
98                '''remove group group from all contacts and all their brothers'''
99                for (contact, account) in self.list_:
100                        gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group])
101
102                # FIXME: Ugly workaround.
103                gajim.interface.roster.draw_group(_('General'), account)
104
105        def add_group(self, group):
106                '''add group group to all contacts and all their brothers'''
107                for (contact, account) in self.list_:
108                        gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group])
109               
110                # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General)
111                gajim.interface.roster.draw_group(_('General'), account)
112
113        def on_add_button_clicked(self, widget):
114                group = self.xml.get_widget('group_entry').get_text().decode('utf-8')
115                if not group:
116                        return
117                # Do not allow special groups
118                if group in helpers.special_groups:
119                        return
120                # check if it already exists
121                model = self.treeview.get_model()
122                iter = model.get_iter_root()
123                while iter:
124                        if model.get_value(iter, 0).decode('utf-8') == group:
125                                return
126                        iter = model.iter_next(iter)
127                self.changes_made = True
128                model.append((group, True, False))
129                self.add_group(group)
130                self.init_list() # Re-draw list to sort new item
131
132        def group_toggled_cb(self, cell, path):
133                self.changes_made = True
134                model = self.treeview.get_model()
135                if model[path][2]:
136                        model[path][2] = False
137                        model[path][1] = True
138                else:
139                        model[path][1] = not model[path][1]
140                group = model[path][0].decode('utf-8')
141                if model[path][1]:
142                        self.add_group(group)
143                else:
144                        self.remove_group(group)
145
146        def init_list(self):
147                store = gtk.ListStore(str, bool, bool)
148                self.treeview.set_model(store)
149                for column in self.treeview.get_columns():
150                        # Clear treeview when re-drawing
151                        self.treeview.remove_column(column)
152                accounts = []
153                # Store groups in a list so we can sort them and the number of contacts in
154                # it
155                groups = {}
156                for (contact, account) in self.list_:
157                        if account not in accounts:
158                                accounts.append(account)
159                                for g in gajim.groups[account].keys():
160                                        if g in groups:
161                                                continue
162                                        groups[g] = 0
163                        c_groups = contact.groups
164                        # FIXME: Move to backend
165                        if not c_groups:
166                                c_groups = [_('General')]
167                        for g in c_groups:
168                                groups[g] += 1
169                group_list = []
170                # Remove special groups if they are empty
171                for group in groups:
172                        if group not in helpers.special_groups or groups[group] > 0:
173                                group_list.append(group)
174                group_list.sort()
175                for group in group_list:
176                        iter = store.append()
177                        store.set(iter, 0, group) # Group name
178                        if groups[group] == 0:
179                                store.set(iter, 1, False)
180                        else:
181                                store.set(iter, 1, True)
182                                if groups[group] == len(self.list_):
183                                        # all contacts are in this group
184                                        store.set(iter, 2, False)
185                                else:
186                                        store.set(iter, 2, True)
187                column = gtk.TreeViewColumn(_('Group'))
188                column.set_expand(True)
189                self.treeview.append_column(column)
190                renderer = gtk.CellRendererText()
191                column.pack_start(renderer)
192                column.set_attributes(renderer, text=0)
193
194                column = gtk.TreeViewColumn(_('In the group'))
195                column.set_expand(False)
196                self.treeview.append_column(column)
197                renderer = gtk.CellRendererToggle()
198                column.pack_start(renderer)
199                renderer.set_property('activatable', True)
200                renderer.connect('toggled', self.group_toggled_cb)
201                column.set_attributes(renderer, active=1, inconsistent=2)
202
203class PassphraseDialog:
204        '''Class for Passphrase dialog'''
205        def __init__(self, titletext, labeltext, checkbuttontext=None,
206        ok_handler=None, cancel_handler=None):
207                self.xml = gtkgui_helpers.get_glade('passphrase_dialog.glade')
208                self.window = self.xml.get_widget('passphrase_dialog')
209                self.passphrase_entry = self.xml.get_widget('passphrase_entry')
210                self.passphrase = -1
211                self.window.set_title(titletext)
212                self.xml.get_widget('message_label').set_text(labeltext)
213
214                self.ok = False
215
216                self.cancel_handler = cancel_handler
217                self.ok_handler = ok_handler
218                okbutton = self.xml.get_widget('ok_button')
219                okbutton.connect('clicked', self.on_okbutton_clicked)
220                cancelbutton = self.xml.get_widget('cancel_button')
221                cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
222
223                self.xml.signal_autoconnect(self)
224                self.window.show_all()
225
226                self.check = bool(checkbuttontext)
227                checkbutton =   self.xml.get_widget('save_passphrase_checkbutton')
228                if self.check:
229                        checkbutton.set_label(checkbuttontext)
230                else:
231                        checkbutton.hide()
232
233        def on_okbutton_clicked(self, widget):
234                if not self.ok_handler:
235                        return
236
237                passph = self.passphrase_entry.get_text().decode('utf-8')
238
239                if self.check:
240                        checked = self.xml.get_widget('save_passphrase_checkbutton').\
241                                get_active()
242                else:
243                        checked = False
244
245                self.ok = True
246
247                self.window.destroy()
248
249                if isinstance(self.ok_handler, tuple):
250                        self.ok_handler[0](passph, checked, *self.ok_handler[1:])
251                else:
252                        self.ok_handler(passph, checked)
253
254        def on_cancelbutton_clicked(self, widget):
255                self.window.destroy()
256
257        def on_passphrase_dialog_destroy(self, widget):
258                if self.cancel_handler and not self.ok:
259                        self.cancel_handler()
260
261class ChooseGPGKeyDialog:
262        '''Class for GPG key dialog'''
263        def __init__(self, title_text, prompt_text, secret_keys, on_response,
264        selected=None):
265                '''secret_keys : {keyID: userName, ...}'''
266                self.on_response = on_response
267                xml = gtkgui_helpers.get_glade('choose_gpg_key_dialog.glade')
268                self.window = xml.get_widget('choose_gpg_key_dialog')
269                self.window.set_title(title_text)
270                self.keys_treeview = xml.get_widget('keys_treeview')
271                prompt_label = xml.get_widget('prompt_label')
272                prompt_label.set_text(prompt_text)
273                model = gtk.ListStore(str, str)
274                model.set_sort_func(1, self.sort_keys)
275                model.set_sort_column_id(1, gtk.SORT_ASCENDING)
276                self.keys_treeview.set_model(model)
277                #columns
278                renderer = gtk.CellRendererText()
279                col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'),
280                        renderer, text = 0)
281                col.set_sort_column_id(0)
282                renderer = gtk.CellRendererText()
283                col = self.keys_treeview.insert_column_with_attributes(-1,
284                        _('Contact name'), renderer, text=1)
285                col.set_sort_column_id(1)
286                self.keys_treeview.set_search_column(1)
287                self.fill_tree(secret_keys, selected)
288                self.window.connect('response', self.on_dialog_response)
289                self.window.show_all()
290
291        def sort_keys(self, model, iter1, iter2):
292                value1 = model[iter1][1]
293                value2 = model[iter2][1]
294                if value1 == _('None'):
295                        return -1
296                elif value2 == _('None'):
297                        return 1
298                elif value1 < value2:
299                        return -1
300                return 1
301
302        def on_dialog_response(self, dialog, response):
303                selection = self.keys_treeview.get_selection()
304                (model, iter) = selection.get_selected()
305                if iter and response == gtk.RESPONSE_OK:
306                        keyID = [ model[iter][0].decode('utf-8'),
307                                model[iter][1].decode('utf-8') ]
308                else:
309                        keyID = None
310                self.on_response(keyID)
311                self.window.destroy()
312
313        def fill_tree(self, list_, selected):
314                model = self.keys_treeview.get_model()
315                for keyID in list_.keys():
316                        iter_ = model.append((keyID, list_[keyID]))
317                        if keyID == selected:
318                                path = model.get_path(iter_)
319                                self.keys_treeview.set_cursor(path)
320
321
322class ChangeActivityDialog:
323        PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming',
324                'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling',
325                'working']
326
327        def __init__(self, account):
328                self.account = account
329                self.xml = gtkgui_helpers.get_glade(
330                        'change_activity_dialog.glade')
331                self.window = self.xml.get_widget('change_activity_dialog')
332                self.window.set_transient_for(gajim.interface.roster.window)
333
334                self.checkbutton = self.xml.get_widget('enable_checkbutton')
335                self.notebook = self.xml.get_widget('notebook')
336                self.entry = self.xml.get_widget('description_entry')
337
338                self.activity = 'working'
339                self.subactivity = 'other'
340
341                rbtns = {}
342                group = None
343
344                for category in pep.ACTIVITIES:
345                        item = self.xml.get_widget(category + '_image')
346                        item.set_from_pixbuf(
347                                gtkgui_helpers.load_activity_icon(category).get_pixbuf())
348                        gtk.Tooltips().set_tip(item, pep.ACTIVITIES[category]['category'])
349
350                        vbox = self.xml.get_widget(category + '_vbox')
351                        vbox.set_border_width(5)
352
353                        # Other
354                        act = category + '_other'
355
356                        if group:
357                                rbtns[act] = gtk.RadioButton(group)
358                        else:
359                                rbtns[act] = group = gtk.RadioButton()
360
361                        hbox = gtk.HBox(False, 5)
362                        hbox.pack_start(gtkgui_helpers.load_activity_icon(category),
363                                False, False, 0)
364                        lbl = gtk.Label('<b>' + pep.ACTIVITIES[category]['category'] + '</b>')
365                        lbl.set_use_markup(True)
366                        hbox.pack_start(lbl, False, False, 0)
367                        rbtns[act].add(hbox)
368                        rbtns[act].connect('toggled', self.on_rbtn_toggled,
369                                [category, 'other'])
370                        vbox.pack_start(rbtns[act], False, False, 0)
371
372                        activities = []
373                        for activity in pep.ACTIVITIES[category]:
374                                activities.append(activity)
375                        activities.sort()
376
377                        for activity in activities:
378                                if activity == 'category':
379                                        continue
380
381                                act = category + '_' + activity
382
383                                if group:
384                                        rbtns[act] = gtk.RadioButton(group)
385                                else:
386                                        rbtns[act] = group = gtk.RadioButton()
387
388                                hbox = gtk.HBox(False, 5)
389                                hbox.pack_start(gtkgui_helpers.load_activity_icon(category,
390                                        activity), False, False, 0)
391                                hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]),
392                                        False, False, 0)
393                                rbtns[act].connect('toggled', self.on_rbtn_toggled,
394                                        [category, activity])
395                                rbtns[act].add(hbox)
396                                vbox.pack_start(rbtns[act], False, False, 0)
397
398                rbtns['working_other'].set_active(True)
399
400                con = gajim.connections[account]
401
402                if 'activity' in con.activity \
403                and con.activity['activity'] in pep.ACTIVITIES:
404                        if 'subactivity' in con.activity \
405                        and con.activity['subactivity'] in pep.ACTIVITIES[con.activity['activity']]:
406                                subactivity = con.activity['subactivity']
407                        else:
408                                subactivity = 'other'
409
410                        rbtns[con.activity['activity'] + '_' + subactivity]. \
411                                set_active(True)
412
413                        self.checkbutton.set_active(True)
414                        self.notebook.set_sensitive(True)
415                        self.entry.set_sensitive(True)
416
417                        self.notebook.set_current_page(
418                                self.PAGELIST.index(con.activity['activity']))
419
420                if 'text' in con.activity:
421                        self.entry.set_text(con.activity['text'])
422
423                self.xml.signal_autoconnect(self)
424                self.window.show_all()
425
426        def on_enable_checkbutton_toggled(self, widget):
427                self.notebook.set_sensitive(widget.get_active())
428                self.entry.set_sensitive(widget.get_active())
429
430        def on_rbtn_toggled(self, widget, data):
431                if widget.get_active():
432                        self.activity = data[0]
433                        self.subactivity = data[1]
434
435        def on_ok_button_clicked(self, widget):
436                '''
437                Return activity and messsage (None if no activity selected)
438                '''
439                message = None
440                if self.checkbutton.get_active():
441                        pep.user_send_activity(self.account, self.activity,
442                                self.subactivity,
443                                self.entry.get_text().decode('utf-8'))
444                else:
445                        pep.user_send_activity(self.account, '')
446                self.window.destroy()
447
448        def on_cancel_button_clicked(self, widget):
449                self.window.destroy()
450
451class ChangeMoodDialog:
452        COLS = 11
453
454        def __init__(self, account):
455                self.account = account
456                self.xml = gtkgui_helpers.get_glade('change_mood_dialog.glade')
457                self.mood = None
458
459                self.window = self.xml.get_widget('change_mood_dialog')
460                self.window.set_transient_for(gajim.interface.roster.window)
461                self.window.set_title(_('Set Mood'))
462
463                table = self.xml.get_widget('mood_icons_table')
464                self.label = self.xml.get_widget('mood_label')
465                self.entry = self.xml.get_widget('description_entry')
466
467                no_mood_button = self.xml.get_widget('no_mood_button')
468                no_mood_button.set_mode(False)
469                no_mood_button.connect('clicked',
470                        self.on_mood_button_clicked, None)
471
472                x = 1
473                y = 0
474                self.mood_buttons = {}
475
476                # Order them first
477                self.MOODS = []
478                for mood in pep.MOODS:
479                        self.MOODS.append(mood)
480                self.MOODS.sort()
481
482                for mood in self.MOODS:
483                        self.mood_buttons[mood] = gtk.RadioButton(no_mood_button)
484                        self.mood_buttons[mood].set_mode(False)
485                        self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood))
486                        self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE)
487                        gtk.Tooltips().set_tip(self.mood_buttons[mood], pep.MOODS[mood])
488                        self.mood_buttons[mood].connect('clicked',
489                                self.on_mood_button_clicked, mood)
490                        table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1)
491                       
492                        # Calculate the next position
493                        x += 1
494                        if x >= self.COLS:
495                                x = 0
496                                y += 1
497
498                con = gajim.connections[account]
499                if 'mood' in con.mood:
500                        self.mood = con.mood['mood']
501                        if self.mood in pep.MOODS:
502                                self.mood_buttons[self.mood].set_active(True)
503                                self.label.set_text(pep.MOODS[self.mood])
504                        else:
505                                self.label.set_text(self.mood)
506
507                if self.mood:
508                        self.entry.set_sensitive(True)
509                else:
510                        self.entry.set_sensitive(False)
511
512                if 'text' in con.mood:
513                        self.entry.set_text(con.mood['text'])
514
515                self.xml.signal_autoconnect(self)
516                self.window.show_all()
517
518        def on_mood_button_clicked(self, widget, data):
519                if data:
520                        self.label.set_text(pep.MOODS[data])
521                        self.entry.set_sensitive(True)
522                else:
523                        self.label.set_text(_('None'))
524                        self.entry.set_text('')
525                        self.entry.set_sensitive(False)
526                self.mood = data
527
528        def on_ok_button_clicked(self, widget):
529                '''Return mood and messsage (None if no mood selected)'''
530                message = self.entry.get_text().decode('utf-8')
531                if self.mood is None:
532                        pep.user_send_mood(self.account, '')
533                else:
534                        pep.user_send_mood(self.account, self.mood, message)
535                self.window.destroy()
536
537        def on_cancel_button_clicked(self, widget):
538                self.window.destroy()
539
540class ChangeStatusMessageDialog:
541        def __init__(self, on_response, show=None):
542                self.show = show
543                self.on_response = on_response
544                self.xml = gtkgui_helpers.get_glade('change_status_message_dialog.glade')
545                self.window = self.xml.get_widget('change_status_message_dialog')
546                self.window.set_transient_for(gajim.interface.roster.window)
547                if show:
548                        uf_show = helpers.get_uf_show(show)
549                        self.title_text = _('%s Status Message') % uf_show
550                else:
551                        self.title_text = _('Status Message')
552                self.window.set_title(self.title_text)
553
554                message_textview = self.xml.get_widget('message_textview')
555                self.message_buffer = message_textview.get_buffer()
556                self.message_buffer.connect('changed',
557                        self.toggle_sensitiviy_of_save_as_preset)
558                msg = None
559                if show:
560                        msg = gajim.config.get('last_status_msg_' + show)
561                if not msg:
562                        msg = ''
563                msg = helpers.from_one_line(msg)
564                self.message_buffer.set_text(msg)
565
566                # have an empty string selectable, so user can clear msg
567                self.preset_messages_dict = {'': ''}
568                for msg_name in gajim.config.get_per('statusmsg'):
569                        msg_text = gajim.config.get_per('statusmsg', msg_name, 'message')
570                        msg_text = helpers.from_one_line(msg_text)
571                        self.preset_messages_dict[msg_name] = msg_text
572                sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict)
573
574                self.countdown_time = gajim.config.get('change_status_window_timeout')
575                self.countdown_left = self.countdown_time
576                self.countdown_enabled = True
577
578                self.message_liststore = gtk.ListStore(str) # msg_name
579                self.message_combobox = self.xml.get_widget('message_combobox')
580                self.message_combobox.set_model(self.message_liststore)
581                cellrenderertext = gtk.CellRendererText()
582                self.message_combobox.pack_start(cellrenderertext, True)
583                self.message_combobox.add_attribute(cellrenderertext, 'text', 0)
584                for msg_name in sorted_keys_list:
585                        self.message_liststore.append((msg_name,))
586                self.xml.signal_autoconnect(self)
587                if self.countdown_time > 0:
588                        self.countdown()
589                        gobject