def __init__(self, model, view, parent): super(SMSContactsController, self).__init__(model, view) self.parent = parent self.category = None self.search_entry = IconEntry() self.search_entry_cid = None self.cts_completion = None self.sms_completion = None self.treeview_index = None self.signal_matches = []
class SMSContactsController(Controller): """ Controller for the SMS and Contacts window """ def __init__(self, model, view, parent): super(SMSContactsController, self).__init__(model, view) self.parent = parent self.category = None self.search_entry = IconEntry() self.search_entry_cid = None self.cts_completion = None self.sms_completion = None self.treeview_index = None self.signal_matches = [] def register_view(self, view): super(SMSContactsController, self).register_view(view) self.init_ui() self.connect_to_signals() self.setup_completions() def connect_to_signals(self): self.view.get_top_widget().connect("delete_event", self.on_delete_event_cb) # treeview self.view["treeview2"].connect("key_press_event", self.on_treeview2_key_press_event) # search entry stuff try: self.search_entry.connect("icon-pressed", self.on_search_entry_clear_cb) except TypeError: # python-sexy is not installed, ignore pass # connect to SIG_SMS and add SMS to model sm = self.model.device.connect_to_signal(SIG_SMS, self.on_sms_received_cb) self.signal_matches.append(sm) def init_ui(self): model = self.model.get_categories_model() self.view.init_categories_treeview(model) treeview = self.view["treeview1"] sel = treeview.get_selection() sel.connect("changed", self.on_categories_treeview_changed) # expand sections by default _iter = model.get_iter_first() while _iter: path = model.get_path(_iter) treeview.expand_row(path, True) _iter = model.iter_next(_iter) # delete_toolbutton should only be enabled if there's # a selected row self.view["delete_toolbutton"].set_sensitive(False) def setup_completions(self): self.cts_completion = gtk.EntryCompletion() self.sms_completion = gtk.EntryCompletion() for completion in [self.cts_completion, self.sms_completion]: completion.set_property("popup-set-width", False) completion.set_property("minimum-key-length", 0) completion.set_inline_selection(True) completion.set_popup_single_match(True) # contacts completion self.cts_completion.connect("match-selected", self.on_search_entry_match_cb) # sms completion if gtk.pygtk_version >= (2, 14, 0): def sms_match_func(completion, key, _iter): model = completion.get_model() text = model.get_value(_iter, model.COL_TEXT) return key in text self.sms_completion.set_match_func(sms_match_func) self.sms_completion.connect("match-selected", self.on_search_entry_match_cb) # callbacks and functionality def on_delete_event_cb(self, *args): while self.signal_matches: sm = self.signal_matches.pop() sm.remove() self.close_controller() def on_quit_menuitem_activate(self, widget): self.on_delete_event_cb(widget) def on_new_toolbutton_clicked(self, widget): if self.treeview_index == SMS_TAB: self._new_sms() else: self._new_contact() def on_delete_toolbutton_clicked(self, widget): selected = self._get_selected_objects(self.view["treeview2"]) model = selected["model"] if self.treeview_index == SMS_TAB: delete_func = self._delete_sms else: delete_func = self._delete_contact map(delete_func, selected["objs"]) map(model.remove, selected["iters"]) def _new_contact(self): model = ContactsModel(device=self.model.device) view = ContactsView() ctrl = ContactsController(model, view, parent=self) view.set_parent_view(self.view) view.show() def _new_sms(self): model = SMSModel(self.model.device) view = SMSView() ctrl = SMSController(model, view, self) view.set_parent_view(self.view) view.show() def _delete_contact(self, contact): self.model.device.Delete( contact.index, dbus_interface=CTS_INTFACE, reply_handler=lambda: None, error_handler=logger.error ) def _delete_sms(self, sms): self.model.device.Delete( sms.index, dbus_interface=SMS_INTFACE, reply_handler=lambda: None, error_handler=logger.error ) # Search entry callbacks def on_search_entry_clear_cb(self, entry, icon_pos, button): entry.set_text("") def on_search_entry_match_cb(self, completion, filtermodel, _iter): treeview = self.view["treeview2"] sel = treeview.get_selection() sel.unselect_all() # convert the iter childiter = filtermodel.convert_iter_to_child_iter(_iter) # select path in original model model = filtermodel.get_model() sel.select_path(model.get_path(childiter)) # DBus callbacks def on_sms_received_cb(self, index, complete): """ Executed whenever a new SMS is received It will append the SMS to the treeview model """ # XXX: Handle complete argument # only process it if we're in SMS mode if self.treeview_index == SMS_TAB: def process_sms_eb(error): title = _("Error reading SMS %d") % index dialogs.show_error_dialog(title, get_error_msg(error)) def process_sms_cb(sms): model = self.view["treeview2"].get_model() if isinstance(model, gtk.TreeModelFilter): model = model.get_model() _iter = model.add_sms(sms) # XXX: This used to work before without any further # explicit action :S path = model.get_path(_iter) # notify the treeview about the new row model.row_inserted(path, _iter) # read the SMS and show it to the user self.model.device.Get( index, dbus_interface=SMS_INTFACE, reply_handler=process_sms_cb, error_handler=process_sms_eb ) # Treeviews callbacks and functionality def get_selected_row(self, treeview=None): """ Returns the selected row in ``treeview`` """ if treeview is None: treeview = self.view["treeview2"] col = treeview.get_cursor()[0] model = treeview.get_model() return model[col] def on_treeview2_row_activated(self, treeview, path, col): model = treeview.get_model() if isinstance(model, ContactsModel): # we'll ignore activated events in the contact model # as the way to edit contacts is straight from treeview return if isinstance(model, gtk.TreeModelFilter): model = model.get_model() row = self.get_selected_row(treeview) obj = row[model.COL_OBJECT] m = SMSModel(self.model.device) ctrl = SMSController(m, self) view = SMSView(ctrl) view.set_parent_view(self.view) view.show() view.set_text(obj.text) def on_treeview2_cursor_changed(self, treeview): model, selected = treeview.get_selection().get_selected_rows() self.view["delete_toolbutton"].set_sensitive(len(selected) > 0) def on_categories_treeview_changed(self, selection): """ executed whenever the categories treeview focus changes """ model, _iter = selection.get_selected() cat = model.get(_iter, model.COL_OBJECT)[0] if cat.name == "SMS" or model.is_ancestor(model.sms_iter, _iter): index = SMS_TAB else: index = CTS_TAB self.view.set_visible_func(cat.visible_func) if self.treeview_index == index: # do not request a new *.List command if its not necessary return self.treeview_index = index # potentially long operation, show throbber self.view.start_throbber() if self.treeview_index == SMS_TAB: self.model.device.List( dbus_interface=SMS_INTFACE, reply_handler=self.on_sms_list_cb, error_handler=self.on_sms_list_eb ) self.category = cat elif self.treeview_index == CTS_TAB: self.model.device.List( dbus_interface=CTS_INTFACE, reply_handler=self.on_contacts_list_cb, error_handler=self.on_contacts_list_eb, ) # there's no contact/SMS selected, so make it not sensitive self.view["delete_toolbutton"].set_sensitive(False) def on_contact_name_cell_edited(self, widget, path, newname): model = self.view["treeview2"].get_model() if newname != model[path][model.COL_NAME] and newname != "": model[path][model.COL_NAME] = newname contact = model[path][model.COL_OBJECT] contact.name = newname self.model.device.Edit( contact.index, newname, contact.number, dbus_interface=CTS_INTFACE, reply_handler=self.on_contact_edited_cb, error_handler=logger.error, ) def on_contact_number_cell_edited(self, widget, path, newnumber): model = self.view["treeview2"].get_model() if newnumber != model[path][model.COL_NUMBER] and newnumber != "": model[path][model.COL_NUMBER] = newnumber contact = model[path][model.COL_OBJECT] contact.number = newnumber self.model.device.Edit( contact.index, contact.name, newnumber, dbus_interface=CTS_INTFACE, reply_handler=self.on_contact_edited_cb, error_handler=logger.error, ) # actions callbacks def on_sms_list_cb(self, sms): """ Callback for org.freedesktop.ModemManager.Gsm.SMS.List Loads ``sms`` in the model substituting known numbers by its contact name """ def setup_widgets(model): self.sms_completion.clear() self.sms_completion.set_model(model) self.search_entry.set_completion(self.sms_completion) if self.category is not None: self.view.set_visible_func(self.category.visible_func) self.category = None def on_contacts_list_cb(contacts): model = self.model.get_sms_model(sms, contacts) self.view.load_sms_model(model) setup_widgets(model) # end of potentially long operation self.view.stop_throbber() def on_contacts_list_eb(error): """ An error has occurred while getting the contacts, ignore 'em """ model = self.model.get_sms_model(sms) self.view.load_sms_model(model) setup_widgets(model) # end of potentially long operation self.view.stop_throbber() if sms: self.model.device.List( dbus_interface=CTS_INTFACE, reply_handler=on_contacts_list_cb, error_handler=on_contacts_list_eb ) else: # do not Contacts.List if there are no SMS to process model = self.model.get_sms_model(sms) self.view.load_sms_model(model) setup_widgets(model) self.view.stop_throbber() def on_sms_list_eb(self, error): """ Errback for org.freedesktop.ModemManager.Gsm.SMS.List Show an error message to the user in case something goes bad """ # end of potentially long operation self.view.stop_throbber() title = _("Error while reading SMS list") dialogs.show_error_dialog(title, get_error_msg(error)) def on_contacts_list_cb(self, contacts): """ Callback for org.freedesktop.ModemManager.Gsm.Contacts.List Loads ``contacts`` in the model, taking care of previous signal handlers """ model = self.model.get_contacts_model(contacts) self.view.load_contacts_model(model, self) self.cts_completion.clear() self.cts_completion.set_text_column(model.COL_NAME) self.cts_completion.set_model(model) self.search_entry.set_completion(self.cts_completion) # end of potentially long operation self.view.stop_throbber() def on_contacts_list_eb(self, error): """ Errback for org.freedesktop.ModemManager.Gsm.Contacts.List Show an error message to the user in case something goes bad """ # end of potentially long operation self.view.stop_throbber() title = _("Error while reading contacts list") dialogs.show_error_dialog(title, get_error_msg(error)) def on_contact_edited_cb(self, index): """ executed when the contact has been edited successfully """ def on_treeview2_button_press_event(self, treeview, event): if event.button == 3 and event.type == gtk.gdk.BUTTON_PRESS: model, pathlist = treeview.get_selection().get_selected_rows() if pathlist: if self.treeview_index == CTS_TAB: menu = self._build_contact_menu(pathlist, treeview) else: menu = self._build_sms_menu(pathlist, treeview) menu.popup(None, None, None, event.button, event.time) return True # selection is lost otherwise def on_treeview2_key_press_event(self, treeview, event): # key Del was pressed if event.keyval == 65535: model, pathlist = treeview.get_selection().get_selected_rows() if pathlist: self._delete_rows(None, pathlist, treeview) # helpers def _get_selected_objects(self, treeview): m, selected = treeview.get_selection().get_selected_rows() # we might deal with a TreeModelFilter selection = selected if isinstance(m, gtk.TreeModelFilter): selection = map(m.convert_path_to_child_path, selected) m = m.get_model() iters = map(m.get_iter, selection) objs = [m.get_value(_iter, m.COL_OBJECT) for _iter in iters] return dict(objs=objs, iters=iters, model=m) def _delete_rows(self, menuitem, pathlist, treeview): selected = self._get_selected_objects(treeview) m = selected["model"] intface = CTS_INTFACE if isinstance(m, ContactsModel) else SMS_INTFACE for obj in selected["objs"]: self.model.device.Delete(obj.index, dbus_interface=intface) for _iter in selected["iters"]: m.remove(_iter) def _build_contact_menu(self, pathlist, treeview): menu = gtk.Menu() item = gtk.MenuItem(_("_Send SMS")) item.connect("activate", self._send_sms_to_contact, treeview) item.show() menu.append(item) item = gtk.ImageMenuItem(_("_Delete")) img = gtk.image_new_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_MENU) item.set_image(img) item.connect("activate", self._delete_rows, pathlist, treeview) item.show() menu.append(item) return menu def _build_sms_menu(self, pathlist, treeview): selected = self._get_selected_objects(treeview) menu = gtk.Menu() if len(selected["objs"]) == 1: # only show reply or send from storage when only one SMS is sel obj = selected["objs"][0] item = None if obj.where <= STO_INBOX: item = gtk.MenuItem(_("_Reply")) callback = self._send_sms_to_contact elif obj.where == STO_DRAFTS: item = gtk.MenuItem(_("_Send")) callback = self._send_sms_from_storage elif obj.where == STO_SENT: # if the SMS is in STO_SENT then only deleting is allowed pass if item: item.connect("activate", callback, treeview) item.show() menu.append(item) item = gtk.ImageMenuItem(_("_Delete")) img = gtk.image_new_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_MENU) item.set_image(img) item.connect("activate", self._delete_rows, pathlist, treeview) item.show() menu.append(item) return menu def _send_sms_from_storage(self, menuitem, treeview): model = SMSModel(self.model.device) view = SMSView() ctrl = SMSController(model, view, self, mode=STORAGE) view.set_parent_view(self.view) selected = self._get_selected_objects(treeview) ctrl.set_message_to_send(selected) view.show() def _send_sms_to_contact(self, menuitem, treeview): selected = self._get_selected_objects(treeview) model = SMSModel(self.model.device) view = SMSView() ctrl = SMSController(model, view, self) view.set_parent_view(self.view) map(view.add_contact, selected["objs"]) view.show()