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