Exemplo n.º 1
0
    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 = []
Exemplo n.º 2
0
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()