Providing focus to the lists for faster search
[gc-dialer] / src / dialogs.py
index 4aa884c..e14582c 100644 (file)
@@ -10,6 +10,7 @@ import logging
 from PyQt4 import QtGui
 from PyQt4 import QtCore
 
+import constants
 from util import qui_utils
 from util import misc as misc_utils
 
@@ -71,6 +72,66 @@ class CredentialsDialog(object):
                finally:
                        self._dialog.setParent(None, QtCore.Qt.Dialog)
 
+       def close(self):
+               self._dialog.reject()
+
+       @QtCore.pyqtSlot()
+       @QtCore.pyqtSlot(bool)
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_close_window(self, checked = True):
+               self._dialog.reject()
+
+
+class AboutDialog(object):
+
+       def __init__(self, app):
+               self._title = QtGui.QLabel(
+                       "<h1>%s</h1><h3>Version: %s</h3>" % (
+                               constants.__pretty_app_name__, constants.__version__
+                       )
+               )
+               self._title.setTextFormat(QtCore.Qt.RichText)
+               self._title.setAlignment(QtCore.Qt.AlignCenter)
+               self._copyright = QtGui.QLabel("<h6>Developed by Ed Page<h6><h6>Icons: See website</h6>")
+               self._copyright.setTextFormat(QtCore.Qt.RichText)
+               self._copyright.setAlignment(QtCore.Qt.AlignCenter)
+               self._link = QtGui.QLabel('<a href="http://gc-dialer.garage.maemo.org">DialCentral Website</a>')
+               self._link.setTextFormat(QtCore.Qt.RichText)
+               self._link.setAlignment(QtCore.Qt.AlignCenter)
+               self._link.setOpenExternalLinks(True)
+
+               self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
+
+               self._layout = QtGui.QVBoxLayout()
+               self._layout.addWidget(self._title)
+               self._layout.addWidget(self._copyright)
+               self._layout.addWidget(self._link)
+               self._layout.addWidget(self._buttonLayout)
+
+               self._dialog = QtGui.QDialog()
+               self._dialog.setWindowTitle("About")
+               self._dialog.setLayout(self._layout)
+               qui_utils.set_autorient(self._dialog, True)
+               self._buttonLayout.rejected.connect(self._dialog.reject)
+
+               self._closeWindowAction = QtGui.QAction(None)
+               self._closeWindowAction.setText("Close")
+               self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
+               self._closeWindowAction.triggered.connect(self._on_close_window)
+
+               self._dialog.addAction(self._closeWindowAction)
+               self._dialog.addAction(app.quitAction)
+               self._dialog.addAction(app.fullscreenAction)
+
+       def run(self, parent=None):
+               self._dialog.setParent(parent, QtCore.Qt.Dialog)
+
+               response = self._dialog.exec_()
+               return response
+
+       def close(self):
+               self._dialog.reject()
+
        @QtCore.pyqtSlot()
        @QtCore.pyqtSlot(bool)
        @misc_utils.log_exception(_moduleLogger)
@@ -82,24 +143,60 @@ class AccountDialog(object):
 
        # @bug Can't enter custom callback numbers
 
+       _RECURRENCE_CHOICES = [
+               (1, "1 minute"),
+               (2, "2 minutes"),
+               (3, "3 minutes"),
+               (5, "5 minutes"),
+               (8, "8 minutes"),
+               (10, "10 minutes"),
+               (15, "15 minutes"),
+               (30, "30 minutes"),
+               (45, "45 minutes"),
+               (60, "1 hour"),
+               (3*60, "3 hours"),
+               (6*60, "6 hours"),
+               (12*60, "12 hours"),
+       ]
+
        def __init__(self, app):
                self._doClear = False
 
                self._accountNumberLabel = QtGui.QLabel("NUMBER NOT SET")
+               self._notificationButton = QtGui.QCheckBox("Notifications")
+               self._notificationButton.stateChanged.connect(self._on_notification_change)
+               self._notificationTimeSelector = QtGui.QComboBox()
+               #self._notificationTimeSelector.setEditable(True)
+               self._notificationTimeSelector.setInsertPolicy(QtGui.QComboBox.InsertAtTop)
+               for _, label in self._RECURRENCE_CHOICES:
+                       self._notificationTimeSelector.addItem(label)
+               self._missedCallsNotificationButton = QtGui.QCheckBox("Missed Calls")
+               self._voicemailNotificationButton = QtGui.QCheckBox("Voicemail")
+               self._smsNotificationButton = QtGui.QCheckBox("SMS")
                self._clearButton = QtGui.QPushButton("Clear Account")
                self._clearButton.clicked.connect(self._on_clear)
-
                self._callbackSelector = QtGui.QComboBox()
                #self._callbackSelector.setEditable(True)
                self._callbackSelector.setInsertPolicy(QtGui.QComboBox.InsertAtTop)
 
