Finishing up a rough sketch of the UI including the delaying of tabs impl
[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._listSelection = QtGui.QComboBox()
559                 self._listSelection.addItems(self.HISTORY_ITEM_TYPES)
560                 self._listSelection.setCurrentIndex(self.HISTORY_ITEM_TYPES.index(self._selectedFilter))
561                 self._listSelection.currentIndexChanged.connect(self._on_filter_changed)
562
563                 self._itemStore = QtGui.QStandardItemModel()
564                 self._itemStore.setHorizontalHeaderLabels(self.HISTORY_COLUMNS)
565
566                 self._itemView = QtGui.QTreeView()
567                 self._itemView.setModel(self._itemStore)
568                 self._itemView.setUniformRowHeights(True)
569                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
570                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
571                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
572                 self._itemView.setHeaderHidden(True)
573                 self._itemView.activated.connect(self._on_row_activated)
574
575                 self._layout = QtGui.QVBoxLayout()
576                 self._layout.addWidget(self._listSelection)
577                 self._layout.addWidget(self._itemView)
578                 self._widget = QtGui.QWidget()
579                 self._widget.setLayout(self._layout)
580
581         @property
582         def toplevel(self):
583                 return self._widget
584
585         def enable(self):
586                 pass
587
588         def disable(self):
589                 pass
590
591         def refresh(self):
592                 pass
593
594         @misc_utils.log_exception(_moduleLogger)
595         def _on_filter_changed(self, newItem):
596                 self._selectedFilter = str(newItem)
597
598         @misc_utils.log_exception(_moduleLogger)
599         def _on_row_activated(self, index):
600                 rowIndex = index.row()
601
602
603 class Messages(object):
604
605         def __init__(self, app):
606                 self._app = app
607
608                 self._itemStore = QtGui.QStandardItemModel()
609                 self._itemStore.setHorizontalHeaderLabels(["Messages"])
610
611                 self._itemView = QtGui.QTreeView()
612                 self._itemView.setModel(self._itemStore)
613                 self._itemView.setUniformRowHeights(True)
614                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
615                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
616                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
617                 self._itemView.setHeaderHidden(True)
618                 self._itemView.activated.connect(self._on_row_activated)
619
620                 self._layout = QtGui.QVBoxLayout()
621                 self._layout.addWidget(self._itemView)
622                 self._widget = QtGui.QWidget()
623                 self._widget.setLayout(self._layout)
624
625         @property
626         def toplevel(self):
627                 return self._widget
628
629         def enable(self):
630                 pass
631
632         def disable(self):
633                 pass
634
635         def refresh(self):
636                 pass
637
638         @misc_utils.log_exception(_moduleLogger)
639         def _on_row_activated(self, index):
640                 rowIndex = index.row()
641
642
643 class Contacts(object):
644
645         def __init__(self, app):
646                 self._selectedFilter = ""
647                 self._app = app
648
649                 self._listSelection = QtGui.QComboBox()
650                 self._listSelection.addItems([])
651                 #self._listSelection.setCurrentIndex(self.HISTORY_ITEM_TYPES.index(self._selectedFilter))
652                 self._listSelection.currentIndexChanged.connect(self._on_filter_changed)
653
654                 self._itemStore = QtGui.QStandardItemModel()
655                 self._itemStore.setHorizontalHeaderLabels(["Contacts"])
656
657                 self._itemView = QtGui.QTreeView()
658                 self._itemView.setModel(self._itemStore)
659                 self._itemView.setUniformRowHeights(True)
660                 self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
661                 self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
662                 self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection)
663                 self._itemView.setHeaderHidden(True)
664                 self._itemView.activated.connect(self._on_row_activated)
665
666                 self._layout = QtGui.QVBoxLayout()
667                 self._layout.addWidget(self._listSelection)
668                 self._layout.addWidget(self._itemView)
669                 self._widget = QtGui.QWidget()
670                 self._widget.setLayout(self._layout)
671
672         @property
673         def toplevel(self):
674                 return self._widget
675
676         def enable(self):
677                 pass
678
679         def disable(self):
680                 pass
681
682         def refresh(self):
683                 pass
684
685         @misc_utils.log_exception(_moduleLogger)
686         def _on_filter_changed(self, newItem):
687                 self._selectedFilter = str(newItem)
688
689         @misc_utils.log_exception(_moduleLogger)
690         def _on_row_activated(self, index):
691                 rowIndex = index.row()
692
693
694 class MainWindow(object):
695
696         KEYPAD_TAB = 0
697         RECENT_TAB = 1
698         MESSAGES_TAB = 2
699         CONTACTS_TAB = 3
700         MAX_TABS = 4
701
702         _TAB_TITLES = [
703                 "Dialpad",
704                 "History",
705                 "Messages",
706                 "Contacts",
707         ]
708         assert len(_TAB_TITLES) == MAX_TABS
709
710         _TAB_CLASS = [
711                 Dialpad,
712                 History,
713                 Messages,
714                 Contacts,
715         ]
716         assert len(_TAB_CLASS) == MAX_TABS
717
718         def __init__(self, parent, app):
719                 self._fsContactsPath = os.path.join(constants._data_path_, "contacts")
720                 self._app = app
721
722                 self._errorDisplay = QErrorDisplay()
723
724                 self._tabsContents = [
725                         DelayedWidget(self._app)
726                         for i in xrange(self.MAX_TABS)
727                 ]
728
729                 self._tabWidget = QtGui.QTabWidget()
730                 if maeqt.screen_orientation() == QtCore.Qt.Vertical:
731                         self._tabWidget.setTabPosition(QtGui.QTabWidget.South)
732                 else:
733                         self._tabWidget.setTabPosition(QtGui.QTabWidget.West)
734                 for tabIndex, tabTitle in enumerate(self._TAB_TITLES):
735                         self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, tabTitle)
736                 self._tabWidget.currentChanged.connect(self._on_tab_changed)
737
738                 self._layout = QtGui.QVBoxLayout()
739                 self._layout.addWidget(self._errorDisplay.toplevel)
740                 self._layout.addWidget(self._tabWidget)
741
742                 centralWidget = QtGui.QWidget()
743                 centralWidget.setLayout(self._layout)
744
745                 self._window = QtGui.QMainWindow(parent)
746                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
747                 maeqt.set_autorient(self._window, True)
748                 maeqt.set_stackable(self._window, True)
749                 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
750                 self._window.setCentralWidget(centralWidget)
751
752                 self._loginTabAction = QtGui.QAction(None)
753                 self._loginTabAction.setText("Login")
754                 self._loginTabAction.triggered.connect(self._on_login)
755
756                 self._importTabAction = QtGui.QAction(None)
757                 self._importTabAction.setText("Import")
758                 self._importTabAction.triggered.connect(self._on_import)
759
760                 self._refreshTabAction = QtGui.QAction(None)
761                 self._refreshTabAction.setText("Refresh")
762                 self._refreshTabAction.setShortcut(QtGui.QKeySequence("CTRL+r"))
763                 self._refreshTabAction.triggered.connect(self._on_refresh)
764
765                 self._closeWindowAction = QtGui.QAction(None)
766                 self._closeWindowAction.setText("Close")
767                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
768                 self._closeWindowAction.triggered.connect(self._on_close_window)
769
770                 if IS_MAEMO:
771                         fileMenu = self._window.menuBar().addMenu("&File")
772                         fileMenu.addAction(self._loginTabAction)
773                         fileMenu.addAction(self._refreshTabAction)
774
775                         toolsMenu = self._window.menuBar().addMenu("&Tools")
776                         toolsMenu.addAction(self._importTabAction)
777
778                         self._window.addAction(self._closeWindowAction)
779                         self._window.addAction(self._app.quitAction)
780                         self._window.addAction(self._app.fullscreenAction)
781                 else:
782                         fileMenu = self._window.menuBar().addMenu("&File")
783                         fileMenu.addAction(self._loginTabAction)
784                         fileMenu.addAction(self._refreshTabAction)
785                         fileMenu.addAction(self._closeWindowAction)
786                         fileMenu.addAction(self._app.quitAction)
787
788                         viewMenu = self._window.menuBar().addMenu("&View")
789                         viewMenu.addAction(self._app.fullscreenAction)
790
791                         toolsMenu = self._window.menuBar().addMenu("&Tools")
792                         toolsMenu.addAction(self._importTabAction)
793
794                 self._window.addAction(self._app.logAction)
795
796                 self._initialize_tab(self._tabWidget.currentIndex())
797                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
798                 self._window.show()
799
800         @property
801         def window(self):
802                 return self._window
803
804         def walk_children(self):
805                 return ()
806
807         def show(self):
808                 self._window.show()
809                 for child in self.walk_children():
810                         child.show()
811
812         def hide(self):
813                 for child in self.walk_children():
814                         child.hide()
815                 self._window.hide()
816
817         def close(self):
818                 for child in self.walk_children():
819                         child.window.destroyed.disconnect(self._on_child_close)
820                         child.close()
821                 self._window.close()
822
823         def set_fullscreen(self, isFullscreen):
824                 if isFullscreen:
825                         self._window.showFullScreen()
826                 else:
827                         self._window.showNormal()
828                 for child in self.walk_children():
829                         child.set_fullscreen(isFullscreen)
830
831         def _initialize_tab(self, index):
832                 assert index < self.MAX_TABS
833                 if not self._tabsContents[index].has_child():
834                         self._tabsContents[index].set_child(self._TAB_CLASS[index](self._app))
835
836         @misc_utils.log_exception(_moduleLogger)
837         def _on_login(self, checked = True):
838                 pass
839
840         @misc_utils.log_exception(_moduleLogger)
841         def _on_tab_changed(self, index):
842                 self._initialize_tab(index)
843
844         @misc_utils.log_exception(_moduleLogger)
845         def _on_refresh(self, checked = True):
846                 index = self._tabWidget.currentIndex()
847                 self._tabsContents[index].refresh()
848
849         @misc_utils.log_exception(_moduleLogger)
850         def _on_import(self, checked = True):
851                 csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)")
852                 if not csvName:
853                         return
854                 shutil.copy2(csvName, self._fsContactsPath)
855
856         @misc_utils.log_exception(_moduleLogger)
857         def _on_close_window(self, checked = True):
858                 self.close()
859
860
861 def run():
862         app = QtGui.QApplication([])
863         handle = Dialcentral(app)
864         qtpie.init_pies()
865         return app.exec_()
866
867
868 if __name__ == "__main__":
869         logging.basicConfig(level = logging.DEBUG)
870         try:
871                 os.makedirs(constants._data_path_)
872         except OSError, e:
873                 if e.errno != 17:
874                         raise
875
876         val = run()
877         sys.exit(val)