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