+               self._update_notification_state()
+
                self._credLayout = QtGui.QGridLayout()
                self._credLayout.addWidget(QtGui.QLabel("Account"), 0, 0)
                self._credLayout.addWidget(self._accountNumberLabel, 0, 1)
                self._credLayout.addWidget(QtGui.QLabel("Callback"), 1, 0)
                self._credLayout.addWidget(self._callbackSelector, 1, 1)
-               self._credLayout.addWidget(QtGui.QLabel(""), 2, 0)
-               self._credLayout.addWidget(self._clearButton, 2, 1)
+               self._credLayout.addWidget(self._notificationButton, 2, 0)
+               self._credLayout.addWidget(self._notificationTimeSelector, 2, 1)
+               self._credLayout.addWidget(QtGui.QLabel(""), 3, 0)
+               self._credLayout.addWidget(self._missedCallsNotificationButton, 3, 1)
+               self._credLayout.addWidget(QtGui.QLabel(""), 4, 0)
+               self._credLayout.addWidget(self._voicemailNotificationButton, 4, 1)
+               self._credLayout.addWidget(QtGui.QLabel(""), 5, 0)
+               self._credLayout.addWidget(self._smsNotificationButton, 5, 1)
+
+               self._credLayout.addWidget(QtGui.QLabel(""), 6, 0)
+               self._credLayout.addWidget(self._clearButton, 6, 1)
                self._credLayout.addWidget(QtGui.QLabel(""), 3, 0)
 
                self._loginButton = QtGui.QPushButton("&Apply")
@@ -111,7 +208,7 @@ class AccountDialog(object):
                self._layout.addWidget(self._buttonLayout)
 
                self._dialog = QtGui.QDialog()
-               self._dialog.setWindowTitle("Login")
+               self._dialog.setWindowTitle("Account")
                self._dialog.setLayout(self._layout)
                qui_utils.set_autorient(self._dialog, True)
                self._buttonLayout.accepted.connect(self._dialog.accept)
@@ -130,11 +227,60 @@ class AccountDialog(object):
        def doClear(self):
                return self._doClear
 
+       def setIfNotificationsSupported(self, isSupported):
+               if isSupported:
+                       self._notificationButton.setVisible(True)
+                       self._notificationTimeSelector.setVisible(True)
+                       self._missedCallsNotificationButton.setVisible(True)
+                       self._voicemailNotificationButton.setVisible(True)
+                       self._smsNotificationButton.setVisible(True)
+               else:
+                       self._notificationButton.setVisible(False)
+                       self._notificationTimeSelector.setVisible(False)
+                       self._missedCallsNotificationButton.setVisible(False)
+                       self._voicemailNotificationButton.setVisible(False)
+                       self._smsNotificationButton.setVisible(False)
+
        accountNumber = property(
                lambda self: str(self._accountNumberLabel.text()),
                lambda self, num: self._accountNumberLabel.setText(num),
        )
 
+       notifications = property(
+               lambda self: self._notificationButton.isChecked(),
+               lambda self, enabled: self._notificationButton.setChecked(enabled),
+       )
+
+       notifyOnMissed = property(
+               lambda self: self._missedCallsNotificationButton.isChecked(),
+               lambda self, enabled: self._missedCallsNotificationButton.setChecked(enabled),
+       )
+
+       notifyOnVoicemail = property(
+               lambda self: self._voicemailNotificationButton.isChecked(),
+               lambda self, enabled: self._voicemailNotificationButton.setChecked(enabled),
+       )
+
+       notifyOnSms = property(
+               lambda self: self._smsNotificationButton.isChecked(),
+               lambda self, enabled: self._smsNotificationButton.setChecked(enabled),
+       )
+
+       def _get_notification_time(self):
+               index = self._notificationTimeSelector.currentIndex()
+               minutes = self._RECURRENCE_CHOICES[index][0]
+               return minutes
+
+       def _set_notification_time(self, minutes):
+               for i, (time, _) in enumerate(self._RECURRENCE_CHOICES):
+                       if time == minutes:
+                               self._callbackSelector.setCurrentIndex(i)
+                               break
+               else:
+                               self._callbackSelector.setCurrentIndex(0)
+
+       notificationTime = property(_get_notification_time, _set_notification_time)
+
        @property
        def selectedCallback(self):
                index = self._callbackSelector.currentIndex()
@@ -144,8 +290,10 @@ class AccountDialog(object):
        def set_callbacks(self, choices, default):
                self._callbackSelector.clear()
 
+               self._callbackSelector.addItem("Not Set", "")
+
                uglyDefault = misc_utils.make_ugly(default)
