dialogs.py 160 KB
Newer Older
1
# -*- coding: utf-8 -*-
roidelapluie's avatar
roidelapluie committed
2
## src/dialogs.py
Yann Leboulanger's avatar
Yann Leboulanger committed
3
##
roidelapluie's avatar
roidelapluie committed
4 5 6 7 8 9 10 11 12 13 14 15
## Copyright (C) 2003-2005 Vincent Hanquez <tab AT snarc.org>
## Copyright (C) 2003-2008 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
##                         Travis Shirk <travis AT pobox.com>
## Copyright (C) 2005-2008 Nikos Kouremenos <kourem AT gmail.com>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
## Copyright (C) 2007-2008 Brendan Taylor <whateley AT gmail.com>
##                         Julien Pivotto <roidelapluie AT gmail.com>
##                         Stephan Erb <steve-e AT h3c.de>
## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
Yann Leboulanger's avatar
Yann Leboulanger committed
16
##
Yann Leboulanger's avatar
Yann Leboulanger committed
17 18 19
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
Yann Leboulanger's avatar
Yann Leboulanger committed
20
## it under the terms of the GNU General Public License as published
Yann Leboulanger's avatar
Yann Leboulanger committed
21
## by the Free Software Foundation; version 3 only.
Yann Leboulanger's avatar
Yann Leboulanger committed
22
##
Yann Leboulanger's avatar
Yann Leboulanger committed
23
## Gajim is distributed in the hope that it will be useful,
Yann Leboulanger's avatar
Yann Leboulanger committed
24
## but WITHOUT ANY WARRANTY; without even the implied warranty of
roidelapluie's avatar
roidelapluie committed
25
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Yann Leboulanger's avatar
Yann Leboulanger committed
26 27
## GNU General Public License for more details.
##
Yann Leboulanger's avatar
Yann Leboulanger committed
28
## You should have received a copy of the GNU General Public License
roidelapluie's avatar
roidelapluie committed
29
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
Yann Leboulanger's avatar
Yann Leboulanger committed
30
##
Yann Leboulanger's avatar
Yann Leboulanger committed
31 32

import gtk
33
import gobject
Yann Leboulanger's avatar
Yann Leboulanger committed
34
import os
35
from weakref import WeakValueDictionary
36

nkour's avatar
nkour committed
37
import gtkgui_helpers
nkour's avatar
nkour committed
38
import vcard
39
import conversation_textview
40
import message_control
Yann Leboulanger's avatar
Yann Leboulanger committed
41 42 43
import dataforms_widget

from random import randrange
js's avatar
js committed
44
from common import pep
45

46 47 48
try:
	import gtkspell
	HAS_GTK_SPELL = True
49
except ImportError:
50 51
	HAS_GTK_SPELL = False

nkour's avatar
nkour committed
52
# those imports are not used in this file, but in files that 'import dialogs'
53
# so they can do dialog.GajimThemesWindow() for example
54
from filetransfers_window import FileTransfersWindow
55
from gajim_themes_window import GajimThemesWindow
56
from advanced_configuration_window import AdvancedConfigurationWindow
57

58
from common import gajim
59
from common import helpers
Yann Leboulanger's avatar
Yann Leboulanger committed
60
from common import dataforms
61
from common.exceptions import GajimGeneralException
Yann Leboulanger's avatar
Yann Leboulanger committed
62

63
class EditGroupsDialog:
nkour's avatar
nkour committed
64
	'''Class for the edit group dialog window'''
65 66
	def __init__(self, list_):
		'''list_ is a list of (contact, account) tuples'''
67
		self.xml = gtkgui_helpers.get_glade('edit_groups_dialog.glade')
68
		self.dialog = self.xml.get_widget('edit_groups_dialog')
69 70
		self.dialog.set_transient_for(gajim.interface.roster.window)
		self.list_ = list_
71
		self.changes_made = False
Yann Leboulanger's avatar
Yann Leboulanger committed
72
		self.treeview = self.xml.get_widget('groups_treeview')
73 74 75
		if len(list_) == 1:
			contact = list_[0][0]
			self.xml.get_widget('nickname_label').set_markup(
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
76
				_('Contact name: <i>%s</i>') % contact.get_shown_name())
77
			self.xml.get_widget('jid_label').set_markup(
jimpp's avatar
jimpp committed
78
				_('Jabber ID: <i>%s</i>') % contact.jid)
79 80 81 82 83 84
		else:
			self.xml.get_widget('nickname_label').set_no_show_all(True)
			self.xml.get_widget('nickname_label').hide()
			self.xml.get_widget('jid_label').set_no_show_all(True)
			self.xml.get_widget('jid_label').hide()

85 86 87
		self.xml.signal_autoconnect(self)
		self.init_list()

88
		self.dialog.show_all()
89
		if self.changes_made:
