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