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