Populating of a single target's phone number in SMS window
[gc-dialer] / src / dialcentral_qt.py
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
3
4 from __future__ import with_statement
5
6 import sys
7 import os
8 import shutil
9 import simplejson
10 import re
11 import logging
12
13 from PyQt4 import QtGui
14 from PyQt4 import QtCore
15
16 import constants
17 from util import qui_utils
18 from util import qtpie
19 from util import misc as misc_utils
20
21 import session
22
23
24 _moduleLogger = logging.getLogger(__name__)
25
26
27 IS_MAEMO = True
28
29
30 class Dialcentral(object):
31
32         _DATA_PATHS = [
33                 os.path.dirname(__file__),
34                 os.path.join(os.path.dirname(__file__), "../data"),
35                 os.path.join(os.path.dirname(__file__), "../lib"),
36                 '/usr/share/%s' % constants.__app_name__,
37                 '/usr/lib/%s' % constants.__app_name__,
38         ]
39
40         def __init__(self, app):
41                 self._app = app
42                 self._recent = []
43                 self._hiddenCategories = set()
44                 self._hiddenUnits = {}
45                 self._clipboard = QtGui.QApplication.clipboard()
46
47                 self._mainWindow = None
48
49                 self._fullscreenAction = QtGui.QAction(None)
50                 self._fullscreenAction.setText("Fullscreen")
51                 self._fullscreenAction.setCheckable(True)
52                 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
53                 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
54
55                 self._logAction = QtGui.QAction(None)
56                 self._logAction.setText("Log")
57                 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
58                 self._logAction.triggered.connect(self._on_log)
59
60                 self._quitAction = QtGui.QAction(None)
61                 self._quitAction.setText("Quit")
62                 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
63                 self._quitAction.triggered.connect(self._on_quit)
64
65                 self._app.lastWindowClosed.connect(self._on_app_quit)
66                 self.load_settings()
67
68                 self._mainWindow = MainWindow(None, self)
69                 self._mainWindow.window.destroyed.connect(self._on_child_close)
70
71         def load_settings(self):
72                 try:
73                         with open(constants._user_settings_, "r") as settingsFile:
74                                 settings = simplejson.load(settingsFile)
75                 except IOError, e:
76                         _moduleLogger.info("No settings")
77                         settings = {}
78                 except ValueError:
79                         _moduleLogger.info("Settings were corrupt")
80                         settings = {}
81
82                 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
83
84         def save_settings(self):
85                 settings = {
86                         "isFullScreen": self._fullscreenAction.isChecked(),
87                 }
88                 with open(constants._user_settings_, "w") as settingsFile:
89                         simplejson.dump(settings, settingsFile)
90
91         @property
92         def fullscreenAction(self):
93                 return self._fullscreenAction
94
95         @property
96         def logAction(self):
97                 return self._logAction
98
99         @property
100         def quitAction(self):
101                 return self._quitAction
102
103         def _close_windows(self):
104                 if self._mainWindow is not None:
105                         self._mainWindow.window.destroyed.disconnect(self._on_child_close)
106                         self._mainWindow.close()
107                         self._mainWindow = None
108
109         @QtCore.pyqtSlot()
110         @QtCore.pyqtSlot(bool)
111         @misc_utils.log_exception(_moduleLogger)
112         def _on_app_quit(self, checked = False):
113                 self.save_settings()
114
115         @QtCore.pyqtSlot(QtCore.QObject)
116         @misc_utils.log_exception(_moduleLogger)
117         def _on_child_close(self, obj = None):
118                 self._mainWindow = None
119
120         @QtCore.pyqtSlot()
121         @QtCore.pyqtSlot(bool)
122         @misc_utils.log_exception(_moduleLogger)
123         def _on_toggle_fullscreen(self, checked = False):
124                 for window in self._walk_children():
125                         window.set_fullscreen(checked)
126
127         @QtCore.pyqtSlot()
128         @QtCore.pyqtSlot(bool)
129         @misc_utils.log_exception(_moduleLogger)
130         def _on_log(self, checked = False):
131                 with open(constants._user_logpath_, "r") as f:
132                         logLines = f.xreadlines()
133                         log = "".join(logLines)
134                         self._clipboard.setText(log)
135
136         @QtCore.pyqtSlot()
137         @QtCore.pyqtSlot(bool)
138         @misc_utils.log_exception(_moduleLogger)
139         def _on_quit(self, checked = False):
140                 self._close_windows()
141
142
143 class CredentialsDialog(object):
144
145         def __init__(self):
146                 self._usernameField = QtGui.QLineEdit()
147                 self._passwordField = QtGui.QLineEdit()
148                 self._passwordField.setEchoMode(QtGui.QLineEdit.PasswordEchoOnEdit)
149
150                 self._credLayout = QtGui.QGridLayout()
151                 self._credLayout.addWidget(QtGui.QLabel("Username"), 0, 0)
152                 self._credLayout.addWidget(self._usernameField, 0, 1)
153                 self._credLayout.addWidget(QtGui.QLabel("Password"), 1, 0)
154                 self._credLayout.addWidget(self._passwordField, 1, 1)
155
156                 self._loginButton = QtGui.QPushButton("&Login")
157                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
158                 self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole)
159
160                 self._layout = QtGui.QVBoxLayout()
161                 self._layout.addLayout(self._credLayout)
162                 self._layout.addWidget(self._buttonLayout)
163
164                 self._dialog = QtGui.QDialog()
165                 self._dialog.setWindowTitle("Login")
166                 self._dialog.setLayout(self._layout)
167                 self._dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)
168                 qui_utils.set_autorient(self._dialog, True)
169                 self._buttonLayout.accepted.connect(self._dialog.accept)
170                 self._buttonLayout.rejected.connect(self._dialog.reject)
171
172         def run(self, defaultUsername, defaultPassword, parent=None):
173                 self._dialog.setParent(parent, QtCore.Qt.Dialog)
174                 try:
175                         self._usernameField.setText(defaultUsername)
176                         self._passwordField.setText(defaultPassword)
177
178                         response = self._dialog.exec_()
179                         if response == QtGui.QDialog.Accepted:
180                                 return str(self._usernameField.text()), str(self._passwordField.text())
181                         elif response == QtGui.QDialog.Rejected:
182                                 raise RuntimeError("Login Cancelled")
183                 finally:
184                         self._dialog.setParent(None, QtCore.Qt.Dialog)
185
186
187 class AccountDialog(object):
188
189         def __init__(self):
190                 self._accountNumberLabel = QtGui.QLabel("NUMBER NOT SET")
191                 self._clearButton = QtGui.QPushButton("Clear Account")
192                 self._clearButton.clicked.connect(self._on_clear)
193                 self._doClear = False
194
195                 self._credLayout = QtGui.QGridLayout()
196                 self._credLayout.addWidget(QtGui.QLabel("Account"), 0, 0)
197                 self._credLayout.addWidget(self._accountNumberLabel, 0, 1)
198                 self._credLayout.addWidget(QtGui.QLabel("Callback"), 1, 0)
199                 self._credLayout.addWidget(self._clearButton, 2, 1)
200
201                 self._loginButton = QtGui.QPushButton("&Login")
202                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
203                 self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole)
204
205                 self._layout = QtGui.QVBoxLayout()
206                 self._layout.addLayout(self._credLayout)
207                 self._layout.addLayout(self._buttonLayout)
208
209                 self._dialog = QtGui.QDialog()
210                 self._dialog.setWindowTitle("Login")
211                 self._dialog.setLayout(self._layout)
212                 qui_utils.set_autorient(self._dialog, True)
213                 self._buttonLayout.accepted.connect(self._dialog.accept)
214                 self._buttonLayout.rejected.connect(self._dialog.reject)
215
216         @property
217         def doClear(self):
218                 return self._doClear
219
220         accountNumber = property(
221                 lambda self: str(self._accountNumberLabel.text()),
222                 lambda self, num: self._accountNumberLabel.setText(num),
223         )
224
225         def run(self, defaultUsername, defaultPassword, parent=None):
226                 self._doClear = False
227                 self._dialog.setParent(parent)
228                 self._usernameField.setText(defaultUsername)
229                 self._passwordField.setText(defaultPassword)
230
231                 response = self._dialog.exec_()
232                 if response == QtGui.QDialog.Accepted:
233                         return str(self._usernameField.text()), str(self._passwordField.text())
234                 elif response == QtGui.QDialog.Rejected:
235                         raise RuntimeError("Login Cancelled")
236
237         @QtCore.pyqtSlot()
238         @QtCore.pyqtSlot(bool)
239         def _on_clear(self, checked = False):
240                 self._doClear = True
241                 self._dialog.accept()
242
243
244 class SMSEntryWindow(object):
245
246         def __init__(self, parent, app, session, errorLog):
247                 self._contacts = []
248                 self._app = app
249                 self._session = session
250                 self._session.draft.recipientsChanged.connect(self._on_recipients_changed)
251                 self._session.draft.called.connect(self._on_op_finished)
252                 self._session.draft.sentMessage.connect(self._on_op_finished)
253                 self._session.draft.cancelled.connect(self._on_op_finished)
254                 self._errorLog = errorLog
255
256                 self._history = QtGui.QTextEdit()
257                 self._smsEntry = QtGui.QTextEdit()
258                 self._smsEntry.textChanged.connect(self._on_letter_count_changed)
259
260                 self._entryLayout = QtGui.QVBoxLayout()
261                 self._entryLayout.addWidget(self._history)
262                 self._entryLayout.addWidget(self._smsEntry)
263                 self._entryWidget = QtGui.QWidget()
264                 self._entryWidget.setLayout(self._entryLayout)
265                 self._scrollEntry = QtGui.QScrollArea()
266                 self._scrollEntry.setWidget(self._entryWidget)
267                 self._scrollEntry.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom)
268                 self._scrollEntry.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
269                 self._scrollEntry.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
270
271                 self._characterCountLabel = QtGui.QLabel("Letters: %s" % 0)
272                 self._singleNumberSelector = QtGui.QComboBox()
273                 self._smsButton = QtGui.QPushButton("SMS")
274                 self._dialButton = QtGui.QPushButton("Dial")
275
276                 self._buttonLayout = QtGui.QHBoxLayout()
277                 self._buttonLayout.addWidget(self._characterCountLabel)
278                 self._buttonLayout.addWidget(self._singleNumberSelector)
279                 self._buttonLayout.addWidget(self._smsButton)
280                 self._buttonLayout.addWidget(self._dialButton)
281
282                 self._layout = QtGui.QVBoxLayout()
283                 self._layout.addWidget(self._scrollEntry)
284                 self._layout.addLayout(self._buttonLayout)
285
286                 centralWidget = QtGui.QWidget()
287                 centralWidget.setLayout(self._layout)
288
289                 self._window = QtGui.QMainWindow(parent)
290                 qui_utils.set_autorient(self._window, True)
291                 qui_utils.set_stackable(self._window, True)
292                 self._window.setWindowTitle("Contact")
293                 self._window.setCentralWidget(centralWidget)
294                 self._window.show()
295                 self._update_recipients()
296
297         def _update_letter_count(self):
298                 count = self._smsEntry.toPlainText().size()
299                 self._characterCountLabel.setText("Letters: %s" % count)
300
301         def _update_button_state(self):
302                 if len(self._contacts) == 0:
303                         self._dialButton.setEnabled(False)
304                         self._smsButton.setEnabled(False)
305                 elif len(self._contacts) == 1:
306                         count = self._smsEntry.toPlainText().size()
307                         if count == 0:
308                                 self._dialButton.setEnabled(True)
309                                 self._smsButton.setEnabled(False)
310                         else:
311                                 self._dialButton.setEnabled(False)
312                                 self._smsButton.setEnabled(True)
313                 else:
314                         self._dialButton.setEnabled(False)
315                         self._smsButton.setEnabled(True)
316
317         def _update_recipients(self):
318                 draftContacts = len(self._session.draft.get_contacts())
319                 if draftContacts == 0:
320                         self._window.hide()
321                 elif draftContacts == 1:
322                         title, description, numbers = list(
323                                 self._session.draft.get_contacts().itervalues()
324                         )[0]
325                         self._window.setWindowTitle(title)
326                         self._history.setHtml(description)
327                         self._history.setVisible(True)
328                         self._populate_number_selector(self._singleNumberSelector, numbers))
329                         self._window.show()
330                 else:
331                         self._window.setWindowTitle("Contacts")
332                         self._history.setHtml("")
333                         self._history.setVisible(False)
334                         self._singleNumberSelector.setVisible(False)
335                         self._window.show()
336
337         def _populate_number_selector(self, selector, numbers):
338                 while 0 < selector.count():
339                         selector.removeItem(0)
340                 for number, description in numbers:
341                         if description:
342                                 label = "%s - %s" % number, description
343                         else:
344                                 label = number
345                         selector.addItem(label, numbers)
346                 selector.setVisible(True)
347                 if 1 < len(numbers):
348                         selector.setEnabled(True)
349                 else:
350                         selector.setEnabled(False)
351
352         @QtCore.pyqtSlot()
353         @misc_utils.log_exception(_moduleLogger)
354         def _on_recipients_changed(self):
355                 self._populate_recipients()
356
357         @QtCore.pyqtSlot()
358         @misc_utils.log_exception(_moduleLogger)
359         def _on_op_finished(self):
360                 self._window.hide()
361
362         @QtCore.pyqtSlot()
363         @misc_utils.log_exception(_moduleLogger)
364         def _on_letter_count_changed(self):
365                 self._update_letter_count()
366                 self._update_button_state()
367
368
369 class DelayedWidget(object):
370
371         def __init__(self, app):
372                 self._layout = QtGui.QVBoxLayout()
373                 self._widget = QtGui.QWidget()
374                 self._widget.setLayout(self._layout)
375
376                 self._child = None
377                 self._isEnabled = True
378
379         @property
380         def toplevel(self):
381                 return self._widget
382
383         def has_child(self):
384                 return self._child is not None
385
386         def set_child(self, child):
387                 if self._child is not None:
388                         self._layout.removeWidget(self._child.toplevel)
389                 self._child = child
390                 if self._child is not None:
391                         self._layout.addWidget(self._child.toplevel)
392
393                 if self._isEnabled:
394                         self._child.enable()
395                 else:
396                         self._child.disable()
397
398         def enable(self):
399                 self._isEnabled = True
400                 if self._child is not None:
401                         self._child.enable()
402
403         def disable(self):
404                 self._isEnabled = False
405                 if self._child is not None:
406                         self._child.disable()
407
408         def clear(self):
409                 if self._child is not None:
410                         self._child.clear()
411
412         def refresh(self):
413                 if self._child is not None:
414                         self._child.refresh()
415
416
417 class Dialpad(object):
418
419         def __init__(self, app, session, errorLog):
420                 self._app = app
421                 self._session = session
422                 self._errorLog = errorLog
423
424                 self._plus = self._generate_key_button("+", "")
425                 self._entry = QtGui.QLineEdit()
426
427                 backAction = QtGui.QAction(None)
428                 backAction.setText("Back")
429                 backAction.triggered.connect(self._on_backspace)
430                 backPieItem = qtpie.QActionPieItem(backAction)
431                 clearAction = QtGui.QAction(None)
432                 clearAction.setText("Clear")
433                 clearAction.triggered.connect(self._on_clear_text)
434                 clearPieItem = qtpie.QActionPieItem(clearAction)
435                 self._back = qtpie.QPieButton(backPieItem)
436                 self._back.set_center(backPieItem)
437                 self._back.insertItem(qtpie.PieFiling.NULL_CENTER)
438                 self._back.insertItem(clearPieItem)
439                 self._back.insertItem(qtpie.PieFiling.NULL_CENTER)
440                 self._back.insertItem(qtpie.PieFiling.NULL_CENTER)
441
442                 self._entryLayout = QtGui.QHBoxLayout()
443                 self._entryLayout.addWidget(self._plus, 0, QtCore.Qt.AlignCenter)
444                 self._entryLayout.addWidget(self._entry, 10)
445                 self._entryLayout.addWidget(self._back, 0, QtCore.Qt.AlignCenter)
446
447                 self._smsButton = QtGui.QPushButton("SMS")
448                 self._smsButton.clicked.connect(self._on_sms_clicked)
449                 self._callButton = QtGui.QPushButton("Call")
450                 self._callButton.clicked.connect(self._on_call_clicked)
451
452                 self._padLayout = QtGui.QGridLayout()
453                 rows = [0, 0, 0, 1, 1, 1, 2, 2, 2]
454                 columns = [0, 1, 2] * 3
455                 keys = [
456                         ("1", ""),
457                         ("2", "ABC"),
458                         ("3", "DEF"),
459                         ("4", "GHI"),
460                         ("5", "JKL"),
461                         ("6", "MNO"),
462                         ("7", "PQRS"),
463                         ("8", "TUV"),
464                         ("9", "WXYZ"),
465                 ]
466                 for (num, letters), (row, column) in zip(keys, zip(rows, columns)):
467                         self._padLayout.addWidget(
468                                 self._generate_key_button(num, letters), row, column, QtCore.Qt.AlignCenter
469                         )
470                 self._padLayout.addWidget(self._smsButton, 3, 0)
471                 self._padLayout.addWidget(
472                         self._generate_key_button("0", ""), 3, 1, QtCore.Qt.AlignCenter
473                 )
474                 self._padLayout.addWidget(self._callButton, 3, 2)
475
476                 self._layout = QtGui.QVBoxLayout()
477                 self._layout.addLayout(self._entryLayout)
478                 self._layout.addLayout(self._padLayout)
479                 self._widget = QtGui.QWidget()
480                 self._widget.setLayout(self._layout)
481
482         @property
483         def toplevel(self):
484                 return self._widget
485
486         def enable(self):
487                 self._smsButton.setEnabled(True)
488                 self._callButton.setEnabled(True)
489
490         def disable(self):
491                 self._smsButton.setEnabled(False)
492                 self._callButton.setEnabled(False)
493
494         def clear(self):
495                 pass
496
497         def refresh(self):
498                 pass
499
500         def _generate_key_button(self, center, letters):
501                 centerPieItem = self._generate_button_slice(center)
502                 button = qtpie.QPieButton(centerPieItem)
503                 button.set_center(centerPieItem)
504
505                 if len(letters) == 0:
506                         for i in xrange(8):
507                                 pieItem = qtpie.PieFiling.NULL_CENTER
508                                 button.insertItem(pieItem)
509                 elif len(letters) in [3, 4]:
510                         for i in xrange(6 - len(letters)):
511                                 pieItem = qtpie.PieFiling.NULL_CENTER
512                                 button.insertItem(pieItem)
513
514                         for letter in letters:
515                                 pieItem = self._generate_button_slice(letter)
516                                 button.insertItem(pieItem)
517
518                         for i in xrange(2):
519                                 pieItem = qtpie.PieFiling.NULL_CENTER
520                                 button.insertItem(pieItem)
521                 else:
522                         raise NotImplementedError("Cannot handle %r" % letters)
523                 return button
524
525         def _generate_button_slice(self, letter):
526                 action = QtGui.QAction(None)
527                 action.setText(letter)
528                 action.triggered.connect(lambda: self._on_keypress(letter))
529                 pieItem = qtpie.QActionPieItem(action)
530                 return pieItem
531
532         @misc_utils.log_exception(_moduleLogger)
533         def _on_keypress(self, key):
534                 self._entry.insert(key)
535
536         @misc_utils.log_exception(_moduleLogger)
537         def _on_backspace(self, toggled = False):
538                 self._entry.backspace()
539
540         @misc_utils.log_exception(_moduleLogger)
541         def _on_clear_text(self, toggled = False):
542                 self._entry.clear()
543
544         @QtCore.pyqtSlot()
545         @QtCore.pyqtSlot(bool)
546         @misc_utils.log_exception(_moduleLogger)
547         def _on_sms_clicked(self, checked = False):
548                 number = str(self._entry.text())
549                 self._entry.clear()
550
551                 contactId = number
552                 title = number
553                 description = number
554                 numbersWithDescriptions = [(number, "")]
555                 self._session.draft.add_contact(contactId, title, description, numbersWithDescriptions)
556
557         @QtCore.pyqtSlot()
558         @QtCore.pyqtSlot(bool)
559         @misc_utils.log_exception(_moduleLogger)
560         def _on_call_clicked(self, checked = False):
561                 number = str(self._entry.text())
562                 self._entry.clear()
563
564                 contactId = number
565                 title = number
566                 description = number
567                 numbersWithDescriptions = [(number, "")]
568                 self._session.draft.clear()
569                 self._session.draft.add_contact(contactId, title, description, numbersWithDescriptions)
570                 self._session.draft.call()
571
572
573 class History(object):
574
575         DATE_IDX = 0
576         ACTION_IDX = 1
577         NUMBER_IDX = 2
578         FROM_IDX = 3
579         MAX_IDX = 4
580
581         HISTORY_ITEM_TYPES = ["Received", "Missed", "Placed", "All"]
582         HISTORY_COLUMNS = ["When", "What", "Number", "From"]
583         assert len(HISTORY_COLUMNS) == MAX_IDX
584
585         def __init__(self, app, session, errorLog):
586                 self._selectedFilter = self.HISTORY_ITEM_TYPES[-1]
587                 self._app = app
588                 self._session = session
589                 self._session.historyUpdated.connect(self._on_history_updated)
590                 self._errorLog = errorLog
591
592                 self._typeSelection = QtGui.QComboBox()
593                 self._typeSelection.addItems(self.HISTORY_ITEM_TYPES)
594                 self._typeSelection.setCurrentIndex(
595                         self.HISTORY_ITEM_TYPES.index(self._selectedFilter)
596                 )
597                 self._typeSelection.currentIndexChanged[str].connect(self._on_filter_changed)
598
599                 self._itemStore = QtGui.QStandardItemModel()
600                 self._itemStore.setHorizontalHeaderLabels(self.HISTORY_COLUMNS)
601
602                 self._itemView = QtGui.QTreeView()
603                 self._itemView.setModel(self._itemStore)
604                 self._itemView.setUniformRowHeights(True)
605                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
606                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
607                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
608                 self._itemView.setHeaderHidden(True)
609                 self._itemView.activated.connect(self._on_row_activated)
610
611                 self._layout = QtGui.QVBoxLayout()
612                 self._layout.addWidget(self._typeSelection)
613                 self._layout.addWidget(self._itemView)
614                 self._widget = QtGui.QWidget()
615                 self._widget.setLayout(self._layout)
616
617                 self._populate_items()
618
619         @property
620         def toplevel(self):
621                 return self._widget
622
623         def enable(self):
624                 self._itemView.setEnabled(True)
625
626         def disable(self):
627                 self._itemView.setEnabled(False)
628
629         def clear(self):
630                 self._itemView.clear()
631
632         def refresh(self):
633                 self._session.update_history()
634
635         def _populate_items(self):
636                 self._itemStore.clear()
637                 history = self._session.get_history()
638                 history.sort(key=lambda item: item["time"], reverse=True)
639                 for event in history:
640                         if self._selectedFilter in [self.HISTORY_ITEM_TYPES[-1], event["action"]]:
641                                 relTime = abbrev_relative_date(event["relTime"])
642                                 action = event["action"]
643                                 number = event["number"]
644                                 prettyNumber = make_pretty(number)
645                                 name = event["name"]
646                                 if not name or name == number:
647                                         name = event["location"]
648                                 if not name:
649                                         name = "Unknown"
650
651                                 timeItem = QtGui.QStandardItem(relTime)
652                                 actionItem = QtGui.QStandardItem(action)
653                                 numberItem = QtGui.QStandardItem(prettyNumber)
654                                 nameItem = QtGui.QStandardItem(name)
655                                 row = timeItem, actionItem, numberItem, nameItem
656                                 for item in row:
657                                         item.setEditable(False)
658                                         item.setCheckable(False)
659                                         if item is not nameItem:
660                                                 itemFont = item.font()
661                                                 itemFont.setPointSize(max(itemFont.pointSize() - 3, 5))
662                                                 item.setFont(itemFont)
663                                 numberItem.setData(event)
664                                 self._itemStore.appendRow(row)
665
666         @QtCore.pyqtSlot(str)
667         @misc_utils.log_exception(_moduleLogger)
668         def _on_filter_changed(self, newItem):
669                 self._selectedFilter = str(newItem)
670                 self._populate_items()
671
672         @QtCore.pyqtSlot()
673         @misc_utils.log_exception(_moduleLogger)
674         def _on_history_updated(self):
675                 self._populate_items()
676
677         @QtCore.pyqtSlot(QtCore.QModelIndex)
678         @misc_utils.log_exception(_moduleLogger)
679         def _on_row_activated(self, index):
680                 rowIndex = index.row()
681                 item = self._itemStore.item(rowIndex, self.NUMBER_IDX)
682                 contactDetails = item.data().toPyObject()
683
684                 title = str(self._itemStore.item(rowIndex, self.FROM_IDX).text())
685                 number = str(contactDetails[QtCore.QString("number")])
686                 contactId = number # ids don't seem too unique so using numbers
687
688                 descriptionRows = []
689                 # @bug doesn't seem to print multiple entries
690                 for i in xrange(self._itemStore.rowCount()):
691                         iItem = self._itemStore.item(i, self.NUMBER_IDX)
692                         iContactDetails = iItem.data().toPyObject()
693                         iNumber = str(iContactDetails[QtCore.QString("number")])
694                         if number != iNumber:
695                                 continue
696                         relTime = abbrev_relative_date(iContactDetails[QtCore.QString("relTime")])
697                         action = str(iContactDetails[QtCore.QString("action")])
698                         number = str(iContactDetails[QtCore.QString("number")])
699                         prettyNumber = make_pretty(number)
700                         rowItems = relTime, action, prettyNumber
701                         descriptionRows.append("<tr><td>%s</td></tr>" % "</td><td>".join(rowItems))
702                 description = "<table>%s</table>" % "".join(descriptionRows)
703                 numbersWithDescriptions = [(str(contactDetails[QtCore.QString("number")]), "")]
704                 self._session.draft.add_contact(contactId, title, description, numbersWithDescriptions)
705
706
707 class Messages(object):
708
709         NO_MESSAGES = "None"
710         VOICEMAIL_MESSAGES = "Voicemail"
711         TEXT_MESSAGES = "SMS"
712         ALL_TYPES = "All Messages"
713         MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
714
715         UNREAD_STATUS = "Unread"
716         UNARCHIVED_STATUS = "Inbox"
717         ALL_STATUS = "Any"
718         MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
719
720         _MIN_MESSAGES_SHOWN = 4
721
722         def __init__(self, app, session, errorLog):
723                 self._selectedTypeFilter = self.ALL_TYPES
724                 self._selectedStatusFilter = self.ALL_STATUS
725                 self._app = app
726                 self._session = session
727                 self._session.messagesUpdated.connect(self._on_messages_updated)
728                 self._errorLog = errorLog
729
730                 self._typeSelection = QtGui.QComboBox()
731                 self._typeSelection.addItems(self.MESSAGE_TYPES)
732                 self._typeSelection.setCurrentIndex(
733                         self.MESSAGE_TYPES.index(self._selectedTypeFilter)
734                 )
735                 self._typeSelection.currentIndexChanged[str].connect(self._on_type_filter_changed)
736
737                 self._statusSelection = QtGui.QComboBox()
738                 self._statusSelection.addItems(self.MESSAGE_STATUSES)
739                 self._statusSelection.setCurrentIndex(
740                         self.MESSAGE_STATUSES.index(self._selectedStatusFilter)
741                 )
742                 self._statusSelection.currentIndexChanged[str].connect(self._on_status_filter_changed)
743
744                 self._selectionLayout = QtGui.QHBoxLayout()
745                 self._selectionLayout.addWidget(self._typeSelection)
746                 self._selectionLayout.addWidget(self._statusSelection)
747
748                 self._itemStore = QtGui.QStandardItemModel()
749                 self._itemStore.setHorizontalHeaderLabels(["Messages"])
750
751                 self._htmlDelegate = qui_utils.QHtmlDelegate()
752                 self._itemView = QtGui.QTreeView()
753                 self._itemView.setModel(self._itemStore)
754                 self._itemView.setUniformRowHeights(False)
755                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
756                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
757                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
758                 self._itemView.setHeaderHidden(True)
759                 self._itemView.setItemDelegate(self._htmlDelegate)
760                 self._itemView.activated.connect(self._on_row_activated)
761
762                 self._layout = QtGui.QVBoxLayout()
763                 self._layout.addLayout(self._selectionLayout)
764                 self._layout.addWidget(self._itemView)
765                 self._widget = QtGui.QWidget()
766                 self._widget.setLayout(self._layout)
767
768                 self._populate_items()
769
770         @property
771         def toplevel(self):
772                 return self._widget
773
774         def enable(self):
775                 self._itemView.setEnabled(True)
776
777         def disable(self):
778                 self._itemView.setEnabled(False)
779
780         def clear(self):
781                 self._itemView.clear()
782
783         def refresh(self):
784                 self._session.update_messages()
785
786         def _populate_items(self):
787                 self._itemStore.clear()
788                 rawMessages = self._session.get_messages()
789                 rawMessages.sort(key=lambda item: item["time"], reverse=True)
790                 for item in rawMessages:
791                         isUnarchived = not item["isArchived"]
792                         isUnread = not item["isRead"]
793                         visibleStatus = {
794                                 self.UNREAD_STATUS: isUnarchived and isUnread,
795                                 self.UNARCHIVED_STATUS: isUnarchived,
796                                 self.ALL_STATUS: True,
797                         }[self._selectedStatusFilter]
798
799                         visibleType = self._selectedTypeFilter in [item["type"], self.ALL_TYPES]
800                         if visibleType and visibleStatus:
801                                 relTime = abbrev_relative_date(item["relTime"])
802                                 number = item["number"]
803                                 prettyNumber = make_pretty(number)
804                                 name = item["name"]
805                                 if not name or name == number:
806                                         name = item["location"]
807                                 if not name:
808                                         name = "Unknown"
809
810                                 messageParts = list(item["messageParts"])
811                                 if len(messageParts) == 0:
812                                         messages = ("No Transcription", )
813                                 elif len(messageParts) == 1:
814                                         if messageParts[0][1]:
815                                                 messages = (messageParts[0][1], )
816                                         else:
817                                                 messages = ("No Transcription", )
818                                 else:
819                                         messages = [
820                                                 "<b>%s</b>: %s" % (messagePart[0], messagePart[1])
821                                                 for messagePart in messageParts
822                                         ]
823
824                                 firstMessage = "<b>%s - %s</b> <i>(%s)</i>" % (name, prettyNumber, relTime)
825
826                                 expandedMessages = [firstMessage]
827                                 expandedMessages.extend(messages)
828                                 if (self._MIN_MESSAGES_SHOWN + 1) < len(messages):
829                                         secondMessage = "<i>%d Messages Hidden...</i>" % (len(messages) - self._MIN_MESSAGES_SHOWN, )
830                                         collapsedMessages = [firstMessage, secondMessage]
831                                         collapsedMessages.extend(messages[-(self._MIN_MESSAGES_SHOWN+0):])
832                                 else:
833                                         collapsedMessages = expandedMessages
834
835                                 item = dict(item.iteritems())
836                                 item["collapsedMessages"] = "<br/>\n".join(collapsedMessages)
837                                 item["expandedMessages"] = "<br/>\n".join(expandedMessages)
838
839                                 messageItem = QtGui.QStandardItem(item["collapsedMessages"])
840                                 # @bug Not showing all of a message
841                                 messageItem.setData(item)
842                                 messageItem.setEditable(False)
843                                 messageItem.setCheckable(False)
844                                 row = (messageItem, )
845                                 self._itemStore.appendRow(row)
846
847         @QtCore.pyqtSlot(str)
848         @misc_utils.log_exception(_moduleLogger)
849         def _on_type_filter_changed(self, newItem):
850                 self._selectedTypeFilter = str(newItem)
851                 self._populate_items()
852
853         @QtCore.pyqtSlot(str)
854         @misc_utils.log_exception(_moduleLogger)
855         def _on_status_filter_changed(self, newItem):
856                 self._selectedStatusFilter = str(newItem)
857                 self._populate_items()
858
859         @QtCore.pyqtSlot()
860         @misc_utils.log_exception(_moduleLogger)
861         def _on_messages_updated(self):
862                 self._populate_items()
863
864         @QtCore.pyqtSlot(QtCore.QModelIndex)
865         @misc_utils.log_exception(_moduleLogger)
866         def _on_row_activated(self, index):
867                 rowIndex = index.row()
868                 item = self._itemStore.item(rowIndex, 0)
869                 contactDetails = item.data().toPyObject()
870
871                 name = str(contactDetails[QtCore.QString("name")])
872                 number = str(contactDetails[QtCore.QString("number")])
873                 if not name or name == number:
874                         name = str(contactDetails[QtCore.QString("location")])
875                 if not name:
876                         name = "Unknown"
877
878                 contactId = str(contactDetails[QtCore.QString("id")])
879                 title = name
880                 description = str(contactDetails[QtCore.QString("expandedMessages")])
881                 numbersWithDescriptions = [(number, "")]
882                 self._session.draft.add_contact(contactId, title, description, numbersWithDescriptions)
883
884
885 class Contacts(object):
886
887         def __init__(self, app, session, errorLog):
888                 self._selectedFilter = ""
889                 self._app = app
890                 self._session = session
891                 self._session.contactsUpdated.connect(self._on_contacts_updated)
892                 self._errorLog = errorLog
893
894                 self._listSelection = QtGui.QComboBox()
895                 self._listSelection.addItems([])
896                 # @todo Implement more contact lists
897                 #self._listSelection.setCurrentIndex(self.HISTORY_ITEM_TYPES.index(self._selectedFilter))
898                 self._listSelection.currentIndexChanged[str].connect(self._on_filter_changed)
899
900                 self._itemStore = QtGui.QStandardItemModel()
901                 self._itemStore.setHorizontalHeaderLabels(["Contacts"])
902
903                 self._itemView = QtGui.QTreeView()
904                 self._itemView.setModel(self._itemStore)
905                 self._itemView.setUniformRowHeights(True)
906                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
907                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
908                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
909                 self._itemView.setHeaderHidden(True)
910                 self._itemView.activated.connect(self._on_row_activated)
911
912                 self._layout = QtGui.QVBoxLayout()
913                 self._layout.addWidget(self._listSelection)
914                 self._layout.addWidget(self._itemView)
915                 self._widget = QtGui.QWidget()
916                 self._widget.setLayout(self._layout)
917
918                 self._populate_items()
919
920         @property
921         def toplevel(self):
922                 return self._widget
923
924         def enable(self):
925                 self._itemView.setEnabled(True)
926
927         def disable(self):
928                 self._itemView.setEnabled(False)
929
930         def clear(self):
931                 self._itemView.clear()
932
933         def refresh(self):
934                 self._session.update_contacts()
935
936         def _populate_items(self):
937                 self._itemStore.clear()
938
939                 contacts = list(self._session.get_contacts().itervalues())
940                 contacts.sort(key=lambda contact: contact["name"].lower())
941                 for item in contacts:
942                         name = item["name"]
943                         numbers = item["numbers"]
944                         nameItem = QtGui.QStandardItem(name)
945                         nameItem.setEditable(False)
946                         nameItem.setCheckable(False)
947                         nameItem.setData(item)
948                         row = (nameItem, )
949                         self._itemStore.appendRow(row)
950
951         @QtCore.pyqtSlot(str)
952         @misc_utils.log_exception(_moduleLogger)
953         def _on_filter_changed(self, newItem):
954                 self._selectedFilter = str(newItem)
955
956         @QtCore.pyqtSlot()
957         @misc_utils.log_exception(_moduleLogger)
958         def _on_contacts_updated(self):
959                 self._populate_items()
960
961         @QtCore.pyqtSlot(QtCore.QModelIndex)
962         @misc_utils.log_exception(_moduleLogger)
963         def _on_row_activated(self, index):
964                 rowIndex = index.row()
965                 item = self._itemStore.item(rowIndex, 0)
966                 contactDetails = item.data().toPyObject()
967
968                 name = str(contactDetails[QtCore.QString("name")])
969                 if not name:
970                         name = str(contactDetails[QtCore.QString("location")])
971                 if not name:
972                         name = "Unknown"
973
974                 contactId = str(contactDetails[QtCore.QString("contactId")])
975                 numbers = contactDetails[QtCore.QString("numbers")]
976                 numbers = [
977                         dict(
978                                 (str(k), str(v))
979                                 for (k, v) in number.iteritems()
980                         )
981                         for number in numbers
982                 ]
983                 numbersWithDescriptions = [
984                         (
985                                 number["phoneNumber"],
986                                 self._choose_phonetype(number),
987                         )
988                         for number in numbers
989                 ]
990                 title = name
991                 description = name
992                 self._session.draft.add_contact(contactId, title, description, numbersWithDescriptions)
993
994         @staticmethod
995         def _choose_phonetype(numberDetails):
996                 if "phoneTypeName" in numberDetails:
997                         return numberDetails["phoneTypeName"]
998                 elif "phoneType" in numberDetails:
999                         return numberDetails["phoneType"]
1000                 else:
1001                         return ""
1002
1003
1004 class MainWindow(object):
1005
1006         KEYPAD_TAB = 0
1007         RECENT_TAB = 1
1008         MESSAGES_TAB = 2
1009         CONTACTS_TAB = 3
1010         MAX_TABS = 4
1011
1012         _TAB_TITLES = [
1013                 "Dialpad",
1014                 "History",
1015                 "Messages",
1016                 "Contacts",
1017         ]
1018         assert len(_TAB_TITLES) == MAX_TABS
1019
1020         _TAB_CLASS = [
1021                 Dialpad,
1022                 History,
1023                 Messages,
1024                 Contacts,
1025         ]
1026         assert len(_TAB_CLASS) == MAX_TABS
1027
1028         def __init__(self, parent, app):
1029                 self._fsContactsPath = os.path.join(constants._data_path_, "contacts")
1030                 self._app = app
1031                 self._session = session.Session()
1032                 self._session.error.connect(self._on_session_error)
1033                 self._session.loggedIn.connect(self._on_login)
1034                 self._session.loggedOut.connect(self._on_logout)
1035                 self._session.draft.recipientsChanged.connect(self._on_recipients_changed)
1036
1037                 self._credentialsDialog = None
1038                 self._smsEntryDialog = None
1039
1040                 self._errorLog = qui_utils.QErrorLog()
1041                 self._errorDisplay = qui_utils.ErrorDisplay(self._errorLog)
1042
1043                 self._tabsContents = [
1044                         DelayedWidget(self._app)
1045                         for i in xrange(self.MAX_TABS)
1046                 ]
1047                 for tab in self._tabsContents:
1048                         tab.disable()
1049
1050                 self._tabWidget = QtGui.QTabWidget()
1051                 if qui_utils.screen_orientation() == QtCore.Qt.Vertical:
1052                         self._tabWidget.setTabPosition(QtGui.QTabWidget.South)
1053                 else:
1054                         self._tabWidget.setTabPosition(QtGui.QTabWidget.West)
1055                 for tabIndex, tabTitle in enumerate(self._TAB_TITLES):
1056                         self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, tabTitle)
1057                 self._tabWidget.currentChanged.connect(self._on_tab_changed)
1058
1059                 self._layout = QtGui.QVBoxLayout()
1060                 self._layout.addWidget(self._errorDisplay.toplevel)
1061                 self._layout.addWidget(self._tabWidget)
1062
1063                 centralWidget = QtGui.QWidget()
1064                 centralWidget.setLayout(self._layout)
1065
1066                 self._window = QtGui.QMainWindow(parent)
1067                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
1068                 qui_utils.set_autorient(self._window, True)
1069                 qui_utils.set_stackable(self._window, True)
1070                 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
1071                 self._window.setCentralWidget(centralWidget)
1072
1073                 self._loginTabAction = QtGui.QAction(None)
1074                 self._loginTabAction.setText("Login")
1075                 self._loginTabAction.triggered.connect(self._on_login_requested)
1076
1077                 self._importTabAction = QtGui.QAction(None)
1078                 self._importTabAction.setText("Import")
1079                 self._importTabAction.triggered.connect(self._on_import)
1080
1081                 self._refreshTabAction = QtGui.QAction(None)
1082                 self._refreshTabAction.setText("Refresh")
1083                 self._refreshTabAction.setShortcut(QtGui.QKeySequence("CTRL+r"))
1084                 self._refreshTabAction.triggered.connect(self._on_refresh)
1085
1086                 self._closeWindowAction = QtGui.QAction(None)
1087                 self._closeWindowAction.setText("Close")
1088                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
1089                 self._closeWindowAction.triggered.connect(self._on_close_window)
1090
1091                 if IS_MAEMO:
1092                         fileMenu = self._window.menuBar().addMenu("&File")
1093                         fileMenu.addAction(self._loginTabAction)
1094                         fileMenu.addAction(self._refreshTabAction)
1095
1096                         toolsMenu = self._window.menuBar().addMenu("&Tools")
1097                         toolsMenu.addAction(self._importTabAction)
1098
1099                         self._window.addAction(self._closeWindowAction)
1100                         self._window.addAction(self._app.quitAction)
1101                         self._window.addAction(self._app.fullscreenAction)
1102                 else:
1103                         fileMenu = self._window.menuBar().addMenu("&File")
1104                         fileMenu.addAction(self._loginTabAction)
1105                         fileMenu.addAction(self._refreshTabAction)
1106                         fileMenu.addAction(self._closeWindowAction)
1107                         fileMenu.addAction(self._app.quitAction)
1108
1109                         viewMenu = self._window.menuBar().addMenu("&View")
1110                         viewMenu.addAction(self._app.fullscreenAction)
1111
1112                         toolsMenu = self._window.menuBar().addMenu("&Tools")
1113                         toolsMenu.addAction(self._importTabAction)
1114
1115                 self._window.addAction(self._app.logAction)
1116
1117                 self._initialize_tab(self._tabWidget.currentIndex())
1118                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
1119                 self._window.show()
1120
1121         @property
1122         def window(self):
1123                 return self._window
1124
1125         def walk_children(self):
1126                 return ()
1127
1128         def show(self):
1129                 self._window.show()
1130                 for child in self.walk_children():
1131                         child.show()
1132
1133         def hide(self):
1134                 for child in self.walk_children():
1135                         child.hide()
1136                 self._window.hide()
1137
1138         def close(self):
1139                 for child in self.walk_children():
1140                         child.window.destroyed.disconnect(self._on_child_close)
1141                         child.close()
1142                 self._window.close()
1143
1144         def set_fullscreen(self, isFullscreen):
1145                 if isFullscreen:
1146                         self._window.showFullScreen()
1147                 else:
1148                         self._window.showNormal()
1149                 for child in self.walk_children():
1150                         child.set_fullscreen(isFullscreen)
1151
1152         def _initialize_tab(self, index):
1153                 assert index < self.MAX_TABS
1154                 if not self._tabsContents[index].has_child():
1155                         tab = self._TAB_CLASS[index](self._app, self._session, self._errorLog)
1156                         self._tabsContents[index].set_child(tab)
1157                         self._tabsContents[index].refresh()
1158
1159         @QtCore.pyqtSlot(str)
1160         @misc_utils.log_exception(_moduleLogger)
1161         def _on_session_error(self, message):
1162                 self._errorLog.push_message(message)
1163
1164         @QtCore.pyqtSlot()
1165         @misc_utils.log_exception(_moduleLogger)
1166         def _on_login(self):
1167                 for tab in self._tabsContents:
1168                         tab.enable()
1169
1170         @QtCore.pyqtSlot()
1171         @misc_utils.log_exception(_moduleLogger)
1172         def _on_logout(self):
1173                 for tab in self._tabsContents:
1174                         tab.disable()
1175
1176         @QtCore.pyqtSlot()
1177         @misc_utils.log_exception(_moduleLogger)
1178         def _on_recipients_changed(self):
1179                 if len(self._session.draft.get_contacts()) == 0:
1180                         return
1181
1182                 if self._smsEntryDialog is None:
1183                         self._smsEntryDialog = SMSEntryWindow(self.window, self._app, self._session, self._errorLog)
1184                 pass
1185
1186         @QtCore.pyqtSlot()
1187         @QtCore.pyqtSlot(bool)
1188         @misc_utils.log_exception(_moduleLogger)
1189         def _on_login_requested(self, checked = True):
1190                 if self._credentialsDialog is None:
1191                         self._credentialsDialog = CredentialsDialog()
1192                 username, password = self._credentialsDialog.run("", "", self.window)
1193                 self._session.login(username, password)
1194
1195         @QtCore.pyqtSlot(int)
1196         @misc_utils.log_exception(_moduleLogger)
1197         def _on_tab_changed(self, index):
1198                 self._initialize_tab(index)
1199
1200         @QtCore.pyqtSlot()
1201         @QtCore.pyqtSlot(bool)
1202         @misc_utils.log_exception(_moduleLogger)
1203         def _on_refresh(self, checked = True):
1204                 index = self._tabWidget.currentIndex()
1205                 self._tabsContents[index].refresh()
1206
1207         @QtCore.pyqtSlot()
1208         @QtCore.pyqtSlot(bool)
1209         @misc_utils.log_exception(_moduleLogger)
1210         def _on_import(self, checked = True):
1211                 csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)")
1212                 if not csvName:
1213                         return
1214                 shutil.copy2(csvName, self._fsContactsPath)
1215
1216         @QtCore.pyqtSlot()
1217         @QtCore.pyqtSlot(bool)
1218         @misc_utils.log_exception(_moduleLogger)
1219         def _on_close_window(self, checked = True):
1220                 self.close()
1221
1222
1223 def make_ugly(prettynumber):
1224         """
1225         function to take a phone number and strip out all non-numeric
1226         characters
1227
1228         >>> make_ugly("+012-(345)-678-90")
1229         '+01234567890'
1230         """
1231         return normalize_number(prettynumber)
1232
1233
1234 def normalize_number(prettynumber):
1235         """
1236         function to take a phone number and strip out all non-numeric
1237         characters
1238
1239         >>> normalize_number("+012-(345)-678-90")
1240         '+01234567890'
1241         >>> normalize_number("1-(345)-678-9000")
1242         '+13456789000'
1243         >>> normalize_number("+1-(345)-678-9000")
1244         '+13456789000'
1245         """
1246         uglynumber = re.sub('[^0-9+]', '', prettynumber)
1247
1248         if uglynumber.startswith("+"):
1249                 pass
1250         elif uglynumber.startswith("1"):
1251                 uglynumber = "+"+uglynumber
1252         elif 10 <= len(uglynumber):
1253                 assert uglynumber[0] not in ("+", "1")
1254                 uglynumber = "+1"+uglynumber
1255         else:
1256                 pass
1257
1258         return uglynumber
1259
1260
1261 def _make_pretty_with_areacode(phonenumber):
1262         prettynumber = "(%s)" % (phonenumber[0:3], )
1263         if 3 < len(phonenumber):
1264                 prettynumber += " %s" % (phonenumber[3:6], )
1265                 if 6 < len(phonenumber):
1266                         prettynumber += "-%s" % (phonenumber[6:], )
1267         return prettynumber
1268
1269
1270 def _make_pretty_local(phonenumber):
1271         prettynumber = "%s" % (phonenumber[0:3], )
1272         if 3 < len(phonenumber):
1273                 prettynumber += "-%s" % (phonenumber[3:], )
1274         return prettynumber
1275
1276
1277 def _make_pretty_international(phonenumber):
1278         prettynumber = phonenumber
1279         if phonenumber.startswith("1"):
1280                 prettynumber = "1 "
1281                 prettynumber += _make_pretty_with_areacode(phonenumber[1:])
1282         return prettynumber
1283
1284
1285 def make_pretty(phonenumber):
1286         """
1287         Function to take a phone number and return the pretty version
1288         pretty numbers:
1289                 if phonenumber begins with 0:
1290                         ...-(...)-...-....
1291                 if phonenumber begins with 1: ( for gizmo callback numbers )
1292                         1 (...)-...-....
1293                 if phonenumber is 13 digits:
1294                         (...)-...-....
1295                 if phonenumber is 10 digits:
1296                         ...-....
1297         >>> make_pretty("12")
1298         '12'
1299         >>> make_pretty("1234567")
1300         '123-4567'
1301         >>> make_pretty("2345678901")
1302         '+1 (234) 567-8901'
1303         >>> make_pretty("12345678901")
1304         '+1 (234) 567-8901'
1305         >>> make_pretty("01234567890")
1306         '+012 (345) 678-90'
1307         >>> make_pretty("+01234567890")
1308         '+012 (345) 678-90'
1309         >>> make_pretty("+12")
1310         '+1 (2)'
1311         >>> make_pretty("+123")
1312         '+1 (23)'
1313         >>> make_pretty("+1234")
1314         '+1 (234)'
1315         """
1316         if phonenumber is None or phonenumber is "":
1317                 return ""
1318
1319         phonenumber = normalize_number(phonenumber)
1320
1321         if phonenumber[0] == "+":
1322                 prettynumber = _make_pretty_international(phonenumber[1:])
1323                 if not prettynumber.startswith("+"):
1324                         prettynumber = "+"+prettynumber
1325         elif 8 < len(phonenumber) and phonenumber[0] in ("1", ):
1326                 prettynumber = _make_pretty_international(phonenumber)
1327         elif 7 < len(phonenumber):
1328                 prettynumber = _make_pretty_with_areacode(phonenumber)
1329         elif 3 < len(phonenumber):
1330                 prettynumber = _make_pretty_local(phonenumber)
1331         else:
1332                 prettynumber = phonenumber
1333         return prettynumber.strip()
1334
1335
1336 def abbrev_relative_date(date):
1337         """
1338         >>> abbrev_relative_date("42 hours ago")
1339         '42 h'
1340         >>> abbrev_relative_date("2 days ago")
1341         '2 d'
1342         >>> abbrev_relative_date("4 weeks ago")
1343         '4 w'
1344         """
1345         parts = date.split(" ")
1346         return "%s %s" % (parts[0], parts[1][0])
1347
1348
1349 def run():
1350         app = QtGui.QApplication([])
1351         handle = Dialcentral(app)
1352         qtpie.init_pies()
1353         return app.exec_()
1354
1355
1356 if __name__ == "__main__":
1357         logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
1358         logging.basicConfig(level=logging.DEBUG, format=logFormat)
1359         try:
1360                 os.makedirs(constants._data_path_)
1361         except OSError, e:
1362                 if e.errno != 17:
1363                         raise
1364
1365         val = run()
1366         sys.exit(val)