Adding back in the google voice number
[gc-dialer] / src / dialogs.py
1 #!/usr/bin/env python
2
3 from __future__ import with_statement
4 from __future__ import division
5
6 import functools
7 import copy
8 import logging
9
10 import util.qt_compat as qt_compat
11 QtCore = qt_compat.QtCore
12 QtGui = qt_compat.import_module("QtGui")
13
14 import constants
15 from util import qwrappers
16 from util import qui_utils
17 from util import misc as misc_utils
18
19
20 _moduleLogger = logging.getLogger(__name__)
21
22
23 class CredentialsDialog(object):
24
25         def __init__(self, app):
26                 self._app = app
27                 self._usernameField = QtGui.QLineEdit()
28                 self._passwordField = QtGui.QLineEdit()
29                 self._passwordField.setEchoMode(QtGui.QLineEdit.Password)
30
31                 self._credLayout = QtGui.QGridLayout()
32                 self._credLayout.addWidget(QtGui.QLabel("Username"), 0, 0)
33                 self._credLayout.addWidget(self._usernameField, 0, 1)
34                 self._credLayout.addWidget(QtGui.QLabel("Password"), 1, 0)
35                 self._credLayout.addWidget(self._passwordField, 1, 1)
36
37                 self._loginButton = QtGui.QPushButton("&Login")
38                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
39                 self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole)
40
41                 self._layout = QtGui.QVBoxLayout()
42                 self._layout.addLayout(self._credLayout)
43                 self._layout.addWidget(self._buttonLayout)
44
45                 self._dialog = QtGui.QDialog()
46                 self._dialog.setWindowTitle("Login")
47                 self._dialog.setLayout(self._layout)
48                 self._dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)
49                 self._buttonLayout.accepted.connect(self._dialog.accept)
50                 self._buttonLayout.rejected.connect(self._dialog.reject)
51
52                 self._closeWindowAction = QtGui.QAction(None)
53                 self._closeWindowAction.setText("Close")
54                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
55                 self._closeWindowAction.triggered.connect(self._on_close_window)
56
57                 self._dialog.addAction(self._closeWindowAction)
58                 self._dialog.addAction(app.quitAction)
59                 self._dialog.addAction(app.fullscreenAction)
60
61         def run(self, defaultUsername, defaultPassword, parent=None):
62                 self._dialog.setParent(parent, QtCore.Qt.Dialog)
63                 try:
64                         self._usernameField.setText(defaultUsername)
65                         self._passwordField.setText(defaultPassword)
66
67                         response = self._dialog.exec_()
68                         if response == QtGui.QDialog.Accepted:
69                                 return str(self._usernameField.text()), str(self._passwordField.text())
70                         elif response == QtGui.QDialog.Rejected:
71                                 return None
72                         else:
73                                 _moduleLogger.error("Unknown response")
74                                 return None
75                 finally:
76                         self._dialog.setParent(None, QtCore.Qt.Dialog)
77
78         def close(self):
79                 try:
80                         self._dialog.reject()
81                 except RuntimeError:
82                         _moduleLogger.exception("Oh well")
83
84         @qt_compat.Slot()
85         @qt_compat.Slot(bool)
86         @misc_utils.log_exception(_moduleLogger)
87         def _on_close_window(self, checked = True):
88                 with qui_utils.notify_error(self._app.errorLog):
89                         self._dialog.reject()
90
91
92 class AboutDialog(object):
93
94         def __init__(self, app):
95                 self._app = app
96                 self._title = QtGui.QLabel(
97                         "<h1>%s</h1><h3>Version: %s</h3>" % (
98                                 constants.__pretty_app_name__, constants.__version__
99                         )
100                 )
101                 self._title.setTextFormat(QtCore.Qt.RichText)
102                 self._title.setAlignment(QtCore.Qt.AlignCenter)
103                 self._copyright = QtGui.QLabel("<h6>Developed by Ed Page<h6><h6>Icons: See website</h6>")
104                 self._copyright.setTextFormat(QtCore.Qt.RichText)
105                 self._copyright.setAlignment(QtCore.Qt.AlignCenter)
106                 self._link = QtGui.QLabel('<a href="http://gc-dialer.garage.maemo.org">DialCentral Website</a>')
107                 self._link.setTextFormat(QtCore.Qt.RichText)
108                 self._link.setAlignment(QtCore.Qt.AlignCenter)
109                 self._link.setOpenExternalLinks(True)
110
111                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
112
113                 self._layout = QtGui.QVBoxLayout()
114                 self._layout.addWidget(self._title)
115                 self._layout.addWidget(self._copyright)
116                 self._layout.addWidget(self._link)
117                 self._layout.addWidget(self._buttonLayout)
118
119                 self._dialog = QtGui.QDialog()
120                 self._dialog.setWindowTitle("About")
121                 self._dialog.setLayout(self._layout)
122                 self._buttonLayout.rejected.connect(self._dialog.reject)
123
124                 self._closeWindowAction = QtGui.QAction(None)
125                 self._closeWindowAction.setText("Close")
126                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
127                 self._closeWindowAction.triggered.connect(self._on_close_window)
128
129                 self._dialog.addAction(self._closeWindowAction)
130                 self._dialog.addAction(app.quitAction)
131                 self._dialog.addAction(app.fullscreenAction)
132
133         def run(self, parent=None):
134                 self._dialog.setParent(parent, QtCore.Qt.Dialog)
135
136                 response = self._dialog.exec_()
137                 return response
138
139         def close(self):
140                 try:
141                         self._dialog.reject()
142                 except RuntimeError:
143                         _moduleLogger.exception("Oh well")
144
145         @qt_compat.Slot()
146         @qt_compat.Slot(bool)
147         @misc_utils.log_exception(_moduleLogger)
148         def _on_close_window(self, checked = True):
149                 with qui_utils.notify_error(self._app.errorLog):
150                         self._dialog.reject()
151
152
153 class AccountDialog(QtCore.QObject, qwrappers.WindowWrapper):
154
155         # @bug Can't enter custom callback numbers
156
157         _RECURRENCE_CHOICES = [
158                 (1, "1 minute"),
159                 (2, "2 minutes"),
160                 (3, "3 minutes"),
161                 (5, "5 minutes"),
162                 (8, "8 minutes"),
163                 (10, "10 minutes"),
164                 (15, "15 minutes"),
165                 (30, "30 minutes"),
166                 (45, "45 minutes"),
167                 (60, "1 hour"),
168                 (3*60, "3 hours"),
169                 (6*60, "6 hours"),
170                 (12*60, "12 hours"),
171         ]
172
173         ALARM_NONE = "No Alert"
174         ALARM_BACKGROUND = "Background Alert"
175         ALARM_APPLICATION = "Application Alert"
176
177         VOICEMAIL_CHECK_NOT_SUPPORTED = "Not Supported"
178         VOICEMAIL_CHECK_DISABLED = "Disabled"
179         VOICEMAIL_CHECK_ENABLED = "Enabled"
180
181         settingsApproved = qt_compat.Signal()
182
183         def __init__(self, parent, app, errorLog):
184                 QtCore.QObject.__init__(self)
185                 qwrappers.WindowWrapper.__init__(self, parent, app)
186                 self._app = app
187                 self._doClear = False
188
189                 self._accountNumberLabel = QtGui.QLabel("NUMBER NOT SET")
190                 self._notificationSelecter = QtGui.QComboBox()
191                 self._notificationSelecter.currentIndexChanged.connect(self._on_notification_change)
192                 self._notificationTimeSelector = QtGui.QComboBox()
193                 #self._notificationTimeSelector.setEditable(True)
194                 self._notificationTimeSelector.setInsertPolicy(QtGui.QComboBox.InsertAtTop)
195                 for _, label in self._RECURRENCE_CHOICES:
196                         self._notificationTimeSelector.addItem(label)
197                 self._missedCallsNotificationButton = QtGui.QCheckBox("Missed Calls")
198                 self._voicemailNotificationButton = QtGui.QCheckBox("Voicemail")
199                 self._smsNotificationButton = QtGui.QCheckBox("SMS")
200                 self._voicemailOnMissedButton = QtGui.QCheckBox("Voicemail Update on Missed Calls")
201                 self._clearButton = QtGui.QPushButton("Clear Account")
202                 self._clearButton.clicked.connect(self._on_clear)
203                 self._callbackSelector = QtGui.QComboBox()
204                 #self._callbackSelector.setEditable(True)
205                 self._callbackSelector.setInsertPolicy(QtGui.QComboBox.InsertAtTop)
206                 self._orientationSelector = QtGui.QComboBox()
207                 for orientationMode in [
208                         self._app.DEFAULT_ORIENTATION,
209                         self._app.AUTO_ORIENTATION,
210                         self._app.LANDSCAPE_ORIENTATION,
211                         self._app.PORTRAIT_ORIENTATION,
212                 ]:
213                         self._orientationSelector.addItem(orientationMode)
214
215                 self._update_notification_state()
216
217                 self._credLayout = QtGui.QGridLayout()
218                 self._credLayout.addWidget(QtGui.QLabel("Account"), 0, 0)
219                 self._credLayout.addWidget(self._accountNumberLabel, 0, 1)
220                 self._credLayout.addWidget(QtGui.QLabel("Callback"), 1, 0)
221                 self._credLayout.addWidget(self._callbackSelector, 1, 1)
222                 self._credLayout.addWidget(self._notificationSelecter, 2, 0)
223                 self._credLayout.addWidget(self._notificationTimeSelector, 2, 1)
224                 self._credLayout.addWidget(QtGui.QLabel(""), 3, 0)
225                 self._credLayout.addWidget(self._missedCallsNotificationButton, 3, 1)
226                 self._credLayout.addWidget(QtGui.QLabel(""), 4, 0)
227                 self._credLayout.addWidget(self._voicemailNotificationButton, 4, 1)
228                 self._credLayout.addWidget(QtGui.QLabel(""), 5, 0)
229                 self._credLayout.addWidget(self._smsNotificationButton, 5, 1)
230                 self._credLayout.addWidget(QtGui.QLabel("Other"), 6, 0)
231                 self._credLayout.addWidget(self._voicemailOnMissedButton, 6, 1)
232                 self._credLayout.addWidget(QtGui.QLabel("Orientation"), 7, 0)
233                 self._credLayout.addWidget(self._orientationSelector, 7, 1)
234                 self._credLayout.addWidget(QtGui.QLabel(""), 8, 0)
235                 self._credLayout.addWidget(QtGui.QLabel(""), 9, 0)
236                 self._credLayout.addWidget(self._clearButton, 9, 1)
237
238                 self._credWidget = QtGui.QWidget()
239                 self._credWidget.setLayout(self._credLayout)
240                 self._credWidget.setContentsMargins(0, 0, 0, 0)
241                 self._scrollSettings = QtGui.QScrollArea()
242                 self._scrollSettings.setWidget(self._credWidget)
243                 self._scrollSettings.setWidgetResizable(True)
244                 self._scrollSettings.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
245                 self._scrollSettings.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
246
247                 self._applyButton = QtGui.QPushButton("&Apply")
248                 self._applyButton.clicked.connect(self._on_settings_apply)
249                 self._cancelButton = QtGui.QPushButton("&Cancel")
250                 self._cancelButton.clicked.connect(self._on_settings_cancel)
251                 self._buttonLayout = QtGui.QHBoxLayout()
252                 self._buttonLayout.addWidget(self._cancelButton)
253                 self._buttonLayout.addWidget(self._applyButton)
254
255                 self._layout.addWidget(self._scrollSettings)
256                 self._layout.addLayout(self._buttonLayout)
257                 self._layout.setDirection(QtGui.QBoxLayout.TopToBottom)
258
259                 self._window.setWindowTitle("Account")
260                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)
261
262         @property
263         def doClear(self):
264                 return self._doClear
265
266         def setIfNotificationsSupported(self, isSupported):
267                 if isSupported:
268                         self._notificationSelecter.clear()
269                         self._notificationSelecter.addItems([self.ALARM_NONE, self.ALARM_APPLICATION, self.ALARM_BACKGROUND])
270                         self._notificationTimeSelector.setEnabled(False)
271                         self._missedCallsNotificationButton.setEnabled(False)
272                         self._voicemailNotificationButton.setEnabled(False)
273                         self._smsNotificationButton.setEnabled(False)
274                 else:
275                         self._notificationSelecter.clear()
276                         self._notificationSelecter.addItems([self.ALARM_NONE, self.ALARM_APPLICATION])
277                         self._notificationTimeSelector.setEnabled(False)
278                         self._missedCallsNotificationButton.setEnabled(False)
279                         self._voicemailNotificationButton.setEnabled(False)
280                         self._smsNotificationButton.setEnabled(False)
281
282         def set_account_number(self, num):
283                 self._accountNumberLabel.setText(num)
284
285         orientation = property(
286                 lambda self: str(self._orientationSelector.currentText()),
287                 lambda self, mode: qui_utils.set_current_index(self._orientationSelector, mode),
288         )
289
290         def _set_voicemail_on_missed(self, status):
291                 if status == self.VOICEMAIL_CHECK_NOT_SUPPORTED:
292                         self._voicemailOnMissedButton.setChecked(False)
293                         self._voicemailOnMissedButton.hide()
294                 elif status == self.VOICEMAIL_CHECK_DISABLED:
295                         self._voicemailOnMissedButton.setChecked(False)
296                         self._voicemailOnMissedButton.show()
297                 elif status == self.VOICEMAIL_CHECK_ENABLED:
298                         self._voicemailOnMissedButton.setChecked(True)
299                         self._voicemailOnMissedButton.show()
300                 else:
301                         raise RuntimeError("Unsupported option for updating voicemail on missed calls %r" % status)
302
303         def _get_voicemail_on_missed(self):
304                 if not self._voicemailOnMissedButton.isVisible():
305                         return self.VOICEMAIL_CHECK_NOT_SUPPORTED
306                 elif self._voicemailOnMissedButton.isChecked():
307                         return self.VOICEMAIL_CHECK_ENABLED
308                 else:
309                         return self.VOICEMAIL_CHECK_DISABLED
310
311         updateVMOnMissedCall = property(_get_voicemail_on_missed, _set_voicemail_on_missed)
312
313         notifications = property(
314                 lambda self: str(self._notificationSelecter.currentText()),
315                 lambda self, enabled: qui_utils.set_current_index(self._notificationSelecter, enabled),
316         )
317
318         notifyOnMissed = property(
319                 lambda self: self._missedCallsNotificationButton.isChecked(),
320                 lambda self, enabled: self._missedCallsNotificationButton.setChecked(enabled),
321         )
322
323         notifyOnVoicemail = property(
324                 lambda self: self._voicemailNotificationButton.isChecked(),
325                 lambda self, enabled: self._voicemailNotificationButton.setChecked(enabled),
326         )
327
328         notifyOnSms = property(
329                 lambda self: self._smsNotificationButton.isChecked(),
330                 lambda self, enabled: self._smsNotificationButton.setChecked(enabled),
331         )
332
333         def _get_notification_time(self):
334                 index = self._notificationTimeSelector.currentIndex()
335                 minutes = self._RECURRENCE_CHOICES[index][0]
336                 return minutes
337
338         def _set_notification_time(self, minutes):
339                 for i, (time, _) in enumerate(self._RECURRENCE_CHOICES):
340                         if time == minutes:
341                                 self._notificationTimeSelector.setCurrentIndex(i)
342                                 break
343                 else:
344                                 self._notificationTimeSelector.setCurrentIndex(0)
345
346         notificationTime = property(_get_notification_time, _set_notification_time)
347
348         @property
349         def selectedCallback(self):
350                 index = self._callbackSelector.currentIndex()
351                 data = str(self._callbackSelector.itemData(index))
352                 return data
353
354         def set_callbacks(self, choices, default):
355                 self._callbackSelector.clear()
356
357                 self._callbackSelector.addItem("Not Set", "")
358
359                 uglyDefault = misc_utils.make_ugly(default)
360                 if not uglyDefault:
361                         uglyDefault = default
362                 for number, description in choices.iteritems():
363                         prettyNumber = misc_utils.make_pretty(number)
364                         uglyNumber = misc_utils.make_ugly(number)
365                         if not uglyNumber:
366                                 prettyNumber = number
367                                 uglyNumber = number
368
369                         self._callbackSelector.addItem("%s - %s" % (prettyNumber, description), uglyNumber)
370                         if uglyNumber == uglyDefault:
371                                 self._callbackSelector.setCurrentIndex(self._callbackSelector.count() - 1)
372
373         def run(self):
374                 self._doClear = False
375                 self._window.show()
376
377         def close(self):
378                 try:
379                         self._window.hide()
380                 except RuntimeError:
381                         _moduleLogger.exception("Oh well")
382
383         def _update_notification_state(self):
384                 currentText = str(self._notificationSelecter.currentText())
385                 if currentText == self.ALARM_BACKGROUND:
386                         self._notificationTimeSelector.setEnabled(True)
387
388                         self._missedCallsNotificationButton.setEnabled(True)
389                         self._voicemailNotificationButton.setEnabled(True)
390                         self._smsNotificationButton.setEnabled(True)
391                 elif currentText == self.ALARM_APPLICATION:
392                         self._notificationTimeSelector.setEnabled(True)
393
394                         self._missedCallsNotificationButton.setEnabled(False)
395                         self._voicemailNotificationButton.setEnabled(True)
396                         self._smsNotificationButton.setEnabled(True)
397
398                         self._missedCallsNotificationButton.setChecked(False)
399                 else:
400                         self._notificationTimeSelector.setEnabled(False)
401
402                         self._missedCallsNotificationButton.setEnabled(False)
403                         self._voicemailNotificationButton.setEnabled(False)
404                         self._smsNotificationButton.setEnabled(False)
405
406                         self._missedCallsNotificationButton.setChecked(False)
407                         self._voicemailNotificationButton.setChecked(False)
408                         self._smsNotificationButton.setChecked(False)
409
410         @qt_compat.Slot(int)
411         @misc_utils.log_exception(_moduleLogger)
412         def _on_notification_change(self, index):
413                 with qui_utils.notify_error(self._app.errorLog):
414                         self._update_notification_state()
415
416         @qt_compat.Slot()
417         @qt_compat.Slot(bool)
418         @misc_utils.log_exception(_moduleLogger)
419         def _on_settings_cancel(self, checked = False):
420                 with qui_utils.notify_error(self._app.errorLog):
421                         self.hide()
422
423         @qt_compat.Slot()
424         @qt_compat.Slot(bool)
425         def _on_settings_apply(self, checked = False):
426                 self.__on_settings_apply(checked)
427
428         @misc_utils.log_exception(_moduleLogger)
429         def __on_settings_apply(self, checked = False):
430                 with qui_utils.notify_error(self._app.errorLog):
431                         self.settingsApproved.emit()
432                         self.hide()
433
434         @qt_compat.Slot()
435         @qt_compat.Slot(bool)
436         @misc_utils.log_exception(_moduleLogger)
437         def _on_clear(self, checked = False):
438                 with qui_utils.notify_error(self._app.errorLog):
439                         self._doClear = True
440                         self.settingsApproved.emit()
441                         self.hide()
442
443
444 class ContactList(object):
445
446         _SENTINEL_ICON = QtGui.QIcon()
447
448         def __init__(self, app, session):
449                 self._app = app
450                 self._session = session
451                 self._targetLayout = QtGui.QVBoxLayout()
452                 self._targetList = QtGui.QWidget()
453                 self._targetList.setLayout(self._targetLayout)
454                 self._uiItems = []
455                 self._closeIcon = qui_utils.get_theme_icon(("window-close", "general_close", "gtk-close"), self._SENTINEL_ICON)
456
457         @property
458         def toplevel(self):
459                 return self._targetList
460
461         def setVisible(self, isVisible):
462                 self._targetList.setVisible(isVisible)
463
464         def update(self):
465                 cids = list(self._session.draft.get_contacts())
466                 amountCommon = min(len(cids), len(self._uiItems))
467
468                 # Run through everything in common
469                 for i in xrange(0, amountCommon):
470                         cid = cids[i]
471                         uiItem = self._uiItems[i]
472                         title = self._session.draft.get_title(cid)
473                         description = self._session.draft.get_description(cid)
474                         numbers = self._session.draft.get_numbers(cid)
475                         uiItem["cid"] = cid
476                         uiItem["title"] = title
477                         uiItem["description"] = description
478                         uiItem["numbers"] = numbers
479                         uiItem["label"].setText(title)
480                         self._populate_number_selector(uiItem["selector"], cid, i, numbers)
481                         uiItem["rowWidget"].setVisible(True)
482
483                 # More contacts than ui items
484                 for i in xrange(amountCommon, len(cids)):
485                         cid = cids[i]
486                         title = self._session.draft.get_title(cid)
487                         description = self._session.draft.get_description(cid)
488                         numbers = self._session.draft.get_numbers(cid)
489
490                         titleLabel = QtGui.QLabel(title)
491                         titleLabel.setWordWrap(True)
492                         numberSelector = QtGui.QComboBox()
493                         self._populate_number_selector(numberSelector, cid, i, numbers)
494
495                         callback = functools.partial(
496                                 self._on_change_number,
497                                 i
498                         )
499                         callback.__name__ = "thanks partials for not having names and pyqt for requiring them"
500                         numberSelector.activated.connect(
501                                 qt_compat.Slot(int)(callback)
502                         )
503
504                         if self._closeIcon is self._SENTINEL_ICON:
505                                 deleteButton = QtGui.QPushButton("Delete")
506                         else:
507                                 deleteButton = QtGui.QPushButton(self._closeIcon, "")
508                         deleteButton.setSizePolicy(QtGui.QSizePolicy(
509                                 QtGui.QSizePolicy.Minimum,
510                                 QtGui.QSizePolicy.Minimum,
511                                 QtGui.QSizePolicy.PushButton,
512                         ))
513                         callback = functools.partial(
514                                 self._on_remove_contact,
515                                 i
516                         )
517                         callback.__name__ = "thanks partials for not having names and pyqt for requiring them"
518                         deleteButton.clicked.connect(callback)
519
520                         rowLayout = QtGui.QHBoxLayout()
521                         rowLayout.addWidget(titleLabel, 1000)
522                         rowLayout.addWidget(numberSelector, 0)
523                         rowLayout.addWidget(deleteButton, 0)
524                         rowWidget = QtGui.QWidget()
525                         rowWidget.setLayout(rowLayout)
526                         self._targetLayout.addWidget(rowWidget)
527
528                         uiItem = {}
529                         uiItem["cid"] = cid
530                         uiItem["title"] = title
531                         uiItem["description"] = description
532                         uiItem["numbers"] = numbers
533                         uiItem["label"] = titleLabel
534                         uiItem["selector"] = numberSelector
535                         uiItem["rowWidget"] = rowWidget
536                         self._uiItems.append(uiItem)
537                         amountCommon = i+1
538
539                 # More UI items than contacts
540                 for i in xrange(amountCommon, len(self._uiItems)):
541                         uiItem = self._uiItems[i]
542                         uiItem["rowWidget"].setVisible(False)
543                         amountCommon = i+1
544
545         def _populate_number_selector(self, selector, cid, cidIndex, numbers):
546                 selector.clear()
547
548                 selectedNumber = self._session.draft.get_selected_number(cid)
549                 if len(numbers) == 1:
550                         # If no alt numbers available, check the address book
551                         numbers, defaultIndex = _get_contact_numbers(self._session, cid, selectedNumber, numbers[0][1])
552                 else:
553                         defaultIndex = _index_number(numbers, selectedNumber)
554
555                 for number, description in numbers:
556                         if description:
557                                 label = "%s - %s" % (number, description)
558                         else:
559                                 label = number
560                         selector.addItem(label)
561                 selector.setVisible(True)
562                 if 1 < len(numbers):
563                         selector.setEnabled(True)
564                         selector.setCurrentIndex(defaultIndex)
565                 else:
566                         selector.setEnabled(False)
567
568         @misc_utils.log_exception(_moduleLogger)
569         def _on_change_number(self, cidIndex, index):
570                 with qui_utils.notify_error(self._app.errorLog):
571                         # Exception thrown when the first item is removed
572                         try:
573                                 cid = self._uiItems[cidIndex]["cid"]
574                                 numbers = self._session.draft.get_numbers(cid)
575                         except IndexError:
576                                 _moduleLogger.error("Contact no longer available (or bizarre error): %r (%r)" % (cid, index))
577                                 return
578                         except KeyError:
579                                 _moduleLogger.error("Contact no longer available (or bizarre error): %r (%r)" % (cid, index))
580                                 return
581                         number = numbers[index][0]
582                         self._session.draft.set_selected_number(cid, number)
583
584         @misc_utils.log_exception(_moduleLogger)
585         def _on_remove_contact(self, index, toggled):
586                 with qui_utils.notify_error(self._app.errorLog):
587                         self._session.draft.remove_contact(self._uiItems[index]["cid"])
588
589
590 class VoicemailPlayer(object):
591
592         def __init__(self, app, session, errorLog):
593                 self._app = app
594                 self._session = session
595                 self._errorLog = errorLog
596                 self._token = None
597                 self._session.voicemailAvailable.connect(self._on_voicemail_downloaded)
598                 self._session.draft.recipientsChanged.connect(self._on_recipients_changed)
599
600                 self._playButton = QtGui.QPushButton("Play")
601                 self._playButton.clicked.connect(self._on_voicemail_play)
602                 self._pauseButton = QtGui.QPushButton("Pause")
603                 self._pauseButton.clicked.connect(self._on_voicemail_pause)
604                 self._pauseButton.hide()
605                 self._resumeButton = QtGui.QPushButton("Resume")
606                 self._resumeButton.clicked.connect(self._on_voicemail_resume)
607                 self._resumeButton.hide()
608                 self._stopButton = QtGui.QPushButton("Stop")
609                 self._stopButton.clicked.connect(self._on_voicemail_stop)
610                 self._stopButton.hide()
611
612                 self._downloadButton = QtGui.QPushButton("Download Voicemail")
613                 self._downloadButton.clicked.connect(self._on_voicemail_download)
614                 self._downloadLayout = QtGui.QHBoxLayout()
615                 self._downloadLayout.addWidget(self._downloadButton)
616                 self._downloadWidget = QtGui.QWidget()
617                 self._downloadWidget.setLayout(self._downloadLayout)
618
619                 self._playLabel = QtGui.QLabel("Voicemail")
620                 self._saveButton = QtGui.QPushButton("Save")
621                 self._saveButton.clicked.connect(self._on_voicemail_save)
622                 self._playerLayout = QtGui.QHBoxLayout()
623                 self._playerLayout.addWidget(self._playLabel)
624                 self._playerLayout.addWidget(self._playButton)
625                 self._playerLayout.addWidget(self._pauseButton)
626                 self._playerLayout.addWidget(self._resumeButton)
627                 self._playerLayout.addWidget(self._stopButton)
628                 self._playerLayout.addWidget(self._saveButton)
629                 self._playerWidget = QtGui.QWidget()
630                 self._playerWidget.setLayout(self._playerLayout)
631
632                 self._visibleWidget = None
633                 self._layout = QtGui.QHBoxLayout()
634                 self._layout.setContentsMargins(0, 0, 0, 0)
635                 self._widget = QtGui.QWidget()
636                 self._widget.setLayout(self._layout)
637                 self._update_state()
638
639         @property
640         def toplevel(self):
641                 return self._widget
642
643         def destroy(self):
644                 self._session.voicemailAvailable.disconnect(self._on_voicemail_downloaded)
645                 self._session.draft.recipientsChanged.disconnect(self._on_recipients_changed)
646                 self._invalidate_token()
647
648         def _invalidate_token(self):
649                 if self._token is not None:
650                         self._token.invalidate()
651                         self._token.error.disconnect(self._on_play_error)
652                         self._token.stateChange.connect(self._on_play_state)
653                         self._token.invalidated.connect(self._on_play_invalidated)
654
655         def _show_download(self, messageId):
656                 if self._visibleWidget is self._downloadWidget:
657                         return
658                 self._hide()
659                 self._layout.addWidget(self._downloadWidget)
660                 self._visibleWidget = self._downloadWidget
661                 self._visibleWidget.show()
662
663         def _show_player(self, messageId):
664                 if self._visibleWidget is self._playerWidget:
665                         return
666                 self._hide()
667                 self._layout.addWidget(self._playerWidget)
668                 self._visibleWidget = self._playerWidget
669                 self._visibleWidget.show()
670
671         def _hide(self):
672                 if self._visibleWidget is None:
673                         return
674                 self._visibleWidget.hide()
675                 self._layout.removeWidget(self._visibleWidget)
676                 self._visibleWidget = None
677
678         def _update_play_state(self):
679                 if self._token is not None and self._token.isValid:
680                         self._playButton.setText("Stop")
681                 else:
682                         self._playButton.setText("Play")
683
684         def _update_state(self):
685                 if self._session.draft.get_num_contacts() != 1:
686                         self._hide()
687                         return
688
689                 (cid, ) = self._session.draft.get_contacts()
690                 messageId = self._session.draft.get_message_id(cid)
691                 if messageId is None:
692                         self._hide()
693                         return
694
695                 if self._session.is_available(messageId):
696                         self._show_player(messageId)
697                 else:
698                         self._show_download(messageId)
699                 if self._token is not None:
700                         self._token.invalidate()
701
702         @misc_utils.log_exception(_moduleLogger)
703         def _on_voicemail_save(self, arg):
704                 with qui_utils.notify_error(self._app.errorLog):
705                         targetPath = QtGui.QFileDialog.getSaveFileName(None, caption="Save Voicemail", filter="Audio File (*.mp3)")
706                         targetPath = unicode(targetPath)
707                         if not targetPath:
708                                 return
709
710                         (cid, ) = self._session.draft.get_contacts()
711                         messageId = self._session.draft.get_message_id(cid)
712                         sourcePath = self._session.voicemail_path(messageId)
713                         import shutil
714                         shutil.copy2(sourcePath, targetPath)
715
716         @misc_utils.log_exception(_moduleLogger)
717         def _on_play_error(self, error):
718                 with qui_utils.notify_error(self._app.errorLog):
719                         self._app.errorLog.push_error(error)
720
721         @misc_utils.log_exception(_moduleLogger)
722         def _on_play_invalidated(self):
723                 with qui_utils.notify_error(self._app.errorLog):
724                         self._playButton.show()
725                         self._pauseButton.hide()
726                         self._resumeButton.hide()
727                         self._stopButton.hide()
728                         self._invalidate_token()
729
730         @misc_utils.log_exception(_moduleLogger)
731         def _on_play_state(self, state):
732                 with qui_utils.notify_error(self._app.errorLog):
733                         if state == self._token.STATE_PLAY:
734                                 self._playButton.hide()
735                                 self._pauseButton.show()
736                                 self._resumeButton.hide()
737                                 self._stopButton.show()
738                         elif state == self._token.STATE_PAUSE:
739                                 self._playButton.hide()
740                                 self._pauseButton.hide()
741                                 self._resumeButton.show()
742                                 self._stopButton.show()
743                         elif state == self._token.STATE_STOP:
744                                 self._playButton.show()
745                                 self._pauseButton.hide()
746                                 self._resumeButton.hide()
747                                 self._stopButton.hide()
748
749         @misc_utils.log_exception(_moduleLogger)
750         def _on_voicemail_play(self, arg):
751                 with qui_utils.notify_error(self._app.errorLog):
752                         (cid, ) = self._session.draft.get_contacts()
753                         messageId = self._session.draft.get_message_id(cid)
754                         sourcePath = self._session.voicemail_path(messageId)
755
756                         self._invalidate_token()
757                         uri = "file://%s" % sourcePath
758                         self._token = self._app.streamHandler.set_file(uri)
759                         self._token.stateChange.connect(self._on_play_state)
760                         self._token.invalidated.connect(self._on_play_invalidated)
761                         self._token.error.connect(self._on_play_error)
762                         self._token.play()
763
764         @misc_utils.log_exception(_moduleLogger)
765         def _on_voicemail_pause(self, arg):
766                 with qui_utils.notify_error(self._app.errorLog):
767                         self._token.pause()
768
769         @misc_utils.log_exception(_moduleLogger)
770         def _on_voicemail_resume(self, arg):
771                 with qui_utils.notify_error(self._app.errorLog):
772                         self._token.play()
773
774         @misc_utils.log_exception(_moduleLogger)
775         def _on_voicemail_stop(self, arg):
776                 with qui_utils.notify_error(self._app.errorLog):
777                         self._token.stop()
778
779         @misc_utils.log_exception(_moduleLogger)
780         def _on_voicemail_download(self, arg):
781                 with qui_utils.notify_error(self._app.errorLog):
782                         (cid, ) = self._session.draft.get_contacts()
783                         messageId = self._session.draft.get_message_id(cid)
784                         self._session.download_voicemail(messageId)
785                         self._hide()
786
787         @qt_compat.Slot()
788         @misc_utils.log_exception(_moduleLogger)
789         def _on_recipients_changed(self):
790                 with qui_utils.notify_error(self._app.errorLog):
791                         self._update_state()
792
793         @qt_compat.Slot(str, str)
794         @misc_utils.log_exception(_moduleLogger)
795         def _on_voicemail_downloaded(self, messageId, filepath):
796                 with qui_utils.notify_error(self._app.errorLog):
797                         self._update_state()
798
799
800 class SMSEntryWindow(qwrappers.WindowWrapper):
801
802         MAX_CHAR = 160
803         # @bug Somehow a window is being destroyed on object creation which causes glitches on Maemo 5
804
805         def __init__(self, parent, app, session, errorLog):
806                 qwrappers.WindowWrapper.__init__(self, parent, app)
807                 self._session = session
808                 self._session.messagesUpdated.connect(self._on_refresh_history)
809                 self._session.historyUpdated.connect(self._on_refresh_history)
810                 self._session.draft.recipientsChanged.connect(self._on_recipients_changed)
811
812                 self._session.draft.sendingMessage.connect(self._on_op_started)
813                 self._session.draft.calling.connect(self._on_op_started)
814                 self._session.draft.calling.connect(self._on_calling_started)
815                 self._session.draft.cancelling.connect(self._on_op_started)
816
817                 self._session.draft.sentMessage.connect(self._on_op_finished)
818                 self._session.draft.called.connect(self._on_op_finished)
819                 self._session.draft.cancelled.connect(self._on_op_finished)
820                 self._session.draft.error.connect(self._on_op_error)
821
822                 self._errorLog = errorLog
823
824                 self._targetList = ContactList(self._app, self._session)
825                 self._history = QtGui.QLabel()
826                 self._history.setTextFormat(QtCore.Qt.RichText)
827                 self._history.setWordWrap(True)
828                 self._voicemailPlayer = VoicemailPlayer(self._app, self._session, self._errorLog)
829                 self._smsEntry = QtGui.QTextEdit()
830                 self._smsEntry.textChanged.connect(self._on_letter_count_changed)
831
832                 self._entryLayout = QtGui.QVBoxLayout()
833                 self._entryLayout.addWidget(self._targetList.toplevel)
834                 self._entryLayout.addWidget(self._history)
835                 self._entryLayout.addWidget(self._voicemailPlayer.toplevel, 0)
836                 self._entryLayout.addWidget(self._smsEntry)
837                 self._entryLayout.setContentsMargins(0, 0, 0, 0)
838                 self._entryWidget = QtGui.QWidget()
839                 self._entryWidget.setLayout(self._entryLayout)
840                 self._entryWidget.setContentsMargins(0, 0, 0, 0)
841                 self._scrollEntry = QtGui.QScrollArea()
842                 self._scrollEntry.setWidget(self._entryWidget)
843                 self._scrollEntry.setWidgetResizable(True)
844                 self._scrollEntry.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom)
845                 self._scrollEntry.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
846                 self._scrollEntry.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
847
848                 self._characterCountLabel = QtGui.QLabel("")
849                 self._singleNumberSelector = QtGui.QComboBox()
850                 self._cids = []
851                 self._singleNumberSelector.activated.connect(self._on_single_change_number)
852                 self._smsButton = QtGui.QPushButton("SMS")
853                 self._smsButton.clicked.connect(self._on_sms_clicked)
854                 self._smsButton.setEnabled(False)
855                 self._dialButton = QtGui.QPushButton("Dial")
856                 self._dialButton.clicked.connect(self._on_call_clicked)
857                 self._cancelButton = QtGui.QPushButton("Cancel Call")
858                 self._cancelButton.clicked.connect(self._on_cancel_clicked)
859                 self._cancelButton.setVisible(False)
860
861                 self._buttonLayout = QtGui.QHBoxLayout()
862                 self._buttonLayout.addWidget(self._characterCountLabel)
863                 self._buttonLayout.addWidget(self._singleNumberSelector)
864                 self._buttonLayout.addWidget(self._smsButton)
865                 self._buttonLayout.addWidget(self._dialButton)
866                 self._buttonLayout.addWidget(self._cancelButton)
867
868                 self._layout.addWidget(self._errorDisplay.toplevel)
869                 self._layout.addWidget(self._scrollEntry)
870                 self._layout.addLayout(self._buttonLayout)
871                 self._layout.setDirection(QtGui.QBoxLayout.TopToBottom)
872
873                 self._window.setWindowTitle("Contact")
874                 self._window.closed.connect(self._on_close_window)
875                 self._window.hidden.connect(self._on_close_window)
876                 self._window.resized.connect(self._on_window_resized)
877
878                 self._scrollTimer = QtCore.QTimer()
879                 self._scrollTimer.setInterval(100)
880                 self._scrollTimer.setSingleShot(True)
881                 self._scrollTimer.timeout.connect(self._on_delayed_scroll_to_bottom)
882
883                 self._smsEntry.setPlainText(self._session.draft.message)
884                 self._update_letter_count()
885                 self._update_target_fields()
886                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
887                 self.update_orientation(self._app.orientation)
888
889         def close(self):
890                 if self._window is None:
891                         # Already closed
892                         return
893                 window = self._window
894                 try:
895                         message = unicode(self._smsEntry.toPlainText())
896                         self._session.draft.message = message
897                         self.hide()
898                 except AttributeError:
899                         _moduleLogger.exception("Oh well")
900                 except RuntimeError:
901                         _moduleLogger.exception("Oh well")
902
903         def destroy(self):
904                 self._session.messagesUpdated.disconnect(self._on_refresh_history)
905                 self._session.historyUpdated.disconnect(self._on_refresh_history)
906                 self._session.draft.recipientsChanged.disconnect(self._on_recipients_changed)
907                 self._session.draft.sendingMessage.disconnect(self._on_op_started)
908                 self._session.draft.calling.disconnect(self._on_op_started)
909                 self._session.draft.calling.disconnect(self._on_calling_started)
910                 self._session.draft.cancelling.disconnect(self._on_op_started)
911                 self._session.draft.sentMessage.disconnect(self._on_op_finished)
912                 self._session.draft.called.disconnect(self._on_op_finished)
913                 self._session.draft.cancelled.disconnect(self._on_op_finished)
914                 self._session.draft.error.disconnect(self._on_op_error)
915                 self._voicemailPlayer.destroy()
916                 window = self._window
917                 self._window = None
918                 try:
919                         window.close()
920                         window.destroy()
921                 except AttributeError:
922                         _moduleLogger.exception("Oh well")
923                 except RuntimeError:
924                         _moduleLogger.exception("Oh well")
925
926         def update_orientation(self, orientation):
927                 qwrappers.WindowWrapper.update_orientation(self, orientation)
928                 self._scroll_to_bottom()
929
930         def _update_letter_count(self):
931                 count = len(self._smsEntry.toPlainText())
932                 numTexts, numCharInText = divmod(count, self.MAX_CHAR)
933                 numTexts += 1
934                 numCharsLeftInText = self.MAX_CHAR - numCharInText
935                 self._characterCountLabel.setText("%d (%d)" % (numCharsLeftInText, numTexts))
936
937         def _update_button_state(self):
938                 self._cancelButton.setEnabled(True)
939                 if self._session.draft.get_num_contacts() == 0:
940                         self._dialButton.setEnabled(False)
941                         self._smsButton.setEnabled(False)
942                 elif self._session.draft.get_num_contacts() == 1:
943                         count = len(self._smsEntry.toPlainText())
944                         if count == 0:
945                                 self._dialButton.setEnabled(True)
946                                 self._smsButton.setEnabled(False)
947                         else:
948                                 self._dialButton.setEnabled(False)
949                                 self._smsButton.setEnabled(True)
950                 else:
951                         self._dialButton.setEnabled(False)
952                         count = len(self._smsEntry.toPlainText())
953                         if count == 0:
954                                 self._smsButton.setEnabled(False)
955                         else:
956                                 self._smsButton.setEnabled(True)
957
958         def _update_history(self, cid):
959                 draftContactsCount = self._session.draft.get_num_contacts()
960                 if draftContactsCount != 1:
961                         self._history.setVisible(False)
962                 else:
963                         description = self._session.draft.get_description(cid)
964
965                         self._targetList.setVisible(False)
966                         if description:
967                                 self._history.setText(description)
968                                 self._history.setVisible(True)
969                         else:
970                                 self._history.setText("")
971                                 self._history.setVisible(False)
972
973         def _update_target_fields(self):
974                 draftContactsCount = self._session.draft.get_num_contacts()
975                 if draftContactsCount == 0:
976                         self.hide()
977                         del self._cids[:]
978                 elif draftContactsCount == 1:
979                         (cid, ) = self._session.draft.get_contacts()
980                         title = self._session.draft.get_title(cid)
981                         numbers = self._session.draft.get_numbers(cid)
982
983                         self._targetList.setVisible(False)
984                         self._update_history(cid)
985                         self._populate_number_selector(self._singleNumberSelector, cid, 0, numbers)
986                         self._cids = [cid]
987
988                         self._scroll_to_bottom()
989                         self._window.setWindowTitle(title)
990                         self._smsEntry.setFocus(QtCore.Qt.OtherFocusReason)
991                         self.show()
992                         self._window.raise_()
993                 else:
994                         self._targetList.setVisible(True)
995                         self._targetList.update()
996                         self._history.setText("")
997                         self._history.setVisible(False)
998                         self._singleNumberSelector.setVisible(False)
999
1000                         self._scroll_to_bottom()
1001                         self._window.setWindowTitle("Contacts")
1002                         self._smsEntry.setFocus(QtCore.Qt.OtherFocusReason)
1003                         self.show()
1004                         self._window.raise_()
1005
1006         def _populate_number_selector(self, selector, cid, cidIndex, numbers):
1007                 selector.clear()
1008
1009                 selectedNumber = self._session.draft.get_selected_number(cid)
1010                 if len(numbers) == 1:
1011                         # If no alt numbers available, check the address book
1012                         numbers, defaultIndex = _get_contact_numbers(self._session, cid, selectedNumber, numbers[0][1])
1013                 else:
1014                         defaultIndex = _index_number(numbers, selectedNumber)
1015
1016                 for number, description in numbers:
1017                         if description:
1018                                 label = "%s - %s" % (number, description)
1019                         else:
1020                                 label = number
1021                         selector.addItem(label)
1022                 selector.setVisible(True)
1023                 if 1 < len(numbers):
1024                         selector.setEnabled(True)
1025                         selector.setCurrentIndex(defaultIndex)
1026                 else:
1027                         selector.setEnabled(False)
1028
1029         def _scroll_to_bottom(self):
1030                 self._scrollTimer.start()
1031
1032         @misc_utils.log_exception(_moduleLogger)
1033         def _on_delayed_scroll_to_bottom(self):
1034                 with qui_utils.notify_error(self._app.errorLog):
1035                         self._scrollEntry.ensureWidgetVisible(self._smsEntry)
1036
1037         @misc_utils.log_exception(_moduleLogger)
1038         def _on_sms_clicked(self, arg):
1039                 with qui_utils.notify_error(self._app.errorLog):
1040                         message = unicode(self._smsEntry.toPlainText())
1041                         self._session.draft.message = message
1042                         self._session.draft.send()
1043
1044         @misc_utils.log_exception(_moduleLogger)
1045         def _on_call_clicked(self, arg):
1046                 with qui_utils.notify_error(self._app.errorLog):
1047                         message = unicode(self._smsEntry.toPlainText())
1048                         self._session.draft.message = message
1049                         self._session.draft.call()
1050
1051         @qt_compat.Slot()
1052         @misc_utils.log_exception(_moduleLogger)
1053         def _on_cancel_clicked(self, message):
1054                 with qui_utils.notify_error(self._app.errorLog):
1055                         self._session.draft.cancel()
1056
1057         @misc_utils.log_exception(_moduleLogger)
1058         def _on_single_change_number(self, index):
1059                 with qui_utils.notify_error(self._app.errorLog):
1060                         # Exception thrown when the first item is removed
1061                         cid = self._cids[0]
1062                         try:
1063                                 numbers = self._session.draft.get_numbers(cid)
1064                         except KeyError:
1065                                 _moduleLogger.error("Contact no longer available (or bizarre error): %r (%r)" % (cid, index))
1066                                 return
1067                         number = numbers[index][0]
1068                         self._session.draft.set_selected_number(cid, number)
1069
1070         @qt_compat.Slot()
1071         @misc_utils.log_exception(_moduleLogger)
1072         def _on_refresh_history(self):
1073                 with qui_utils.notify_error(self._app.errorLog):
1074                         draftContactsCount = self._session.draft.get_num_contacts()
1075                         if draftContactsCount != 1:
1076                                 # Changing contact count will automatically refresh it
1077                                 return
1078                         (cid, ) = self._session.draft.get_contacts()
1079                         self._update_history(cid)
1080
1081         @qt_compat.Slot()
1082         @misc_utils.log_exception(_moduleLogger)
1083         def _on_recipients_changed(self):
1084                 with qui_utils.notify_error(self._app.errorLog):
1085                         self._update_target_fields()
1086                         self._update_button_state()
1087
1088         @qt_compat.Slot()
1089         @misc_utils.log_exception(_moduleLogger)
1090         def _on_op_started(self):
1091                 with qui_utils.notify_error(self._app.errorLog):
1092                         self._smsEntry.setReadOnly(True)
1093                         self._smsButton.setVisible(False)
1094                         self._dialButton.setVisible(False)
1095                         self.show()
1096
1097         @qt_compat.Slot()
1098         @misc_utils.log_exception(_moduleLogger)
1099         def _on_calling_started(self):
1100                 with qui_utils.notify_error(self._app.errorLog):
1101                         self._cancelButton.setVisible(True)
1102
1103         @qt_compat.Slot()
1104         @misc_utils.log_exception(_moduleLogger)
1105         def _on_op_finished(self):
1106                 with qui_utils.notify_error(self._app.errorLog):
1107                         self._smsEntry.setPlainText("")
1108                         self._smsEntry.setReadOnly(False)
1109                         self._cancelButton.setVisible(False)
1110                         self._smsButton.setVisible(True)
1111                         self._dialButton.setVisible(True)
1112                         self.close()
1113                         self.destroy()
1114
1115         @qt_compat.Slot()
1116         @misc_utils.log_exception(_moduleLogger)
1117         def _on_op_error(self, message):
1118                 with qui_utils.notify_error(self._app.errorLog):
1119                         self._smsEntry.setReadOnly(False)
1120                         self._cancelButton.setVisible(False)
1121                         self._smsButton.setVisible(True)
1122                         self._dialButton.setVisible(True)
1123
1124                         self._errorLog.push_error(message)
1125
1126         @qt_compat.Slot()
1127         @misc_utils.log_exception(_moduleLogger)
1128         def _on_letter_count_changed(self):
1129                 with qui_utils.notify_error(self._app.errorLog):
1130                         self._update_letter_count()
1131                         self._update_button_state()
1132
1133         @qt_compat.Slot()
1134         @misc_utils.log_exception(_moduleLogger)
1135         def _on_window_resized(self):
1136                 with qui_utils.notify_error(self._app.errorLog):
1137                         self._scroll_to_bottom()
1138
1139         @qt_compat.Slot()
1140         @qt_compat.Slot(bool)
1141         @misc_utils.log_exception(_moduleLogger)
1142         def _on_close_window(self, checked = True):
1143                 with qui_utils.notify_error(self._app.errorLog):
1144                         self.close()
1145
1146
1147 def _index_number(numbers, default):
1148         uglyDefault = misc_utils.make_ugly(default)
1149         uglyContactNumbers = list(
1150                 misc_utils.make_ugly(contactNumber)
1151                 for (contactNumber, _) in numbers
1152         )
1153         defaultMatches = [
1154                 misc_utils.similar_ugly_numbers(uglyDefault, contactNumber)
1155                 for contactNumber in uglyContactNumbers
1156         ]
1157         try:
1158                 defaultIndex = defaultMatches.index(True)
1159         except ValueError:
1160                 defaultIndex = -1
1161                 _moduleLogger.warn(
1162                         "Could not find contact number %s among %r" % (
1163                                 default, numbers
1164                         )
1165                 )
1166         return defaultIndex
1167
1168
1169 def _get_contact_numbers(session, contactId, number, description):
1170         contactPhoneNumbers = []
1171         if contactId and contactId != "0":
1172                 try:
1173                         contactDetails = copy.deepcopy(session.get_contacts()[contactId])
1174                         contactPhoneNumbers = contactDetails["numbers"]
1175                 except KeyError:
1176                         contactPhoneNumbers = []
1177                 contactPhoneNumbers = [
1178                         (contactPhoneNumber["phoneNumber"], contactPhoneNumber.get("phoneType", "Unknown"))
1179                         for contactPhoneNumber in contactPhoneNumbers
1180                 ]
1181                 defaultIndex = _index_number(contactPhoneNumbers, number)
1182
1183         if not contactPhoneNumbers or defaultIndex == -1:
1184                 contactPhoneNumbers += [(number, description)]
1185                 defaultIndex = 0
1186
1187         return contactPhoneNumbers, defaultIndex