-               for i, (number, description) in enumerate(choices.iteritems()):
+               for number, description in choices.iteritems():
                        prettyNumber = misc_utils.make_pretty(number)
                        uglyNumber = misc_utils.make_ugly(number)
                        if not uglyNumber:
@@ -153,16 +301,34 @@ class AccountDialog(object):
 
                        self._callbackSelector.addItem("%s - %s" % (prettyNumber, description), uglyNumber)
                        if uglyNumber == uglyDefault:
-                               self._callbackSelector.setCurrentIndex(i)
-
+                               self._callbackSelector.setCurrentIndex(self._callbackSelector.count() - 1)
 
        def run(self, parent=None):
                self._doClear = False
-               self._dialog.setParent(parent)
+               self._dialog.setParent(parent, QtCore.Qt.Dialog)
 
                response = self._dialog.exec_()
                return response
 
+       def close(self):
+               self._dialog.reject()
+
+       def _update_notification_state(self):
+               if self._notificationButton.isChecked():
+                       self._notificationTimeSelector.setEnabled(True)
+                       self._missedCallsNotificationButton.setEnabled(True)
+                       self._voicemailNotificationButton.setEnabled(True)
+                       self._smsNotificationButton.setEnabled(True)
+               else:
+                       self._notificationTimeSelector.setEnabled(False)
+                       self._missedCallsNotificationButton.setEnabled(False)
+                       self._voicemailNotificationButton.setEnabled(False)
+                       self._smsNotificationButton.setEnabled(False)
+
+       @QtCore.pyqtSlot(int)
+       def _on_notification_change(self, state):
+               self._update_notification_state()
+
        @QtCore.pyqtSlot()
        @QtCore.pyqtSlot(bool)
        def _on_clear(self, checked = False):
@@ -221,6 +387,8 @@ class SMSEntryWindow(object):
 
                self._characterCountLabel = QtGui.QLabel("0 (0)")
                self._singleNumberSelector = QtGui.QComboBox()
+               self._singleNumbersCID = None
+               self._singleNumberSelector.activated.connect(self._on_single_change_number)
                self._smsButton = QtGui.QPushButton("SMS")
                self._smsButton.clicked.connect(self._on_sms_clicked)
                self._smsButton.setEnabled(False)
@@ -270,6 +438,10 @@ class SMSEntryWindow(object):
                self._window.show()
                self._update_recipients()
 
+       def close(self):
+               self._window.destroy()
+               self._window = None
+
        def _update_letter_count(self):
                count = self._smsEntry.toPlainText().size()
                numTexts, numCharInText = divmod(count, self.MAX_CHAR)
@@ -278,6 +450,7 @@ class SMSEntryWindow(object):
                self._characterCountLabel.setText("%d (%d)" % (numCharsLeftInText, numTexts))
 
        def _update_button_state(self):
+               self._cancelButton.setEnabled(True)
                if self._session.draft.get_num_contacts() == 0:
                        self._dialButton.setEnabled(False)
                        self._smsButton.setEnabled(False)
@@ -297,6 +470,7 @@ class SMSEntryWindow(object):
                draftContactsCount = self._session.draft.get_num_contacts()
                if draftContactsCount == 0:
                        self._window.hide()
+                       self._singleNumbersCID = None
                elif draftContactsCount == 1:
                        (cid, ) = self._session.draft.get_contacts()
                        title = self._session.draft.get_title(cid)
@@ -304,6 +478,7 @@ class SMSEntryWindow(object):
                        numbers = self._session.draft.get_numbers(cid)
 
                        self._targetList.setVisible(False)
+                       self._clear_target_list()
                        if description:
                                self._history.setText(description)
                                self._history.setVisible(True)
@@ -311,6 +486,7 @@ class SMSEntryWindow(object):
                                self._history.setText("")
                                self._history.setVisible(False)
                        self._populate_number_selector(self._singleNumberSelector, cid, numbers)
+                       self._singleNumbersCID = None
 
                        self._scroll_to_bottom()
                        self._window.setWindowTitle(title)
@@ -318,10 +494,7 @@ class SMSEntryWindow(object):
                        self._smsEntry.setFocus(QtCore.Qt.OtherFocusReason)
                else:
                        self._targetList.setVisible(True)
-                       while self._targetLayout.count():
-                               removedLayoutItem = self._targetLayout.takeAt(self._targetLayout.count()-1)
-                               removedWidget = removedLayoutItem.widget()
-                               removedWidget.close()
+                       self._clear_target_list()
                        for cid in self._session.draft.get_contacts():
                                title = self._session.draft.get_title(cid)
                                description = self._session.draft.get_description(cid)
@@ -335,7 +508,7 @@ class SMSEntryWindow(object):
                                        self._on_remove_contact,
                                        cid
                                )
-                               callback.__name__ = "b"
+                               callback.__name__ = "thanks partials for not having names and pyqt for requiring them"
                                deleteButton.clicked.connect(callback)
 
                                rowLayout = QtGui.QHBoxLayout()
