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