90 91
			for (contact, account) in self.list_:
				gajim.connections[account].update_contact(contact.jid, contact.name,
Yann Leboulanger's avatar
Yann Leboulanger committed
92
					contact.groups)
93

94 95 96 97
	def on_edit_groups_dialog_response(self, widget, response_id):
		if response_id == gtk.RESPONSE_CLOSE:
			self.dialog.destroy()

98
	def remove_group(self, group):
99 100
		'''remove group group from all contacts and all their brothers'''
		for (contact, account) in self.list_:
101 102 103 104
			gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group])

		# FIXME: Ugly workaround.
		gajim.interface.roster.draw_group(_('General'), account)
105 106

	def add_group(self, group):
107 108
		'''add group group to all contacts and all their brothers'''
		for (contact, account) in self.list_:
109
			gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group])
110

111 112
		# FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General)
		gajim.interface.roster.draw_group(_('General'), account)
113 114

	def on_add_button_clicked(self, widget):
115
		group = self.xml.get_widget('group_entry').get_text().decode('utf-8')
116 117
		if not group:
			return
118 119 120
		# Do not allow special groups
		if group in helpers.special_groups:
			return
121
		# check if it already exists
Yann Leboulanger's avatar
Yann Leboulanger committed
122
		model = self.treeview.get_model()
123 124 125
		iter_ = model.get_iter_root()
		while iter_:
			if model.get_value(iter_, 0).decode('utf-8') == group:
126
				return
127
			iter_ = model.iter_next(iter_)
128
		self.changes_made = True
129
		model.append((group, True, False))
130
		self.add_group(group)
131
		self.init_list() # Re-draw list to sort new item
132 133

	def group_toggled_cb(self, cell, path):
134
		self.changes_made = True
Yann Leboulanger's avatar
Yann Leboulanger committed
135
		model = self.treeview.get_model()
136 137 138 139 140
		if model[path][2]:
			model[path][2] = False
			model[path][1] = True
		else:
			model[path][1] = not model[path][1]
141 142 143 144 145
		group = model[path][0].decode('utf-8')
		if model[path][1]:
			self.add_group(group)
		else:
			self.remove_group(group)
146 147

	def init_list(self):
148
		store = gtk.ListStore(str, bool, bool)
Yann Leboulanger's avatar
Yann Leboulanger committed
149 150 151 152
		self.treeview.set_model(store)
		for column in self.treeview.get_columns():
			# Clear treeview when re-drawing
			self.treeview.remove_column(column)
153 154 155 156 157 158 159 160 161 162 163
		accounts = []
		# Store groups in a list so we can sort them and the number of contacts in
		# it
		groups = {}
		for (contact, account) in self.list_:
			if account not in accounts:
				accounts.append(account)
				for g in gajim.groups[account].keys():
					if g in groups:
						continue
					groups[g] = 0
164 165
			c_groups = contact.groups
			for g in c_groups:
166
				groups[g] += 1
167 168 169 170 171
		group_list = []
		# Remove special groups if they are empty
		for group in groups:
			if group not in helpers.special_groups or groups[group] > 0:
				group_list.append(group)
172
		group_list.sort()
173
		for group in group_list:
174 175
			iter_ = store.append()
			store.set(iter_, 0, group) # Group name
176
			if groups[group] == 0:
177
				store.set(iter_, 1, False)
178
			else:
179
				store.set(iter_, 1, True)
180 181
				if groups[group] == len(self.list_):
					# all contacts are in this group
182
					store.set(iter_, 2, False)
183
				else:
184
					store.set(iter_, 2, True)
185
		column = gtk.TreeViewColumn(_('Group'))
nkour's avatar
nkour committed
186
		column.set_expand(True)
Yann Leboulanger's avatar
Yann Leboulanger committed
187
		self.treeview.append_column(column)
188 189
		renderer = gtk.CellRendererText()
		column.pack_start(renderer)
Yann Leboulanger's avatar
Yann Leboulanger committed
190
		column.set_attributes(renderer, text=0)
191

192
		column = gtk.TreeViewColumn(_('In the group'))
nkour's avatar
nkour committed
193
		column.set_expand(False)
Yann Leboulanger's avatar
Yann Leboulanger committed
194
		self.treeview.append_column(column)
195 196 197 198
		renderer = gtk.CellRendererToggle()
		column.pack_start(renderer)
		renderer.set_property('activatable', True)
		renderer.connect('toggled', self.group_toggled_cb)
Yann Leboulanger's avatar
Yann Leboulanger committed
199
		column.set_attributes(renderer, active=1, inconsistent=2)
200

201
class PassphraseDialog:
nkour's avatar
nkour committed
202
	'''Class for Passphrase dialog'''
203
	def __init__(self, titletext, labeltext, checkbuttontext=None,
Yann Leboulanger's avatar
Yann Leboulanger committed
204
	ok_handler=None, cancel_handler=None):
205
		self.xml = gtkgui_helpers.get_glade('passphrase_dialog.glade')
206 207 208
		self.window = self.xml.get_widget('passphrase_dialog')
		self.passphrase_entry = self.xml.get_widget('passphrase_entry')
		self.passphrase = -1
209
		self.window.set_title(titletext)
210
		self.xml.get_widget('message_label').set_text(labeltext)
Yann Leboulanger's avatar
Yann Leboulanger committed
211 212 213 214

		self.ok = False

		self.cancel_handler = cancel_handler
215 216 217 218 219
		self.ok_handler = ok_handler
		okbutton = self.xml.get_widget('ok_button')
		okbutton.connect('clicked', self.on_okbutton_clicked)
		cancelbutton = self.xml.get_widget('cancel_button')
		cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
Yann Leboulanger's avatar
Yann Leboulanger committed
220

221
		self.xml.signal_autoconnect(self)
Yann Leboulanger's avatar
Yann Leboulanger committed
222
		self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
223
		self.window.show_all()
224

Yann Leboulanger's avatar
Yann Leboulanger committed
225 226 227 228 229 230
		self.check = bool(checkbuttontext)
		checkbutton =	self.xml.get_widget('save_passphrase_checkbutton')
		if self.check:
			checkbutton.set_label(checkbuttontext)
		else:
			checkbutton.hide()
231

Yann Leboulanger's avatar
Yann Leboulanger committed
232
	def on_okbutton_clicked(self, widget):
233 234 235
		if not self.ok_handler:
			return

Yann Leboulanger's avatar
Yann Leboulanger committed
236 237 238 239
		passph = self.passphrase_entry.get_text().decode('utf-8')

		if self.check:
			checked = self.xml.get_widget('save_passphrase_checkbutton').\
Yann Leboulanger's avatar
Yann Leboulanger committed
240
				get_active()
Yann Leboulanger's avatar
Yann Leboulanger committed
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
		else:
			checked = False

		self.ok = True

		self.window.destroy()

		if isinstance(self.ok_handler, tuple):
			self.ok_handler[0](passph, checked, *self.ok_handler[1:])
		else:
			self.ok_handler(passph, checked)

	def on_cancelbutton_clicked(self, widget):
		self.window.destroy()

	def on_passphrase_dialog_destroy(self, widget):
		if self.cancel_handler and not self.ok:
			self.cancel_handler()
Yann Leboulanger's avatar
Yann Leboulanger committed
259

260
class ChooseGPGKeyDialog:
nkour's avatar
nkour committed
261
	'''Class for GPG key dialog'''
262
	def __init__(self, title_text, prompt_text, secret_keys, on_response,
263
				 selected=None):
264 265
		'''secret_keys : {keyID: userName, ...}'''
		self.on_response = on_response
266
		xml = gtkgui_helpers.get_glade('choose_gpg_key_dialog.glade')
267
		self.window = xml.get_widget('choose_gpg_key_dialog')
268
		self.window.set_title(title_text)
269
		self.keys_treeview = xml.get_widget('keys_treeview')
270 271
		prompt_label = xml.get_widget('prompt_label')
		prompt_label.set_text(prompt_text)
272
		model = gtk.ListStore(str, str)
273
		model.set_sort_func(1, self.sort_keys)
274
		model.set_sort_column_id(1, gtk.SORT_ASCENDING)
275
		self.keys_treeview.set_model(model)
276 277
		#columns
		renderer = gtk.CellRendererText()
Yann Leboulanger's avatar
Yann Leboulanger committed
278
		col = self.keys_treeview.insert_column_with_attributes(-1, _('KeyID'),
Yann Leboulanger's avatar
Yann Leboulanger committed
279
			renderer, text=0)
Yann Leboulanger's avatar
Yann Leboulanger committed
280
		col.set_sort_column_id(0)
281
		renderer = gtk.CellRendererText()
Yann Leboulanger's avatar
Yann Leboulanger committed
282
		col = self.keys_treeview.insert_column_with_attributes(-1,
Yann Leboulanger's avatar
Yann Leboulanger committed
283
			_('Contact name'), renderer, text=1)
Yann Leboulanger's avatar
Yann Leboulanger committed
284
		col.set_sort_column_id(1)
285
		self.keys_treeview.set_search_column(1)
286
		self.fill_tree(secret_keys, selected)
287
		self.window.connect('response', self.on_dialog_response)
Yann Leboulanger's avatar
Yann Leboulanger committed
288
		self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
289 290
		self.window.show_all()

291 292 293 294 295 296 297 298 299 300 301
	def sort_keys(self, model, iter1, iter2):
		value1 = model[iter1][1]
		value2 = model[iter2][1]
		if value1 == _('None'):
			return -1
		elif value2 == _('None'):
			return 1
		elif value1 < value2:
			return -1
		return 1

302
	def on_dialog_response(self, dialog, response):
303
		selection = self.keys_treeview.get_selection()
304 305 306
		(model, iter_) = selection.get_selected()
		if iter_ and response == gtk.RESPONSE_OK:
			keyID = [ model[iter_][0].decode('utf-8'),
Yann Leboulanger's avatar
Yann Leboulanger committed
307
				model[iter_][1].decode('utf-8') ]
308 309
		else:
			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 316
		for keyID in list_.keys():
			iter_ = model.append((keyID, list_[keyID]))
317
			if keyID == selected:
318
				path = model.get_path(iter_)
319 320 321
				self.keys_treeview.set_cursor(path)


322
class ChangeActivityDialog:
323
	PAGELIST = ['doing_chores', 'drinking', 'eating', 'exercising', 'grooming',
Yann Leboulanger's avatar
Yann Leboulanger committed
324 325
		'having_appointment', 'inactive', 'relaxing', 'talking', 'traveling',
		'working']
js's avatar
js committed
326

327 328 329 330 331
	def __init__(self, on_response, activity=None, subactivity=None, text=''):
		self.on_response = on_response
		self.activity = activity
		self.subactivity = subactivity
		self.text = text
js's avatar
js committed
332 333
		self.xml = gtkgui_helpers.get_glade(
			'change_activity_dialog.glade')
334 335 336
		self.window = self.xml.get_widget('change_activity_dialog')
		self.window.set_transient_for(gajim.interface.roster.window)

js's avatar
js committed
337 338
		self.checkbutton = self.xml.get_widget('enable_checkbutton')
		self.notebook = self.xml.get_widget('notebook')
339
		self.entry = self.xml.get_widget('description_entry')
340

js's avatar
js committed
341 342
		rbtns = {}
		group = None
js's avatar
js committed
343

js's avatar
js committed
344 345 346
		for category in pep.ACTIVITIES:
			item = self.xml.get_widget(category + '_image')
			item.set_from_pixbuf(
js's avatar
js committed
347
				gtkgui_helpers.load_activity_icon(category).get_pixbuf())
348
			item.set_tooltip_text(pep.ACTIVITIES[category]['category'])
349

js's avatar
js committed
350
			vbox = self.xml.get_widget(category + '_vbox')
351 352 353 354 355 356 357 358 359 360
			vbox.set_border_width(5)

			# Other
			act = category + '_other'

			if group:
				rbtns[act] = gtk.RadioButton(group)
			else:
				rbtns[act] = group = gtk.RadioButton()

js's avatar
js committed
361
			hbox = gtk.HBox(False, 5)
Yann Leboulanger's avatar
Yann Leboulanger committed
362 363
			hbox.pack_start(gtkgui_helpers.load_activity_icon(category), False,
				False, 0)
364 365
			lbl = gtk.Label('<b>' + pep.ACTIVITIES[category]['category'] + '</b>')
			lbl.set_use_markup(True)
js's avatar
js committed
366 367
			hbox.pack_start(lbl, False, False, 0)
			rbtns[act].add(hbox)
368
			rbtns[act].connect('toggled', self.on_rbtn_toggled,
Yann Leboulanger's avatar
Yann Leboulanger committed
369
				[category, 'other'])
370 371
			vbox.pack_start(rbtns[act], False, False, 0)

js's avatar
js committed
372
			activities = []
js's avatar
js committed
373
			for activity in pep.ACTIVITIES[category]:
js's avatar
js committed
374 375 376 377
				activities.append(activity)
			activities.sort()

			for activity in activities:
js's avatar
js committed
378 379 380
				if activity == 'category':
					continue

js's avatar
js committed
381
				act = category + '_' + activity
382

js's avatar
js committed
383 384 385 386
				if group:
					rbtns[act] = gtk.RadioButton(group)
				else:
					rbtns[act] = group = gtk.RadioButton()
387

js's avatar
js committed
388 389
				hbox = gtk.HBox(False, 5)
				hbox.pack_start(gtkgui_helpers.load_activity_icon(category,
Yann Leboulanger's avatar
Yann Leboulanger committed
390
					activity), False, False, 0)
js's avatar
js committed
391
				hbox.pack_start(gtk.Label(pep.ACTIVITIES[category][activity]),
Yann Leboulanger's avatar
Yann Leboulanger committed
392
					False, False, 0)
js's avatar
js committed
393
				rbtns[act].connect('toggled', self.on_rbtn_toggled,
Yann Leboulanger's avatar
Yann Leboulanger committed
394
					[category, activity])
js's avatar
js committed
395
				rbtns[act].add(hbox)
js's avatar
js committed
396
				vbox.pack_start(rbtns[act], False, False, 0)
397

js's avatar
js committed
398

399 400 401
		if self.activity in pep.ACTIVITIES:
			if not self.subactivity in pep.ACTIVITIES[self.activity]:
				self.subactivity = 'other'
js's avatar
js committed
402

403
			rbtns[self.activity + '_' + self.subactivity].set_active(True)
js's avatar
js committed
404 405 406 407 408 409

			self.checkbutton.set_active(True)
			self.notebook.set_sensitive(True)
			self.entry.set_sensitive(True)

			self.notebook.set_current_page(
410 411 412
				self.PAGELIST.index(self.activity))

			self.entry.set_text(text)
413

414 415
		else:
			self.checkbutton.set_active(False)
416

417
		self.xml.signal_autoconnect(self)
Yann Leboulanger's avatar
Yann Leboulanger committed
418
		self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
419 420
		self.window.show_all()

js's avatar
js committed
421 422 423
	def on_enable_checkbutton_toggled(self, widget):
		self.notebook.set_sensitive(widget.get_active())
		self.entry.set_sensitive(widget.get_active())
424

js's avatar
js committed
425 426 427 428
	def on_rbtn_toggled(self, widget, data):
		if widget.get_active():
			self.activity = data[0]
			self.subactivity = data[1]
429

430
	def on_ok_button_clicked(self, widget):
js's avatar
js committed
431 432 433 434
		'''
		Return activity and messsage (None if no activity selected)
		'''
		if self.checkbutton.get_active():
435
			self.on_response(self.activity, self.subactivity,
Yann Leboulanger's avatar
Yann Leboulanger committed
436
				self.entry.get_text().decode('utf-8'))
js's avatar
js committed
437
		else:
438
			self.on_response(None, None, '')
js's avatar
js committed
439
		self.window.destroy()
440 441 442 443

	def on_cancel_button_clicked(self, widget):
		self.window.destroy()

444
class ChangeMoodDialog:
js's avatar
js committed
445 446
	COLS = 11

447 448 449 450
	def __init__(self, on_response, mood=None, text=''):
		self.on_response = on_response
		self.mood = mood
		self.text = text
451
		self.xml = gtkgui_helpers.get_glade('change_mood_dialog.glade')
js's avatar
js committed
452

453 454
		self.window = self.xml.get_widget('change_mood_dialog')
		self.window.set_transient_for(gajim.interface.roster.window)
455
		self.window.set_title(_('Set Mood'))
456

js's avatar
js committed
457 458
		table = self.xml.get_widget('mood_icons_table')
		self.label = self.xml.get_widget('mood_label')
459
		self.entry = self.xml.get_widget('description_entry')
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
460

js's avatar
js committed
461 462 463
		no_mood_button = self.xml.get_widget('no_mood_button')
		no_mood_button.set_mode(False)
		no_mood_button.connect('clicked',
Yann Leboulanger's avatar
Yann Leboulanger committed
464
			self.on_mood_button_clicked, None)
js's avatar
js committed
465 466 467 468

		x = 1
		y = 0
		self.mood_buttons = {}
js's avatar
js committed
469 470 471

		# Order them first
		self.MOODS = []
js's avatar
js committed
472
		for mood in pep.MOODS:
js's avatar
js committed
473 474 475 476 477
			self.MOODS.append(mood)
		self.MOODS.sort()

		for mood in self.MOODS:
			self.mood_buttons[mood] = gtk.RadioButton(no_mood_button)
js's avatar
js committed
478
			self.mood_buttons[mood].set_mode(False)
js's avatar
js committed
479
			self.mood_buttons[mood].add(gtkgui_helpers.load_mood_icon(mood))
js's avatar
js committed
480
			self.mood_buttons[mood].set_relief(gtk.RELIEF_NONE)
481
			self.mood_buttons[mood].set_tooltip_text(pep.MOODS[mood])
js's avatar
js committed
482
			self.mood_buttons[mood].connect('clicked',
Yann Leboulanger's avatar
Yann Leboulanger committed
483
				self.on_mood_button_clicked, mood)
js's avatar
js committed
484
			table.attach(self.mood_buttons[mood], x, x + 1, y, y + 1)
485

js's avatar
js committed
486 487 488 489 490
			# Calculate the next position
			x += 1
			if x >= self.COLS:
				x = 0
				y += 1
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
491

492 493 494
		if self.mood in pep.MOODS:
			self.mood_buttons[self.mood].set_active(True)
			self.label.set_text(pep.MOODS[self.mood])
Yann Leboulanger's avatar
Yann Leboulanger committed
495
			self.entry.set_sensitive(True)
496 497
			if self.text:
				self.entry.set_text(self.text)
Yann Leboulanger's avatar
Yann Leboulanger committed
498
		else:
499 500
			self.label.set_text(_('None'))
			self.entry.set_text('')
Yann Leboulanger's avatar
Yann Leboulanger committed
501
			self.entry.set_sensitive(False)
502

503
		self.xml.signal_autoconnect(self)
Yann Leboulanger's avatar
Yann Leboulanger committed
504
		self.window.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
505
		self.window.show_all()
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
506

js's avatar
js committed
507 508
	def on_mood_button_clicked(self, widget, data):
		if data:
js's avatar
js committed
509
			self.label.set_text(pep.MOODS[data])
js's avatar
js committed
510 511
			self.entry.set_sensitive(True)
		else:
512
			self.label.set_text(_('None'))
js's avatar
js committed
513 514 515 516
			self.entry.set_text('')
			self.entry.set_sensitive(False)
		self.mood = data

517 518
	def on_ok_button_clicked(self, widget):
		'''Return mood and messsage (None if no mood selected)'''
js's avatar
js committed
519
		message = self.entry.get_text().decode('utf-8')
520
		self.on_response(self.mood, message)
js's avatar
js committed
521
		self.window.destroy()
522

523 524
	def on_cancel_button_clicked(self, widget):
		self.window.destroy()
525

526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559
class TimeoutDialog:
	'''
	Class designed to be derivated to create timeout'd dialogs (dialogs that
	closes automatically after a timeout)
	'''
	def __init__(self, timeout, on_timeout):
		self.countdown_left = timeout
		self.countdown_enabled = True
		self.title_text = ''
		self.on_timeout = on_timeout

	def run_timeout(self):
		if self.countdown_left > 0:
			self.countdown()
			gobject.timeout_add_seconds(1, self.countdown)

	def on_timeout():
		'''To be implemented in derivated classes'''
		pass

	def countdown(self):
		if self.countdown_enabled:
			if self.countdown_left <= 0:
				self.on_timeout()
				return False
			self.dialog.set_title('%s [%s]' % (self.title_text,
				str(self.countdown_left)))
			self.countdown_left -= 1
			return True
		else:
			self.dialog.set_title(self.title_text)
			return False

class ChangeStatusMessageDialog(TimeoutDialog):
560
	def __init__(self, on_response, show=None, show_pep=True):
561 562
		countdown_time = gajim.config.get('change_status_window_timeout')
		TimeoutDialog.__init__(self, countdown_time, self.on_timeout)
563
		self.show = show
564 565
		self.pep_dict = {}
		self.show_pep = show_pep
566
		self.on_response = on_response
567
		self.xml = gtkgui_helpers.get_glade('change_status_message_dialog.glade')
568 569
		self.dialog = self.xml.get_widget('change_status_message_dialog')
		self.dialog.set_transient_for(gajim.interface.roster.window)
570
		msg = None
571 572
		if show:
			uf_show = helpers.get_uf_show(show)
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
573
			self.title_text = _('%s Status Message') % uf_show
574
			msg = gajim.config.get_per('statusmsg', '_last_' + self.show,
575
									   'message')
576
			self.pep_dict['activity'] = gajim.config.get_per('statusmsg',
577
															 '_last_' + self.show, 'activity')
578
			self.pep_dict['subactivity'] = gajim.config.get_per('statusmsg',
579
																'_last_' + self.show, 'subactivity')
580
			self.pep_dict['activity_text'] = gajim.config.get_per('statusmsg',
581
																  '_last_' + self.show, 'activity_text')
582
			self.pep_dict['mood'] = gajim.config.get_per('statusmsg',
583
														 '_last_' + self.show, 'mood')
584
			self.pep_dict['mood_text'] = gajim.config.get_per('statusmsg',
585
															  '_last_' + self.show, 'mood_text')
586
		else:
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
587
			self.title_text = _('Status Message')
588
		self.dialog.set_title(self.title_text)
589

nkour's avatar
nkour committed
590 591
		message_textview = self.xml.get_widget('message_textview')
		self.message_buffer = message_textview.get_buffer()
592
		self.message_buffer.connect('changed', self.on_message_buffer_changed)
593 594
		if not msg:
			msg = ''
595
		msg = helpers.from_one_line(msg)
596
		self.message_buffer.set_text(msg)
597

598
		# have an empty string selectable, so user can clear msg
599
		self.preset_messages_dict = {'': ['', '', '', '', '', '']}
600
		for msg_name in gajim.config.get_per('statusmsg'):
601 602
			if msg_name.startswith('_last_'):
				continue
603 604
			opts = []
			for opt in ['message', 'activity', 'subactivity', 'activity_text',
605
						'mood', 'mood_text']:
606 607 608
				opts.append(gajim.config.get_per('statusmsg', msg_name, opt))
			opts[0] = helpers.from_one_line(opts[0])
			self.preset_messages_dict[msg_name] = opts
609
		sorted_keys_list = helpers.get_sorted_keys(self.preset_messages_dict)
610

611 612 613 614 615 616 617 618
		self.message_liststore = gtk.ListStore(str) # msg_name
		self.message_combobox = self.xml.get_widget('message_combobox')
		self.message_combobox.set_model(self.message_liststore)
		cellrenderertext = gtk.CellRendererText()
		self.message_combobox.pack_start(cellrenderertext, True)
		self.message_combobox.add_attribute(cellrenderertext, 'text', 0)
		for msg_name in sorted_keys_list:
			self.message_liststore.append((msg_name,))
619

620
		if show_pep:
621 622 623 624 625 626 627 628 629 630 631 632 633
			self.draw_activity()
			self.draw_mood()
		else:
			# remove acvtivity / mood lines
			self.xml.get_widget('activity_label').set_no_show_all(True)
			self.xml.get_widget('activity_button').set_no_show_all(True)
			self.xml.get_widget('mood_label').set_no_show_all(True)
			self.xml.get_widget('mood_button').set_no_show_all(True)
			self.xml.get_widget('activity_label').hide()
			self.xml.get_widget('activity_button').hide()
			self.xml.get_widget('mood_label').hide()
			self.xml.get_widget('mood_button').hide()

nkour's avatar
nkour committed
634
		self.xml.signal_autoconnect(self)
635 636 637 638
		self.run_timeout()
		self.dialog.connect('response', self.on_dialog_response)
		self.dialog.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
		self.dialog.show_all()
nkour's avatar
nkour committed
639

640 641 642 643 644
	def draw_activity(self):
		'''Set activity button'''
		img = self.xml.get_widget('activity_image')
		label = self.xml.get_widget('activity_button_label')
		if 'activity' in self.pep_dict and self.pep_dict['activity'] in \
645
		   pep.ACTIVITIES:
646
			if 'subactivity' in self.pep_dict and self.pep_dict['subactivity'] in \
647
			   pep.ACTIVITIES[self.pep_dict['activity']]:
648 649
				img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
					self.pep_dict['activity'], self.pep_dict['subactivity']).\
650
									get_pixbuf())
651 652 653
			else:
				img.set_from_pixbuf(gtkgui_helpers.load_activity_icon(
					self.pep_dict['activity']).get_pixbuf())
654
#			item.set_tooltip_text(pep.ACTIVITIES[category]['category'])
655 656
			if self.pep_dict['activity_text']:
				label.set_text(self.pep_dict['activity_text'])
657 658
			else:
				label.set_text('')
659 660 661 662 663 664 665 666 667 668 669 670 671
		else:
			img.set_from_pixbuf(None)
			label.set_text('')

	def draw_mood(self):
		'''Set mood button'''
		img = self.xml.get_widget('mood_image')
		label = self.xml.get_widget('mood_button_label')
		if self.pep_dict['mood'] in pep.MOODS:
			img.set_from_pixbuf(gtkgui_helpers.load_mood_icon(
				self.pep_dict['mood']).get_pixbuf())
			if self.pep_dict['mood_text']:
				label.set_text(self.pep_dict['mood_text'])
672 673
			else:
				label.set_text('')
674 675 676 677
		else:
			img.set_from_pixbuf(None)
			label.set_text('')

678 679 680 681
	def on_timeout(self):
		# Prevent GUI freeze when the combobox menu is opened on close
		self.message_combobox.popdown()
		self.dialog.response(gtk.RESPONSE_OK)
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
682

683 684
	def on_dialog_response(self, dialog, response):
		if response == gtk.RESPONSE_OK:
685
			beg, end = self.message_buffer.get_bounds()
686
			message = self.message_buffer.get_text(beg, end).decode('utf-8')\
687
				.strip()
688
			message = helpers.remove_invalid_xml_chars(message)
689
			msg = helpers.to_one_line(message)
690
			if self.show:
691
				gajim.config.set_per('statusmsg', '_last_' + self.show, 'message',
692
					msg)
693 694
				if self.show_pep:
					gajim.config.set_per('statusmsg', '_last_' + self.show,
695
						'activity', self.pep_dict['activity'])
696
					gajim.config.set_per('statusmsg', '_last_' + self.show,
697
						'subactivity', self.pep_dict['subactivity'])
698
					gajim.config.set_per('statusmsg', '_last_' + self.show,
699
						'activity_text', self.pep_dict['activity_text'])
700
					gajim.config.set_per('statusmsg', '_last_' + self.show, 'mood',
701
						self.pep_dict['mood'])
702
					gajim.config.set_per('statusmsg', '_last_' + self.show,
703
						'mood_text', self.pep_dict['mood_text'])
Yann Leboulanger's avatar
Yann Leboulanger committed
704
		else:
705
			message = None # user pressed Cancel button or X wm button
706
		self.dialog.destroy()
707
		self.on_response(message, self.pep_dict)
708

709
	def on_message_combobox_changed(self, widget):
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
710
		self.countdown_enabled = False
711 712 713 714
		model = widget.get_model()
		active = widget.get_active()
		if active < 0:
			return None