@@ -348,12 +521,21 @@ class SMSEntryWindow(object):
                        self._history.setText("")
                        self._history.setVisible(False)
                        self._singleNumberSelector.setVisible(False)
+                       self._singleNumbersCID = None
 
                        self._scroll_to_bottom()
                        self._window.setWindowTitle("Contacts")
                        self._window.show()
                        self._smsEntry.setFocus(QtCore.Qt.OtherFocusReason)
 
+       def _clear_target_list(self):
+               while self._targetLayout.count():
+                       removedLayoutItem = self._targetLayout.takeAt(self._targetLayout.count()-1)
+                       removedWidget = removedLayoutItem.widget()
+                       removedWidget.hide()
+                       removedWidget.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
+                       removedWidget.close()
+
        def _populate_number_selector(self, selector, cid, numbers):
                selector.clear()
 
@@ -374,14 +556,16 @@ class SMSEntryWindow(object):
                        selector.setCurrentIndex(defaultIndex)
                else:
                        selector.setEnabled(False)
-               callback = functools.partial(
-                       self._on_change_number,
-                       cid
-               )
-               callback.__name__ = "thanks partials for not having names and pyqt for requiring them"
-               selector.currentIndexChanged.connect(
-                       QtCore.pyqtSlot(int)(callback)
-               )
+
+               if selector is not self._singleNumberSelector:
+                       callback = functools.partial(
+                               self._on_change_number,
+                               cid
+                       )
+                       callback.__name__ = "thanks partials for not having names and pyqt for requiring them"
+                       selector.activated.connect(
+                               QtCore.pyqtSlot(int)(callback)
+                       )
 
        def _scroll_to_bottom(self):
                self._scrollTimer.start()
@@ -392,7 +576,7 @@ class SMSEntryWindow(object):
 
        @misc_utils.log_exception(_moduleLogger)
        def _on_sms_clicked(self, arg):
-               message = str(self._smsEntry.toPlainText())
+               message = unicode(self._smsEntry.toPlainText())
                self._session.draft.send(message)
                self._smsEntry.setPlainText("")
 
@@ -411,9 +595,28 @@ class SMSEntryWindow(object):
                self._session.draft.remove_contact(cid)
 
        @misc_utils.log_exception(_moduleLogger)
+       def _on_single_change_number(self, index):
+               # Exception thrown when the first item is removed
+               cid = self._singleNumbersCID
+               if cid is None:
+                       _moduleLogger.error("Number change occurred on the single selector when in multi-selector mode (%r)" % index)
+                       return
+               try:
+                       numbers = self._session.draft.get_numbers(cid)
+               except KeyError:
+                       _moduleLogger.error("Contact no longer available (or bizarre error): %r (%r)" % (cid, index))
+                       return
+               number = numbers[index][0]
+               self._session.draft.set_selected_number(cid, number)
+
+       @misc_utils.log_exception(_moduleLogger)
        def _on_change_number(self, cid, index):
                # Exception thrown when the first item is removed
-               numbers = self._session.draft.get_numbers(cid)
+               try:
+                       numbers = self._session.draft.get_numbers(cid)
+               except KeyError:
+                       _moduleLogger.error("Contact no longer available (or bizarre error): %r (%r)" % (cid, index))
+                       return
                number = numbers[index][0]
                self._session.draft.set_selected_number(cid, number)
 
@@ -438,12 +641,11 @@ class SMSEntryWindow(object):
        @QtCore.pyqtSlot()
        @misc_utils.log_exception(_moduleLogger)
        def _on_op_finished(self):
-               self._window.hide()
-
                self._smsEntry.setReadOnly(False)
                self._cancelButton.setVisible(False)
                self._smsButton.setVisible(True)
                self._dialButton.setVisible(True)
+               self._window.hide()
 
        @QtCore.pyqtSlot()
        @misc_utils.log_exception(_moduleLogger)
@@ -453,7 +655,7 @@ class SMSEntryWindow(object):
                self._smsButton.setVisible(True)
                self._dialButton.setVisible(True)
 
-               self._errorLog.push_message(message)
+               self._errorLog.push_error(message)
 
        @QtCore.pyqtSlot()
        @misc_utils.log_exception(_moduleLogger)
@@ -477,7 +679,7 @@ def _get_contact_numbers(session, contactId, numberDescription):
                except KeyError:
                        contactPhoneNumbers = []
                contactPhoneNumbers = [
-                       (contactPhoneNumber["phoneNumber"], contactPhoneNumber["phoneType"])
+                       (contactPhoneNumber["phoneNumber"], contactPhoneNumber.get("phoneType", "Unknown"))
                        for contactPhoneNumber in contactPhoneNumbers
                ]
                if contactPhoneNumbers: