first version of Triggers plugin
[gajim.git] / plugins / triggers / triggers.py
blob5c968516ecb3e8bc4c8706427f8d5e1fcf6337d4
1 # -*- coding: utf-8 -*-
3 ## plugins/triggers/triggers.py
4 ##
5 ## Copyright (C) 2011 Yann Leboulanger <asterix AT lagaule.org>
6 ##
7 ## This file is part of Gajim.
8 ##
9 ## Gajim is free software; you can redistribute it and/or modify
10 ## it under the terms of the GNU General Public License as published
11 ## by the Free Software Foundation; version 3 only.
13 ## Gajim is distributed in the hope that it will be useful,
14 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ## GNU General Public License for more details.
18 ## You should have received a copy of the GNU General Public License
19 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
22 import gtk
23 import sys
25 from common import gajim
26 from plugins import GajimPlugin
27 from plugins.helpers import log_calls, log
28 from plugins.gui import GajimPluginConfigDialog
29 from common import i18n
30 from common import ged
31 from common import helpers
34 class Triggers(GajimPlugin):
36 @log_calls('TriggersPlugin')
37 def init(self):
38 self.config_dialog = TriggersPluginConfigDialog(self)
39 self.config_default_values = {}
41 self.events_handlers = {'notification' : (ged.PREGUI, self._nec_notif),
42 'decrypted-message-received': (ged.PREGUI2,
43 self._nec_decrypted_message_received)}
45 def _check_rule_recipients(self, obj, rule):
46 rule_recipients = [t.strip() for t in rule['recipients'].split(',')]
47 if rule['recipient_type'] == 'contact' and obj.jid not in \
48 rule_recipients:
49 return False
50 contact_groups = gajim.contacts.get_first_contact_from_jid(
51 obj.conn.name, obj.jid).groups
52 group_found = False
53 for group in contact_groups:
54 if group in rule_recipients:
55 group_found = True
56 break
57 if rule['recipient_type'] == 'group' and not group_found:
58 return False
60 return True
62 def _check_rule_status(self, obj, rule):
63 rule_statuses = rule['status'].split()
64 our_status = gajim.SHOW_LIST[obj.conn.connected]
65 if rule['status'] != 'all' and our_status not in rule_statuses:
66 return False
68 return True
70 def _check_rule_tab_opened(self, obj, rule):
71 if rule['tab_opened'] == 'both':
72 return True
73 tab_opened = False
74 if gajim.interface.msg_win_mgr.get_control(obj.jid, obj.conn.name):
75 tab_opened = True
76 if tab_opened and rule['tab_opened'] == 'no':
77 return False
78 elif not tab_opened and rule['tab_opened'] == 'yes':
79 return False
81 return True
83 def check_rule_apply_notif(self, obj, rule):
84 # Check notification type
85 notif_type = ''
86 if obj.notif_type == 'msg':
87 notif_type = 'message_received'
88 if notif_type != rule['event']:
89 return False
91 # notification type is ok. Now check recipient
92 if not self._check_rule_recipients(obj, rule):
93 return False
95 # recipient is ok. Now check our status
96 if not self._check_rule_status(obj, rule):
97 return False
99 # our_status is ok. Now check opened chat window
100 if not self._check_rule_tab_opened(obj, rule):
101 return False
103 # All is ok
104 return True
106 def check_rule_apply_decrypted_msg(self, obj, rule):
107 # Check notification type
108 if rule['event'] != 'message_received':
109 return False
111 # notification type is ok. Now check recipient
112 if not self._check_rule_recipients(obj, rule):
113 return False
115 # recipient is ok. Now check our status
116 if not self._check_rule_status(obj, rule):
117 return False
119 # our_status is ok. Now check opened chat window
120 if not self._check_rule_tab_opened(obj, rule):
121 return False
123 # All is ok
124 return True
126 def apply_rule(self, obj, rule):
127 if rule['sound'] == 'no':
128 obj.do_sound = False
129 elif rule['sound'] == 'yes':
130 obj.do_sound = True
131 obj.sound_event = ''
132 obj.sound_file = rule['sound_file']
134 if rule['popup'] == 'no':
135 obj.do_popup = False
136 elif rule['popup'] == 'yes':
137 obj.do_popup = True
139 if rule['run_command']:
140 obj.do_command = True
141 obj.command = rule['command']
142 else:
143 obj.do_command = False
145 if rule['systray'] == 'no':
146 obj.show_in_notification_area = False
147 elif rule['systray'] == 'yes':
148 obj.show_in_notification_area = True
150 if rule['roster'] == 'no':
151 obj.show_in_roster = False
152 elif rule['roster'] == 'yes':
153 obj.show_in_roster = True
155 # if rule['urgency_hint'] == 'no':
156 # ?? not in obj actions
157 # elif rule['urgency_hint'] == 'yes':
159 def _nec_notif(self, obj):
160 # check rules in order
161 rules_num = [int(i) for i in self.config.keys()]
162 rules_num.sort()
163 for num in rules_num:
164 if self.check_rule_apply_notif(obj, self.config[str(num)]):
165 self.apply_rule(obj, self.config[str(num)])
166 # Should we stop after first valid rule ?
167 # break
169 def _nec_decrypted_message_received(self, obj):
170 rules_num = [int(i) for i in self.config.keys()]
171 rules_num.sort()
172 for num in rules_num:
173 rule = self.config[str(num)]
174 if self.check_rule_apply_decrypted_msg(obj, rule):
175 if rule['auto_open'] == 'no':
176 obj.popup = False
177 elif rule['auto_open'] == 'yes':
178 obj.popup = True
181 class TriggersPluginConfigDialog(GajimPluginConfigDialog):
182 events_list = ['message_received']#, 'contact_connected',
183 #'contact_disconnected', 'contact_change_status', 'gc_msg_highlight',
184 #'gc_msg']
185 recipient_types_list = ['contact', 'group', 'all']
186 config_options = ['event', 'recipient_type', 'recipients', 'status',
187 'tab_opened', 'sound', 'sound_file', 'popup', 'auto_open',
188 'run_command', 'command', 'systray', 'roster', 'urgency_hint']
190 def init(self):
191 self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
192 'config_dialog.ui')
193 self.xml = gtk.Builder()
194 self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
195 ['vbox', 'liststore1', 'liststore2'])
196 vbox = self.xml.get_object('vbox')
197 self.child.pack_start(vbox)
199 self.xml.connect_signals(self)
200 self.connect('hide', self.on_hide)
202 def on_run(self):
203 # fill window
204 for w in ('conditions_treeview', 'config_vbox', 'event_combobox',
205 'recipient_type_combobox', 'recipient_list_entry', 'delete_button',
206 'status_hbox', 'use_sound_cb', 'disable_sound_cb', 'use_popup_cb',
207 'disable_popup_cb', 'use_auto_open_cb', 'disable_auto_open_cb',
208 'use_systray_cb', 'disable_systray_cb', 'use_roster_cb',
209 'disable_roster_cb', 'tab_opened_cb', 'not_tab_opened_cb',
210 'sound_entry', 'sound_file_hbox', 'up_button', 'down_button',
211 'run_command_cb', 'command_entry', 'use_urgency_hint_cb',
212 'disable_urgency_hint_cb'):
213 self.__dict__[w] = self.xml.get_object(w)
215 self.config = {}
216 for n in self.plugin.config:
217 self.config[int(n)] = self.plugin.config[n]
219 # Contains status checkboxes
220 childs = self.status_hbox.get_children()
222 self.all_status_rb = childs[0]
223 self.special_status_rb = childs[1]
224 self.online_cb = childs[2]
225 self.away_cb = childs[3]
226 self.xa_cb = childs[4]
227 self.dnd_cb = childs[5]
228 self.invisible_cb = childs[6]
230 if not self.conditions_treeview.get_column(0):
231 # window never opened
232 model = gtk.ListStore(int, str)
233 model.set_sort_column_id(0, gtk.SORT_ASCENDING)
234 self.conditions_treeview.set_model(model)
236 # means number
237 col = gtk.TreeViewColumn(_('#'))
238 self.conditions_treeview.append_column(col)
239 renderer = gtk.CellRendererText()
240 col.pack_start(renderer, expand=False)
241 col.set_attributes(renderer, text=0)
243 col = gtk.TreeViewColumn(_('Condition'))
244 self.conditions_treeview.append_column(col)
245 renderer = gtk.CellRendererText()
246 col.pack_start(renderer, expand=True)
247 col.set_attributes(renderer, text=1)
248 else:
249 model = self.conditions_treeview.get_model()
251 model.clear()
253 # Fill conditions_treeview
254 num = 0
255 while num in self.config:
256 iter_ = model.append((num, ''))
257 path = model.get_path(iter_)
258 self.conditions_treeview.set_cursor(path)
259 self.active_num = num
260 self.initiate_rule_state()
261 self.set_treeview_string()
262 num += 1
264 # No rule selected at init time
265 self.conditions_treeview.get_selection().unselect_all()
266 self.active_num = -1
267 self.config_vbox.set_sensitive(False)
268 self.delete_button.set_sensitive(False)
269 self.down_button.set_sensitive(False)
270 self.up_button.set_sensitive(False)
272 def initiate_rule_state(self):
274 Set values for all widgets
276 if self.active_num < 0:
277 return
278 # event
279 value = self.config[self.active_num]['event']
280 if value:
281 self.event_combobox.set_active(self.events_list.index(value))
282 else:
283 self.event_combobox.set_active(-1)
284 # recipient_type
285 value = self.config[self.active_num]['recipient_type']
286 if value:
287 self.recipient_type_combobox.set_active(
288 self.recipient_types_list.index(value))
289 else:
290 self.recipient_type_combobox.set_active(-1)
291 # recipient
292 value = self.config[self.active_num]['recipients']
293 if not value:
294 value = ''
295 self.recipient_list_entry.set_text(value)
296 # status
297 value = self.config[self.active_num]['status']
298 if value == 'all':
299 self.all_status_rb.set_active(True)
300 else:
301 self.special_status_rb.set_active(True)
302 values = value.split()
303 for v in ('online', 'away', 'xa', 'dnd', 'invisible'):
304 if v in values:
305 self.__dict__[v + '_cb'].set_active(True)
306 else:
307 self.__dict__[v + '_cb'].set_active(False)
308 self.on_status_radiobutton_toggled(self.all_status_rb)
310 # tab_opened
311 value = self.config[self.active_num]['tab_opened']
312 self.tab_opened_cb.set_active(True)
313 self.not_tab_opened_cb.set_active(True)
314 if value == 'no':
315 self.tab_opened_cb.set_active(False)
316 elif value == 'yes':
317 self.not_tab_opened_cb.set_active(False)
319 # sound_file
320 value = self.config[self.active_num]['sound_file']
321 self.sound_entry.set_text(value)
323 # sound, popup, auto_open, systray, roster
324 for option in ('sound', 'popup', 'auto_open', 'systray', 'roster',
325 'urgency_hint'):
326 value = self.config[self.active_num][option]
327 if value == 'yes':
328 self.__dict__['use_' + option + '_cb'].set_active(True)
329 else:
330 self.__dict__['use_' + option + '_cb'].set_active(False)
331 if value == 'no':
332 self.__dict__['disable_' + option + '_cb'].set_active(True)
333 else:
334 self.__dict__['disable_' + option + '_cb'].set_active(False)
336 # run_command
337 value = self.config[self.active_num]['run_command']
338 self.run_command_cb.set_active(value)
340 # command
341 value = self.config[self.active_num]['command']
342 self.command_entry.set_text(value)
344 def set_treeview_string(self):
345 (model, iter_) = self.conditions_treeview.get_selection().get_selected()
346 if not iter_:
347 return
348 event = self.event_combobox.get_active_text()
349 recipient_type = self.recipient_type_combobox.get_active_text()
350 recipient = ''
351 if recipient_type != 'everybody':
352 recipient = self.recipient_list_entry.get_text()
353 if self.all_status_rb.get_active():
354 status = ''
355 else:
356 status = _('when I am ')
357 for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
358 if self.__dict__[st + '_cb'].get_active():
359 status += helpers.get_uf_show(st) + ' '
360 model[iter_][1] = "When %s for %s %s %s" % (event, recipient_type,
361 recipient, status)
363 def on_conditions_treeview_cursor_changed(self, widget):
364 (model, iter_) = widget.get_selection().get_selected()
365 if not iter_:
366 self.active_num = ''
367 return
368 self.active_num = model[iter_][0]
369 if self.active_num == '0':
370 self.up_button.set_sensitive(False)
371 else:
372 self.up_button.set_sensitive(True)
373 max = self.conditions_treeview.get_model().iter_n_children(None)
374 if self.active_num == max - 1:
375 self.down_button.set_sensitive(False)
376 else:
377 self.down_button.set_sensitive(True)
378 self.initiate_rule_state()
379 self.config_vbox.set_sensitive(True)
380 self.delete_button.set_sensitive(True)
382 def on_new_button_clicked(self, widget):
383 model = self.conditions_treeview.get_model()
384 num = self.conditions_treeview.get_model().iter_n_children(None)
385 self.config[num] = {'event': '', 'recipient_type': 'all',
386 'recipients': '', 'status': 'all', 'tab_opened': 'both',
387 'sound': '', 'sound_file': '', 'popup': '', 'auto_open': '',
388 'run_command': False, 'command': '', 'systray': '', 'roster': '',
389 'urgency_hint': False}
390 iter_ = model.append((num, ''))
391 path = model.get_path(iter_)
392 self.conditions_treeview.set_cursor(path)
393 self.active_num = num
394 self.set_treeview_string()
395 self.config_vbox.set_sensitive(True)
397 def on_delete_button_clicked(self, widget):
398 (model, iter_) = self.conditions_treeview.get_selection().get_selected()
399 if not iter_:
400 return
401 # up all others
402 iter2 = model.iter_next(iter_)
403 num = self.active_num
404 while iter2:
405 num = model[iter2][0]
406 model[iter2][0] = num - 1
407 self.config[num-1] = self.config[num].copy()
408 iter2 = model.iter_next(iter2)
409 model.remove(iter_)
410 del self.config[num]
411 self.active_num = ''
412 self.config_vbox.set_sensitive(False)
413 self.delete_button.set_sensitive(False)
414 self.up_button.set_sensitive(False)
415 self.down_button.set_sensitive(False)
417 def on_up_button_clicked(self, widget):
418 (model, iter_) = self.conditions_treeview.get_selection().get_selected()
419 if not iter_:
420 return
421 conf = self.config[self.active_num].copy()
422 self.config[self.active_num] = self.config[self.active_num - 1]
423 self.config[self.active_num - 1] = conf
425 model[iter_][0] =self.active_num - 1
426 # get previous iter
427 path = model.get_path(iter_)
428 iter_ = model.get_iter((path[0] - 1,))
429 model[iter_][0] = self.active_num
430 self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
432 def on_down_button_clicked(self, widget):
433 (model, iter_) = self.conditions_treeview.get_selection().get_selected()
434 if not iter_:
435 return
436 conf = self.config[self.active_num].copy()
437 self.config[self.active_num] = self.config[self.active_num + 1]
438 self.config[self.active_num + 1] = conf
440 model[iter_][0] = self.active_num + 1
441 iter_ = model.iter_next(iter_)
442 model[iter_][0] = self.active_num
443 self.on_conditions_treeview_cursor_changed(self.conditions_treeview)
445 def on_event_combobox_changed(self, widget):
446 if self.active_num < 0:
447 return
448 active = self.event_combobox.get_active()
449 if active == -1:
450 event = ''
451 else:
452 event = self.events_list[active]
453 self.config[self.active_num]['event'] = event
454 self.set_treeview_string()
456 def on_recipient_type_combobox_changed(self, widget):
457 if self.active_num < 0:
458 return
459 recipient_type = self.recipient_types_list[
460 self.recipient_type_combobox.get_active()]
461 self.config[self.active_num]['recipient_type'] = recipient_type
462 if recipient_type == 'all':
463 self.recipient_list_entry.hide()
464 else:
465 self.recipient_list_entry.show()
466 self.set_treeview_string()
468 def on_recipient_list_entry_changed(self, widget):
469 if self.active_num < 0:
470 return
471 recipients = widget.get_text().decode('utf-8')
472 #TODO: do some check
473 self.config[self.active_num]['recipients'] = recipients
474 self.set_treeview_string()
476 def set_status_config(self):
477 if self.active_num < 0:
478 return
479 status = ''
480 for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
481 if self.__dict__[st + '_cb'].get_active():
482 status += st + ' '
483 if status:
484 status = status[:-1]
485 self.config[self.active_num]['status'] = status
486 self.set_treeview_string()
488 def on_status_radiobutton_toggled(self, widget):
489 if self.active_num < 0:
490 return
491 if self.all_status_rb.get_active():
492 self.config[self.active_num]['status'] = 'all'
493 # 'All status' clicked
494 for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
495 self.__dict__[st + '_cb'].hide()
497 self.special_status_rb.show()
498 else:
499 self.set_status_config()
500 # 'special status' clicked
501 for st in ('online', 'away', 'xa', 'dnd', 'invisible'):
502 self.__dict__[st + '_cb'].show()
504 self.special_status_rb.hide()
505 self.set_treeview_string()
507 def on_status_cb_toggled(self, widget):
508 if self.active_num < 0:
509 return
510 self.set_status_config()
512 # tab_opened OR (not xor) not_tab_opened must be active
513 def on_tab_opened_cb_toggled(self, widget):
514 if self.active_num < 0:
515 return
516 if self.tab_opened_cb.get_active():
517 if self.not_tab_opened_cb.get_active():
518 self.config[self.active_num]['tab_opened'] = 'both'
519 else:
520 self.config[self.active_num]['tab_opened'] = 'yes'
521 else:
522 self.not_tab_opened_cb.set_active(True)
523 self.config[self.active_num]['tab_opened'] = 'no'
525 def on_not_tab_opened_cb_toggled(self, widget):
526 if self.active_num < 0:
527 return
528 if self.not_tab_opened_cb.get_active():
529 if self.tab_opened_cb.get_active():
530 self.config[self.active_num]['tab_opened'] = 'both'
531 else:
532 self.config[self.active_num]['tab_opened'] = 'no'
533 else:
534 self.tab_opened_cb.set_active(True)
535 self.config[self.active_num]['tab_opened'] = 'yes'
537 def on_use_it_toggled(self, widget, oposite_widget, option):
538 if widget.get_active():
539 if oposite_widget.get_active():
540 oposite_widget.set_active(False)
541 self.config[self.active_num][option] = 'yes'
542 elif oposite_widget.get_active():
543 self.config[self.active_num][option] = 'no'
544 else:
545 self.config[self.active_num][option] = ''
547 def on_disable_it_toggled(self, widget, oposite_widget, option):
548 if widget.get_active():
549 if oposite_widget.get_active():
550 oposite_widget.set_active(False)
551 self.config[self.active_num][option] = 'no'
552 elif oposite_widget.get_active():
553 self.config[self.active_num][option] = 'yes'
554 else:
555 self.config[self.active_num][option] = ''
557 def on_use_sound_cb_toggled(self, widget):
558 self.on_use_it_toggled(widget, self.disable_sound_cb, 'sound')
559 if widget.get_active():
560 self.sound_file_hbox.set_sensitive(True)
561 else:
562 self.sound_file_hbox.set_sensitive(False)
564 def on_browse_for_sounds_button_clicked(self, widget, data=None):
565 if self.active_num < 0:
566 return
568 def on_ok(widget, path_to_snd_file):
569 dialog.destroy()
570 if not path_to_snd_file:
571 path_to_snd_file = ''
572 self.config[self.active_num]['sound_file'] = path_to_snd_file
573 self.sound_entry.set_text(path_to_snd_file)
575 path_to_snd_file = self.sound_entry.get_text().decode('utf-8')
576 path_to_snd_file = os.path.join(os.getcwd(), path_to_snd_file)
577 dialog = SoundChooserDialog(path_to_snd_file, on_ok)
579 def on_play_button_clicked(self, widget):
580 helpers.play_sound_file(self.sound_entry.get_text().decode('utf-8'))
582 def on_disable_sound_cb_toggled(self, widget):
583 self.on_disable_it_toggled(widget, self.use_sound_cb, 'sound')
585 def on_sound_entry_changed(self, widget):
586 self.config[self.active_num]['sound_file'] = widget.get_text().\
587 decode('utf-8')
589 def on_use_popup_cb_toggled(self, widget):
590 self.on_use_it_toggled(widget, self.disable_popup_cb, 'popup')
592 def on_disable_popup_cb_toggled(self, widget):
593 self.on_disable_it_toggled(widget, self.use_popup_cb, 'popup')
595 def on_use_auto_open_cb_toggled(self, widget):
596 self.on_use_it_toggled(widget, self.disable_auto_open_cb, 'auto_open')
598 def on_disable_auto_open_cb_toggled(self, widget):
599 self.on_disable_it_toggled(widget, self.use_auto_open_cb, 'auto_open')
601 def on_run_command_cb_toggled(self, widget):
602 self.config[self.active_num]['run_command'] = widget.get_active()
603 if widget.get_active():
604 self.command_entry.set_sensitive(True)
605 else:
606 self.command_entry.set_sensitive(False)
608 def on_command_entry_changed(self, widget):
609 self.config[self.active_num]['command'] = widget.get_text().\
610 decode('utf-8')
612 def on_use_systray_cb_toggled(self, widget):
613 self.on_use_it_toggled(widget, self.disable_systray_cb, 'systray')
615 def on_disable_systray_cb_toggled(self, widget):
616 self.on_disable_it_toggled(widget, self.use_systray_cb, 'systray')
618 def on_use_roster_cb_toggled(self, widget):
619 self.on_use_it_toggled(widget, self.disable_roster_cb, 'roster')
621 def on_disable_roster_cb_toggled(self, widget):
622 self.on_disable_it_toggled(widget, self.use_roster_cb, 'roster')
624 def on_use_urgency_hint_cb_toggled(self, widget):
625 self.on_use_it_toggled(widget, self.disable_urgency_hint_cb,
626 'uregency_hint')
628 def on_disable_urgency_hint_cb_toggled(self, widget):
629 self.on_disable_it_toggled(widget, self.use_urgency_hint_cb,
630 'uregency_hint')
632 def on_hide(self, widget):
633 # save config
634 for n in self.plugin.config:
635 del self.plugin.config[n]
636 for n in self.config:
637 self.plugin.config[str(n)] = self.config[n]