715
		name = model[active][0].decode('utf-8')
716 717 718 719 720 721 722 723
		self.message_buffer.set_text(self.preset_messages_dict[name][0])
		self.pep_dict['activity'] = self.preset_messages_dict[name][1]
		self.pep_dict['subactivity'] = self.preset_messages_dict[name][2]
		self.pep_dict['activity_text'] = self.preset_messages_dict[name][3]
		self.pep_dict['mood'] = self.preset_messages_dict[name][4]
		self.pep_dict['mood_text'] = self.preset_messages_dict[name][5]
		self.draw_activity()
		self.draw_mood()
724

725
	def on_change_status_message_dialog_key_press_event(self, widget, event):
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
726
		self.countdown_enabled = False
nkour's avatar
fix  
nkour committed
727
		if event.keyval == gtk.keysyms.Return or \
728
		   event.keyval == gtk.keysyms.KP_Enter: # catch CTRL+ENTER
729
			if (event.state & gtk.gdk.CONTROL_MASK):
730
				self.dialog.response(gtk.RESPONSE_OK)
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
731 732
				# Stop the event
				return True
nkour's avatar
nkour committed
733

734 735 736 737 738
	def on_message_buffer_changed(self, widget):
		self.countdown_enabled = False
		self.toggle_sensitiviy_of_save_as_preset()

	def toggle_sensitiviy_of_save_as_preset(self):
739 740 741 742 743
		btn = self.xml.get_widget('save_as_preset_button')
		if self.message_buffer.get_char_count() == 0:
			btn.set_sensitive(False)
		else:
			btn.set_sensitive(True)
744

745
	def on_save_as_preset_button_clicked(self, widget):
Piotr Gaczkowski's avatar
Piotr Gaczkowski committed
746
		self.countdown_enabled = False
747 748 749
		start_iter, finish_iter = self.message_buffer.get_bounds()
		status_message_to_save_as_preset = self.message_buffer.get_text(
			start_iter, finish_iter)
750
		def on_ok(msg_name):
751 752
			msg_text = status_message_to_save_as_preset.decode('utf-8')
			msg_text_1l = helpers.to_one_line(msg_text)
753
			if not msg_name: # msg_name was ''
754
				msg_name = msg_text_1l.decode('utf-8')
755

756 757 758
			def on_ok2():
				self.preset_messages_dict[msg_name] = [msg_text, self.pep_dict.get(
					'activity'), self.pep_dict.get('subactivity'), self.pep_dict.get(
759 760
						'activity_text'), self.pep_dict.get('mood'), self.pep_dict.get(
							'mood_text')]
761 762
				gajim.config.set_per('statusmsg', msg_name, 'message', msg_text_1l)
				gajim.config.set_per('statusmsg', msg_name, 'activity',
763
									 self.pep_dict.get('activity'))
764
				gajim.config.set_per('statusmsg', msg_name, 'subactivity',
765
									 self.pep_dict.get('subactivity'))
766
				gajim.config.set_per('statusmsg', msg_name, 'activity_text',
767
									 self.pep_dict.get('activity_text'))
768
				gajim.config.set_per('statusmsg', msg_name, 'mood',
769
									 self.pep_dict.get('mood'))
770
				gajim.config.set_per('statusmsg', msg_name, 'mood_text',
771
									 self.pep_dict.get('mood_text'))
772
			if msg_name in self.preset_messages_dict:
773
				ConfirmationDialog(_('Overwrite Status Message?'),
774 775
								   _('This name is already used. Do you want to overwrite this '
									 'status message?'), on_response_ok=on_ok2)
Yann Leboulanger's avatar
Yann Leboulanger committed
776 777
				return
			gajim.config.add_per('statusmsg', msg_name)
778 779
			on_ok2()
			iter_ = self.message_liststore.append((msg_name,))
Yann Leboulanger's avatar
Yann Leboulanger committed
780 781
			# select in combobox the one we just saved
			self.message_combobox.set_active_iter(iter_)
782
		InputDialog(_('Save as Preset Status Message'),
783 784
					_('Please type a name for this status message'), is_modal=False,
					ok_handler=on_ok)
785

786 787 788
	def on_activity_button_clicked(self, widget):
		self.countdown_enabled = False
		def on_response(activity, subactivity, text):
789 790
			self.pep_dict['activity'] = activity or ''
			self.pep_dict['subactivity'] = subactivity or ''
791 792 793
			self.pep_dict['activity_text'] = text
			self.draw_activity()
		ChangeActivityDialog(on_response, self.pep_dict['activity'],
794
							 self.pep_dict['subactivity'], self.pep_dict['activity_text'])
795 796 797 798

	def on_mood_button_clicked(self, widget):
		self.countdown_enabled = False
		def on_response(mood, text):
799
			self.pep_dict['mood'] = mood or ''