Fixed issues with the credentials dialog
[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 logging
11
12 from PyQt4 import QtGui
13 from PyQt4 import QtCore
14
15 import constants
16 from util import qui_utils
17 from util import qtpie
18 from util import misc as misc_utils
19
20 import session
21
22
23 _moduleLogger = logging.getLogger(__name__)
24
25
26 IS_MAEMO = True
27
28
29 class Dialcentral(object):
30
31         _DATA_PATHS = [
32                 os.path.dirname(__file__),
33                 os.path.join(os.path.dirname(__file__), "../data"),
34                 os.path.join(os.path.dirname(__file__), "../lib"),
35                 '/usr/share/%s' % constants.__app_name__,
36                 '/usr/lib/%s' % constants.__app_name__,
37         ]
38
39         def __init__(self, app):
40                 self._app = app
41                 self._recent = []
42                 self._hiddenCategories = set()
43                 self._hiddenUnits = {}
44                 self._clipboard = QtGui.QApplication.clipboard()
45
46                 self._mainWindow = None
47
48                 self._fullscreenAction = QtGui.QAction(None)
49                 self._fullscreenAction.setText("Fullscreen")
50                 self._fullscreenAction.setCheckable(True)
51                 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
52                 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
53
54                 self._logAction = QtGui.QAction(None)
55                 self._logAction.setText("Log")
56                 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
57                 self._logAction.triggered.connect(self._on_log)
58
59                 self._quitAction = QtGui.QAction(None)
60                 self._quitAction.setText("Quit")
61                 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
62                 self._quitAction.triggered.connect(self._on_quit)
63
64                 self._app.lastWindowClosed.connect(self._on_app_quit)
65                 self.load_settings()
66
67                 self._mainWindow = MainWindow(None, self)
68                 self._mainWindow.window.destroyed.connect(self._on_child_close)
69
70         def load_settings(self):
71                 try:
72                         with open(constants._user_settings_, "r") as settingsFile:
73                                 settings = simplejson.load(settingsFile)
74                 except IOError, e:
75                         _moduleLogger.info("No settings")
76                         settings = {}
77                 except ValueError:
78                         _moduleLogger.info("Settings were corrupt")
79                         settings = {}
80
81                 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
82
83         def save_settings(self):
84                 settings = {
85                         "isFullScreen": self._fullscreenAction.isChecked(),
86                 }
87                 with open(constants._user_settings_, "w") as settingsFile:
88                         simplejson.dump(settings, settingsFile)
89
90         @property
91         def fullscreenAction(self):
92                 return self._fullscreenAction
93
94         @property
95         def logAction(self):
96                 return self._logAction
97
98         @property
99         def quitAction(self):
100                 return self._quitAction
101
102         def _close_windows(self):
103                 if self._mainWindow is not None:
104                         self._mainWindow.window.destroyed.disconnect(self._on_child_close)
105                         self._mainWindow.close()
106                         self._mainWindow = None
107
108         @QtCore.pyqtSlot()
109         @QtCore.pyqtSlot(bool)
110         @misc_utils.log_exception(_moduleLogger)
111         def _on_app_quit(self, checked = False):
112                 self.save_settings()
113
114         @QtCore.pyqtSlot(QtCore.QObject)
115         @misc_utils.log_exception(_moduleLogger)
116         def _on_child_close(self, obj = None):
117                 self._mainWindow = None
118
119         @QtCore.pyqtSlot()
120         @QtCore.pyqtSlot(bool)
121         @misc_utils.log_exception(_moduleLogger)
122         def _on_toggle_fullscreen(self, checked = False):
123                 for window in self._walk_children():
124                         window.set_fullscreen(checked)
125
126         @QtCore.pyqtSlot()
127         @QtCore.pyqtSlot(bool)
128         @misc_utils.log_exception(_moduleLogger)
129         def _on_log(self, checked = False):
130                 with open(constants._user_logpath_, "r") as f:
131                         logLines = f.xreadlines()
132                         log = "".join(logLines)
133                         self._clipboard.setText(log)
134
135         @QtCore.pyqtSlot()
136         @QtCore.pyqtSlot(bool)
137         @misc_utils.log_exception(_moduleLogger)
138         def _on_quit(self, checked = False):
139                 self._close_windows()
140
141
142 class CredentialsDialog(object):
143
144         def __init__(self):
145                 self._usernameField = QtGui.QLineEdit()
146                 self._passwordField = QtGui.QLineEdit()
147                 self._passwordField.setEchoMode(QtGui.QLineEdit.PasswordEchoOnEdit)
148
149                 self._credLayout = QtGui.QGridLayout()
150                 self._credLayout.addWidget(QtGui.QLabel("Username"), 0, 0)
151                 self._credLayout.addWidget(self._usernameField, 0, 1)
152                 self._credLayout.addWidget(QtGui.QLabel("Password"), 1, 0)
153                 self._credLayout.addWidget(self._passwordField, 1, 1)
154
155                 self._loginButton = QtGui.QPushButton("&Login")
156                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
157                 self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole)
158
159                 self._layout = QtGui.QVBoxLayout()
160                 self._layout.addLayout(self._credLayout)
161                 self._layout.addWidget(self._buttonLayout)
162
163                 self._dialog = QtGui.QDialog()
164                 self._dialog.setWindowTitle("Login")
165                 self._dialog.setLayout(self._layout)
166                 self._dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)
167                 qui_utils.set_autorient(self._dialog, True)
168                 self._buttonLayout.accepted.connect(self._dialog.accept)
169                 self._buttonLayout.rejected.connect(self._dialog.reject)
170
171         def run(self, defaultUsername, defaultPassword, parent=None):
172                 self._dialog.setParent(parent, QtCore.Qt.Dialog)
173                 try:
174                         self._usernameField.setText(defaultUsername)
175                         self._passwordField.setText(defaultPassword)
176
177                         response = self._dialog.exec_()
178                         if response == QtGui.QDialog.Accepted:
179                                 return str(self._usernameField.text()), str(self._passwordField.text())
180                         elif response == QtGui.QDialog.Rejected:
181                                 raise RuntimeError("Login Cancelled")
182                 finally:
183                         self._dialog.setParent(None, QtCore.Qt.Dialog)
184
185
186 class AccountDialog(object):
187
188         def __init__(self):
189                 self._accountNumberLabel = QtGui.QLabel("NUMBER NOT SET")
190                 self._clearButton = QtGui.QPushButton("Clear Account")
191                 self._clearButton.clicked.connect(self._on_clear)
192                 self._doClear = False
193
194                 self._credLayout = QtGui.QGridLayout()
195                 self._credLayout.addWidget(QtGui.QLabel("Account"), 0, 0)
196                 self._credLayout.addWidget(self._accountNumberLabel, 0, 1)
197                 self._credLayout.addWidget(QtGui.QLabel("Callback"), 1, 0)
198                 self._credLayout.addWidget(self._clearButton, 2, 1)
199
200                 self._loginButton = QtGui.QPushButton("&Login")
201                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
202                 self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole)
203
204                 self._layout = QtGui.QVBoxLayout()
205                 self._layout.addLayout(self._credLayout)
206                 self._layout.addLayout(self._buttonLayout)
207
208                 self._dialog = QtGui.QDialog()
209                 self._dialog.setWindowTitle("Login")
210                 self._dialog.setLayout(self._layout)
211                 qui_utils.set_autorient(self._dialog, True)
212                 self._buttonLayout.accepted.connect(self._dialog.accept)
213                 self._buttonLayout.rejected.connect(self._dialog.reject)
214
215         @property
216         def doClear(self):
217                 return self._doClear
218
219         accountNumber = property(
220                 lambda self: str(self._accountNumberLabel.text()),
221                 lambda self, num: self._accountNumberLabel.setText(num),
222         )
223
224         def run(self, defaultUsername, defaultPassword, parent=None):
225                 self._doClear = False
226                 self._dialog.setParent(parent)
227                 self._usernameField.setText(defaultUsername)
228                 self._passwordField.setText(defaultPassword)
229
230                 response = self._dialog.exec_()
231                 if response == QtGui.QDialog.Accepted:
232                         return str(self._usernameField.text()), str(self._passwordField.text())
233                 elif response == QtGui.QDialog.Rejected:
234                         raise RuntimeError("Login Cancelled")
235
236         @QtCore.pyqtSlot()
237         @QtCore.pyqtSlot(bool)
238         def _on_clear(self, checked = False):
239                 self._doClear = True
240                 self._dialog.accept()
241
242
243 class SMSEntryWindow(object):
244
245         def __init__(self, parent, app, session, errorLog):
246                 self._contacts = []
247                 self._app = app
248                 self._session = session
249                 self._errorLog = errorLog
250
251                 self._history = QtGui.QListView()
252                 self._smsEntry = QtGui.QTextEdit()
253                 self._smsEntry.textChanged.connect(self._on_letter_count_changed)
254
255                 self._entryLayout = QtGui.QVBoxLayout()
256                 self._entryLayout.addWidget(self._history)
257                 self._entryLayout.addWidget(self._smsEntry)
258                 self._entryWidget = QtGui.QWidget()
259                 self._entryWidget.setLayout(self._entryLayout)
260                 self._scrollEntry = QtGui.QScrollArea()
261                 self._scrollEntry.setWidget(self._entryWidget)
262                 self._scrollEntry.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom)
263                 self._scrollEntry.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
264                 self._scrollEntry.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
265
266                 self._characterCountLabel = QtGui.QLabel("Letters: %s" % 0)
267                 self._numberSelector = None
268                 self._smsButton = QtGui.QPushButton("SMS")
269                 self._dialButton = QtGui.QPushButton("Dial")
270
271                 self._buttonLayout = QtGui.QHBoxLayout()
272                 self._buttonLayout.addWidget(self._characterCountLabel)
273                 self._buttonLayout.addWidget(self._smsButton)
274                 self._buttonLayout.addWidget(self._dialButton)
275
276                 self._layout = QtGui.QVBoxLayout()
277                 self._layout.addLayout(self._entryLayout)
278                 self._layout.addLayout(self._buttonLayout)
279
280                 centralWidget = QtGui.QWidget()
281                 centralWidget.setLayout(self._layout)
282
283                 self._window = QtGui.QMainWindow(parent)
284                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)
285                 qui_utils.set_autorient(self._window, True)
286                 qui_utils.set_stackable(self._window, True)
287                 self._window.setWindowTitle("Contact")
288                 self._window.setCentralWidget(centralWidget)
289
290         def _update_letter_count(self):
291                 count = self._smsEntry.toPlainText().size()
292                 self._characterCountLabel.setText("Letters: %s" % count)
293
294         def _update_button_state(self):
295                 if len(self._contacts) == 0:
296                         self._dialButton.setEnabled(False)
297                         self._smsButton.setEnabled(False)
298                 elif len(self._contacts) == 1:
299                         count = self._smsEntry.toPlainText().size()
300                         if count == 0:
301                                 self._dialButton.setEnabled(True)
302                                 self._smsButton.setEnabled(False)
303                         else:
304                                 self._dialButton.setEnabled(False)
305                                 self._smsButton.setEnabled(True)
306                 else:
307                         self._dialButton.setEnabled(False)
308                         self._smsButton.setEnabled(True)
309
310         @QtCore.pyqtSlot()
311         @misc_utils.log_exception(_moduleLogger)
312         def _on_letter_count_changed(self):
313                 self._update_letter_count()
314                 self._update_button_state()
315
316
317 class DelayedWidget(object):
318
319         def __init__(self, app):
320                 self._layout = QtGui.QVBoxLayout()
321                 self._widget = QtGui.QWidget()
322                 self._widget.setLayout(self._layout)
323
324                 self._child = None
325                 self._isEnabled = True
326
327         @property
328         def toplevel(self):
329                 return self._widget
330
331         def has_child(self):
332                 return self._child is not None
333
334         def set_child(self, child):
335                 if self._child is not None:
336                         self._layout.removeWidget(self._child.toplevel)
337                 self._child = child
338                 if self._child is not None:
339                         self._layout.addWidget(self._child.toplevel)
340
341                 if self._isEnabled:
342                         self._child.enable()
343                 else:
344                         self._child.disable()
345
346         def enable(self):
347                 self._isEnabled = True
348                 if self._child is not None:
349                         self._child.enable()
350
351         def disable(self):
352                 self._isEnabled = False
353                 if self._child is not None:
354                         self._child.disable()
355
356         def clear(self):
357                 if self._child is not None:
358                         self._child.clear()
359
360         def refresh(self):
361                 if self._child is not None:
362                         self._child.refresh()
363
364
365 class Dialpad(object):
366
367         def __init__(self, app, session, errorLog):
368                 self._app = app
369                 self._session = session
370                 self._errorLog = errorLog
371
372                 self._plus = self._generate_key_button("+", "")
373                 self._entry = QtGui.QLineEdit()
374
375                 backAction = QtGui.QAction(None)
376                 backAction.setText("Back")
377                 backAction.triggered.connect(self._on_backspace)
378                 backPieItem = qtpie.QActionPieItem(backAction)
379                 clearAction = QtGui.QAction(None)
380                 clearAction.setText("Clear")
381                 clearAction.triggered.connect(self._on_clear_text)
382                 clearPieItem = qtpie.QActionPieItem(clearAction)
383                 self._back = qtpie.QPieButton(backPieItem)
384                 self._back.set_center(backPieItem)
385                 self._back.insertItem(qtpie.PieFiling.NULL_CENTER)
386                 self._back.insertItem(clearPieItem)
387                 self._back.insertItem(qtpie.PieFiling.NULL_CENTER)
388                 self._back.insertItem(qtpie.PieFiling.NULL_CENTER)
389
390                 self._entryLayout = QtGui.QHBoxLayout()
391                 self._entryLayout.addWidget(self._plus, 0, QtCore.Qt.AlignCenter)
392                 self._entryLayout.addWidget(self._entry, 10)
393                 self._entryLayout.addWidget(self._back, 0, QtCore.Qt.AlignCenter)
394
395                 self._smsButton = QtGui.QPushButton("SMS")
396                 self._smsButton.clicked.connect(self._on_sms_clicked)
397                 self._callButton = QtGui.QPushButton("Call")
398                 self._callButton.clicked.connect(self._on_call_clicked)
399
400                 self._padLayout = QtGui.QGridLayout()
401                 rows = [0, 0, 0, 1, 1, 1, 2, 2, 2]
402                 columns = [0, 1, 2] * 3
403                 keys = [
404                         ("1", ""),
405                         ("2", "ABC"),
406                         ("3", "DEF"),
407                         ("4", "GHI"),
408                         ("5", "JKL"),
409                         ("6", "MNO"),
410                         ("7", "PQRS"),
411                         ("8", "TUV"),
412                         ("9", "WXYZ"),
413                 ]
414                 for (num, letters), (row, column) in zip(keys, zip(rows, columns)):
415                         self._padLayout.addWidget(
416                                 self._generate_key_button(num, letters), row, column, QtCore.Qt.AlignCenter
417                         )
418                 self._padLayout.addWidget(self._smsButton, 3, 0)
419                 self._padLayout.addWidget(
420                         self._generate_key_button("0", ""), 3, 1, QtCore.Qt.AlignCenter
421                 )
422                 self._padLayout.addWidget(self._callButton, 3, 2)
423
424                 self._layout = QtGui.QVBoxLayout()
425                 self._layout.addLayout(self._entryLayout)
426                 self._layout.addLayout(self._padLayout)
427                 self._widget = QtGui.QWidget()
428                 self._widget.setLayout(self._layout)
429
430         @property
431         def toplevel(self):
432                 return self._widget
433
434         def enable(self):
435                 self._smsButton.setEnabled(True)
436                 self._callButton.setEnabled(True)
437
438         def disable(self):
439                 self._smsButton.setEnabled(False)
440                 self._callButton.setEnabled(False)
441
442         def clear(self):
443                 pass
444
445         def refresh(self):
446                 pass
447
448         def _generate_key_button(self, center, letters):
449                 centerPieItem = self._generate_button_slice(center)
450                 button = qtpie.QPieButton(centerPieItem)
451                 button.set_center(centerPieItem)
452
453                 if len(letters) == 0:
454                         for i in xrange(8):
455                                 pieItem = qtpie.PieFiling.NULL_CENTER
456                                 button.insertItem(pieItem)
457                 elif len(letters) in [3, 4]:
458                         for i in xrange(6 - len(letters)):
459                                 pieItem = qtpie.PieFiling.NULL_CENTER
460                                 button.insertItem(pieItem)
461
462                         for letter in letters:
463                                 pieItem = self._generate_button_slice(letter)
464                                 button.insertItem(pieItem)
465
466                         for i in xrange(2):
467                                 pieItem = qtpie.PieFiling.NULL_CENTER
468                                 button.insertItem(pieItem)
469                 else:
470                         raise NotImplementedError("Cannot handle %r" % letters)
471                 return button
472
473         def _generate_button_slice(self, letter):
474                 action = QtGui.QAction(None)
475                 action.setText(letter)
476                 action.triggered.connect(lambda: self._on_keypress(letter))
477                 pieItem = qtpie.QActionPieItem(action)
478                 return pieItem
479
480         @misc_utils.log_exception(_moduleLogger)
481         def _on_keypress(self, key):
482                 self._entry.insert(key)
483
484         @misc_utils.log_exception(_moduleLogger)
485         def _on_backspace(self, toggled = False):
486                 self._entry.backspace()
487
488         @misc_utils.log_exception(_moduleLogger)
489         def _on_clear_text(self, toggled = False):
490                 self._entry.clear()
491
492         @QtCore.pyqtSlot()
493         @QtCore.pyqtSlot(bool)
494         @misc_utils.log_exception(_moduleLogger)
495         def _on_sms_clicked(self, checked = False):
496                 number = str(self._entry.text())
497                 self._entry.clear()
498                 self._session.draft.add_contact(number, [])
499
500         @QtCore.pyqtSlot()
501         @QtCore.pyqtSlot(bool)
502         @misc_utils.log_exception(_moduleLogger)
503         def _on_call_clicked(self, checked = False):
504                 number = str(self._entry.text())
505                 self._entry.clear()
506                 self._session.draft.add_contact(number, [])
507                 self._session.draft.call()
508
509
510 class History(object):
511
512         DATE_IDX = 0
513         ACTION_IDX = 1
514         NUMBER_IDX = 2
515         FROM_IDX = 3
516         MAX_IDX = 4
517
518         HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"]
519         HISTORY_COLUMNS = ["When", "What", "Number", "From"]
520         assert len(HISTORY_COLUMNS) == MAX_IDX
521
522         def __init__(self, app, session, errorLog):
523                 self._selectedFilter = self.HISTORY_ITEM_TYPES[0]
524                 self._app = app
525                 self._session = session
526                 self._session.historyUpdated.connect(self._on_history_updated)
527                 self._errorLog = errorLog
528
529                 self._typeSelection = QtGui.QComboBox()
530                 self._typeSelection.addItems(self.HISTORY_ITEM_TYPES)
531                 self._typeSelection.setCurrentIndex(
532                         self.HISTORY_ITEM_TYPES.index(self._selectedFilter)
533                 )
534                 self._typeSelection.currentIndexChanged.connect(self._on_filter_changed)
535
536                 self._itemStore = QtGui.QStandardItemModel()
537                 self._itemStore.setHorizontalHeaderLabels(self.HISTORY_COLUMNS)
538
539                 self._itemView = QtGui.QTreeView()
540                 self._itemView.setModel(self._itemStore)
541                 self._itemView.setUniformRowHeights(True)
542                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
543                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
544                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
545                 self._itemView.setHeaderHidden(True)
546                 self._itemView.activated.connect(self._on_row_activated)
547
548                 self._layout = QtGui.QVBoxLayout()
549                 self._layout.addWidget(self._typeSelection)
550                 self._layout.addWidget(self._itemView)
551                 self._widget = QtGui.QWidget()
552                 self._widget.setLayout(self._layout)
553
554                 self._populate_items()
555
556         @property
557         def toplevel(self):
558                 return self._widget
559
560         def enable(self):
561                 self._itemView.setEnabled(True)
562
563         def disable(self):
564                 self._itemView.setEnabled(False)
565
566         def clear(self):
567                 self._itemView.clear()
568
569         def refresh(self):
570                 pass
571
572         def _populate_items(self):
573                 self._errorLog.push_message("Not supported")
574
575         @QtCore.pyqtSlot(str)
576         @misc_utils.log_exception(_moduleLogger)
577         def _on_filter_changed(self, newItem):
578                 self._selectedFilter = str(newItem)
579
580         @QtCore.pyqtSlot()
581         @misc_utils.log_exception(_moduleLogger)
582         def _on_history_updated(self):
583                 self._populate_items()
584
585         @QtCore.pyqtSlot(QtCore.QModelIndex)
586         @misc_utils.log_exception(_moduleLogger)
587         def _on_row_activated(self, index):
588                 rowIndex = index.row()
589                 #self._session.draft.add_contact(number, details)
590
591
592 class Messages(object):
593
594         NO_MESSAGES = "None"
595         VOICEMAIL_MESSAGES = "Voicemail"
596         TEXT_MESSAGES = "SMS"
597         ALL_TYPES = "All Messages"
598         MESSAGE_TYPES = [NO_MESSAGES, VOICEMAIL_MESSAGES, TEXT_MESSAGES, ALL_TYPES]
599
600         UNREAD_STATUS = "Unread"
601         UNARCHIVED_STATUS = "Inbox"
602         ALL_STATUS = "Any"
603         MESSAGE_STATUSES = [UNREAD_STATUS, UNARCHIVED_STATUS, ALL_STATUS]
604
605         def __init__(self, app, session, errorLog):
606                 self._selectedTypeFilter = self.ALL_TYPES
607                 self._selectedStatusFilter = self.ALL_STATUS
608                 self._app = app
609                 self._session = session
610                 self._session.messagesUpdated.connect(self._on_messages_updated)
611                 self._errorLog = errorLog
612
613                 self._typeSelection = QtGui.QComboBox()
614                 self._typeSelection.addItems(self.MESSAGE_TYPES)
615                 self._typeSelection.setCurrentIndex(
616                         self.MESSAGE_TYPES.index(self._selectedTypeFilter)
617                 )
618                 self._typeSelection.currentIndexChanged.connect(self._on_type_filter_changed)
619
620                 self._statusSelection = QtGui.QComboBox()
621                 self._statusSelection.addItems(self.MESSAGE_STATUSES)
622                 self._statusSelection.setCurrentIndex(
623                         self.MESSAGE_STATUSES.index(self._selectedStatusFilter)
624                 )
625                 self._statusSelection.currentIndexChanged.connect(self._on_status_filter_changed)
626
627                 self._selectionLayout = QtGui.QHBoxLayout()
628                 self._selectionLayout.addWidget(self._typeSelection)
629                 self._selectionLayout.addWidget(self._statusSelection)
630
631                 self._itemStore = QtGui.QStandardItemModel()
632                 self._itemStore.setHorizontalHeaderLabels(["Messages"])
633
634                 self._itemView = QtGui.QTreeView()
635                 self._itemView.setModel(self._itemStore)
636                 self._itemView.setUniformRowHeights(True)
637                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
638                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
639                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
640                 self._itemView.setHeaderHidden(True)
641                 self._itemView.activated.connect(self._on_row_activated)
642
643                 self._layout = QtGui.QVBoxLayout()
644                 self._layout.addLayout(self._selectionLayout)
645                 self._layout.addWidget(self._itemView)
646                 self._widget = QtGui.QWidget()
647                 self._widget.setLayout(self._layout)
648
649                 self._populate_items()
650
651         @property
652         def toplevel(self):
653                 return self._widget
654
655         def enable(self):
656                 self._itemView.setEnabled(True)
657
658         def disable(self):
659                 self._itemView.setEnabled(False)
660
661         def clear(self):
662                 self._itemView.clear()
663
664         def refresh(self):
665                 pass
666
667         def _populate_items(self):
668                 self._errorLog.push_message("Not supported")
669
670         @QtCore.pyqtSlot(str)
671         @misc_utils.log_exception(_moduleLogger)
672         def _on_type_filter_changed(self, newItem):
673                 self._selectedTypeFilter = str(newItem)
674
675         @QtCore.pyqtSlot(str)
676         @misc_utils.log_exception(_moduleLogger)
677         def _on_status_filter_changed(self, newItem):
678                 self._selectedStatusFilter = str(newItem)
679
680         @QtCore.pyqtSlot()
681         @misc_utils.log_exception(_moduleLogger)
682         def _on_messages_updated(self):
683                 self._populate_items()
684
685         @QtCore.pyqtSlot(QtCore.QModelIndex)
686         @misc_utils.log_exception(_moduleLogger)
687         def _on_row_activated(self, index):
688                 rowIndex = index.row()
689                 #self._session.draft.add_contact(number, details)
690
691
692 class Contacts(object):
693
694         def __init__(self, app, session, errorLog):
695                 self._selectedFilter = ""
696                 self._app = app
697                 self._session = session
698                 self._session.contactsUpdated.connect(self._on_contacts_updated)
699                 self._errorLog = errorLog
700
701                 self._listSelection = QtGui.QComboBox()
702                 self._listSelection.addItems([])
703                 #self._listSelection.setCurrentIndex(self.HISTORY_ITEM_TYPES.index(self._selectedFilter))
704                 self._listSelection.currentIndexChanged.connect(self._on_filter_changed)
705
706                 self._itemStore = QtGui.QStandardItemModel()
707                 self._itemStore.setHorizontalHeaderLabels(["Contacts"])
708
709                 self._itemView = QtGui.QTreeView()
710                 self._itemView.setModel(self._itemStore)
711                 self._itemView.setUniformRowHeights(True)
712                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
713                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
714                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
715                 self._itemView.setHeaderHidden(True)
716                 self._itemView.activated.connect(self._on_row_activated)
717
718                 self._layout = QtGui.QVBoxLayout()
719                 self._layout.addWidget(self._listSelection)
720                 self._layout.addWidget(self._itemView)
721                 self._widget = QtGui.QWidget()
722                 self._widget.setLayout(self._layout)
723
724                 self._populate_items()
725
726         @property
727         def toplevel(self):
728                 return self._widget
729
730         def enable(self):
731                 self._itemView.setEnabled(True)
732
733         def disable(self):
734                 self._itemView.setEnabled(False)
735
736         def clear(self):
737                 self._itemView.clear()
738
739         def refresh(self):
740                 pass
741
742         def _populate_items(self):
743                 self._errorLog.push_message("Not supported")
744
745         @QtCore.pyqtSlot(str)
746         @misc_utils.log_exception(_moduleLogger)
747         def _on_filter_changed(self, newItem):
748                 self._selectedFilter = str(newItem)
749
750         @QtCore.pyqtSlot()
751         @misc_utils.log_exception(_moduleLogger)
752         def _on_contacts_updated(self):
753                 self._populate_items()
754
755         @QtCore.pyqtSlot(QtCore.QModelIndex)
756         @misc_utils.log_exception(_moduleLogger)
757         def _on_row_activated(self, index):
758                 rowIndex = index.row()
759                 #self._session.draft.add_contact(number, details)
760
761
762 class MainWindow(object):
763
764         KEYPAD_TAB = 0
765         RECENT_TAB = 1
766         MESSAGES_TAB = 2
767         CONTACTS_TAB = 3
768         MAX_TABS = 4
769
770         _TAB_TITLES = [
771                 "Dialpad",
772                 "History",
773                 "Messages",
774                 "Contacts",
775         ]
776         assert len(_TAB_TITLES) == MAX_TABS
777
778         _TAB_CLASS = [
779                 Dialpad,
780                 History,
781                 Messages,
782                 Contacts,
783         ]
784         assert len(_TAB_CLASS) == MAX_TABS
785
786         def __init__(self, parent, app):
787                 self._fsContactsPath = os.path.join(constants._data_path_, "contacts")
788                 self._app = app
789                 self._session = session.Session()
790                 self._session.error.connect(self._on_session_error)
791                 self._session.loggedIn.connect(self._on_login)
792                 self._session.loggedOut.connect(self._on_logout)
793
794                 self._credentialsDialog = None
795
796                 self._errorLog = qui_utils.QErrorLog()
797                 self._errorDisplay = qui_utils.ErrorDisplay(self._errorLog)
798
799                 self._tabsContents = [
800                         DelayedWidget(self._app)
801                         for i in xrange(self.MAX_TABS)
802                 ]
803                 for tab in self._tabsContents:
804                         tab.disable()
805
806                 self._tabWidget = QtGui.QTabWidget()
807                 if qui_utils.screen_orientation() == QtCore.Qt.Vertical:
808                         self._tabWidget.setTabPosition(QtGui.QTabWidget.South)
809                 else:
810                         self._tabWidget.setTabPosition(QtGui.QTabWidget.West)
811                 for tabIndex, tabTitle in enumerate(self._TAB_TITLES):
812                         self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, tabTitle)
813                 self._tabWidget.currentChanged.connect(self._on_tab_changed)
814
815                 self._layout = QtGui.QVBoxLayout()
816                 self._layout.addWidget(self._errorDisplay.toplevel)
817                 self._layout.addWidget(self._tabWidget)
818
819                 centralWidget = QtGui.QWidget()
820                 centralWidget.setLayout(self._layout)
821
822                 self._window = QtGui.QMainWindow(parent)
823                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
824                 qui_utils.set_autorient(self._window, True)
825                 qui_utils.set_stackable(self._window, True)
826                 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
827                 self._window.setCentralWidget(centralWidget)
828
829                 self._loginTabAction = QtGui.QAction(None)
830                 self._loginTabAction.setText("Login")
831                 self._loginTabAction.triggered.connect(self._on_login_requested)
832
833                 self._importTabAction = QtGui.QAction(None)
834                 self._importTabAction.setText("Import")
835                 self._importTabAction.triggered.connect(self._on_import)
836
837                 self._refreshTabAction = QtGui.QAction(None)
838                 self._refreshTabAction.setText("Refresh")
839                 self._refreshTabAction.setShortcut(QtGui.QKeySequence("CTRL+r"))
840                 self._refreshTabAction.triggered.connect(self._on_refresh)
841
842                 self._closeWindowAction = QtGui.QAction(None)
843                 self._closeWindowAction.setText("Close")
844                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
845                 self._closeWindowAction.triggered.connect(self._on_close_window)
846
847                 if IS_MAEMO:
848                         fileMenu = self._window.menuBar().addMenu("&File")
849                         fileMenu.addAction(self._loginTabAction)
850                         fileMenu.addAction(self._refreshTabAction)
851
852                         toolsMenu = self._window.menuBar().addMenu("&Tools")
853                         toolsMenu.addAction(self._importTabAction)
854
855                         self._window.addAction(self._closeWindowAction)
856                         self._window.addAction(self._app.quitAction)
857                         self._window.addAction(self._app.fullscreenAction)
858                 else:
859                         fileMenu = self._window.menuBar().addMenu("&File")
860                         fileMenu.addAction(self._loginTabAction)
861                         fileMenu.addAction(self._refreshTabAction)
862                         fileMenu.addAction(self._closeWindowAction)
863                         fileMenu.addAction(self._app.quitAction)
864
865                         viewMenu = self._window.menuBar().addMenu("&View")
866                         viewMenu.addAction(self._app.fullscreenAction)
867
868                         toolsMenu = self._window.menuBar().addMenu("&Tools")
869                         toolsMenu.addAction(self._importTabAction)
870
871                 self._window.addAction(self._app.logAction)
872
873                 self._initialize_tab(self._tabWidget.currentIndex())
874                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
875                 self._window.show()
876
877         @property
878         def window(self):
879                 return self._window
880
881         def walk_children(self):
882                 return ()
883
884         def show(self):
885                 self._window.show()
886                 for child in self.walk_children():
887                         child.show()
888
889         def hide(self):
890                 for child in self.walk_children():
891                         child.hide()
892                 self._window.hide()
893
894         def close(self):
895                 for child in self.walk_children():
896                         child.window.destroyed.disconnect(self._on_child_close)
897                         child.close()
898                 self._window.close()
899
900         def set_fullscreen(self, isFullscreen):
901                 if isFullscreen:
902                         self._window.showFullScreen()
903                 else:
904                         self._window.showNormal()
905                 for child in self.walk_children():
906                         child.set_fullscreen(isFullscreen)
907
908         def _initialize_tab(self, index):
909                 assert index < self.MAX_TABS
910                 if not self._tabsContents[index].has_child():
911                         self._tabsContents[index].set_child(
912                                 self._TAB_CLASS[index](self._app, self._session, self._errorLog)
913                         )
914
915         @QtCore.pyqtSlot(str)
916         @misc_utils.log_exception(_moduleLogger)
917         def _on_session_error(self, message):
918                 self._errorLog.push_message(message)
919
920         @QtCore.pyqtSlot()
921         @misc_utils.log_exception(_moduleLogger)
922         def _on_login(self):
923                 for tab in self._tabsContents:
924                         tab.enable()
925
926         @QtCore.pyqtSlot()
927         @misc_utils.log_exception(_moduleLogger)
928         def _on_logout(self):
929                 for tab in self._tabsContents:
930                         tab.disable()
931
932         @QtCore.pyqtSlot()
933         @QtCore.pyqtSlot(bool)
934         @misc_utils.log_exception(_moduleLogger)
935         def _on_login_requested(self, checked = True):
936                 if self._credentialsDialog is None:
937                         self._credentialsDialog = CredentialsDialog()
938                 username, password = self._credentialsDialog.run("", "", self.window)
939                 self._session.login(username, password)
940
941         @QtCore.pyqtSlot(int)
942         @misc_utils.log_exception(_moduleLogger)
943         def _on_tab_changed(self, index):
944                 self._initialize_tab(index)
945
946         @QtCore.pyqtSlot()
947         @QtCore.pyqtSlot(bool)
948         @misc_utils.log_exception(_moduleLogger)
949         def _on_refresh(self, checked = True):
950                 index = self._tabWidget.currentIndex()
951                 self._tabsContents[index].refresh()
952
953         @QtCore.pyqtSlot()
954         @QtCore.pyqtSlot(bool)
955         @misc_utils.log_exception(_moduleLogger)
956         def _on_import(self, checked = True):
957                 csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)")
958                 if not csvName:
959                         return
960                 shutil.copy2(csvName, self._fsContactsPath)
961
962         @QtCore.pyqtSlot()
963         @QtCore.pyqtSlot(bool)
964         @misc_utils.log_exception(_moduleLogger)
965         def _on_close_window(self, checked = True):
966                 self.close()
967
968
969 def run():
970         app = QtGui.QApplication([])
971         handle = Dialcentral(app)
972         qtpie.init_pies()
973         return app.exec_()
974
975
976 if __name__ == "__main__":
977         logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
978         logging.basicConfig(level=logging.DEBUG, format=logFormat)
979         try:
980                 os.makedirs(constants._data_path_)
981         except OSError, e:
982                 if e.errno != 17:
983                         raise
984
985         val = run()
986         sys.exit(val)