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