4 from __future__ import with_statement
12 from PyQt4 import QtGui
13 from PyQt4 import QtCore
16 from util import qtpie
17 from util import qui_utils
18 from util import misc as misc_utils
23 _moduleLogger = logging.getLogger(__name__)
26 class LedWrapper(object):
29 self._ledHandler = None
34 if self._ledHandler is not None:
35 self._ledHandler.off()
43 self._ledHandler = led_handler.LedHandler()
45 _moduleLogger.exception('Unable to initialize LED Handling: "%s"' % str(e))
46 self._ledHandler = None
50 class Dialcentral(object):
53 os.path.join(os.path.dirname(__file__), "../share"),
54 os.path.join(os.path.dirname(__file__), "../data"),
57 def __init__(self, app):
60 self._hiddenCategories = set()
61 self._hiddenUnits = {}
62 self._clipboard = QtGui.QApplication.clipboard()
64 self._ledHandler = LedWrapper()
65 self.notifyOnMissed = False
66 self.notifyOnVoicemail = False
67 self.notifyOnSms = False
69 self._mainWindow = None
71 self._fullscreenAction = QtGui.QAction(None)
72 self._fullscreenAction.setText("Fullscreen")
73 self._fullscreenAction.setCheckable(True)
74 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
75 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
77 self._logAction = QtGui.QAction(None)
78 self._logAction.setText("Log")
79 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
80 self._logAction.triggered.connect(self._on_log)
82 self._quitAction = QtGui.QAction(None)
83 self._quitAction.setText("Quit")
84 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
85 self._quitAction.triggered.connect(self._on_quit)
87 self._app.lastWindowClosed.connect(self._on_app_quit)
88 self._mainWindow = MainWindow(None, self)
89 self._mainWindow.window.destroyed.connect(self._on_child_close)
93 if alarm_handler.AlarmHandler is not alarm_handler._NoneAlarmHandler:
94 self._alarmHandler = alarm_handler.AlarmHandler()
96 self._alarmHandler = None
97 except (ImportError, OSError):
98 self._alarmHandler = None
100 _moduleLogger.exception("Notification failure")
101 self._alarmHandler = None
102 if self._alarmHandler is None:
103 _moduleLogger.info("No notification support")
107 self._mainWindow.show()
108 self._idleDelay = QtCore.QTimer()
109 self._idleDelay.setSingleShot(True)
110 self._idleDelay.setInterval(0)
111 self._idleDelay.timeout.connect(lambda: self._mainWindow.start())
112 self._idleDelay.start()
114 def load_settings(self):
116 config = ConfigParser.SafeConfigParser()
117 config.read(constants._user_settings_)
119 _moduleLogger.info("No settings")
122 _moduleLogger.info("Settings were corrupt")
124 except ConfigParser.MissingSectionHeaderError:
125 _moduleLogger.info("Settings were corrupt")
128 _moduleLogger.exception("Unknown loading error")
135 config.get(constants.__pretty_app_name__, "bin_blob_%i" % i)
136 for i in xrange(len(self._mainWindow.get_default_credentials()))
138 isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen")
139 tabIndex = config.getint(constants.__pretty_app_name__, "tab")
140 except ConfigParser.NoOptionError, e:
142 "Settings file %s is missing option %s" % (
143 constants._user_settings_,
147 except ConfigParser.NoSectionError, e:
149 "Settings file %s is missing section %s" % (
150 constants._user_settings_,
155 _moduleLogger.exception("Unknown loading error")
157 if self._alarmHandler is not None:
159 self._alarmHandler.load_settings(config, "alarm")
160 self.notifyOnMissed = config.getboolean("2 - Account Info", "notifyOnMissed")
161 self.notifyOnVoicemail = config.getboolean("2 - Account Info", "notifyOnVoicemail")
162 self.notifyOnSms = config.getboolean("2 - Account Info", "notifyOnSms")
163 except ConfigParser.NoOptionError, e:
165 "Settings file %s is missing option %s" % (
166 constants._user_settings_,
170 except ConfigParser.NoSectionError, e:
172 "Settings file %s is missing section %s" % (
173 constants._user_settings_,
178 _moduleLogger.exception("Unknown loading error")
181 base64.b64decode(blob)
184 self._mainWindow.set_default_credentials(*creds)
185 self._fullscreenAction.setChecked(isFullscreen)
186 self._mainWindow.set_current_tab(tabIndex)
187 self._mainWindow.load_settings(config)
189 def save_settings(self):
190 _moduleLogger.info("Saving settings")
191 config = ConfigParser.SafeConfigParser()
193 config.add_section(constants.__pretty_app_name__)
194 config.set(constants.__pretty_app_name__, "tab", str(self._mainWindow.get_current_tab()))
195 config.set(constants.__pretty_app_name__, "fullscreen", str(self._fullscreenAction.isChecked()))
196 for i, value in enumerate(self._mainWindow.get_default_credentials()):
197 blob = base64.b64encode(value)
198 config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob)
200 if self._alarmHandler is not None:
201 config.add_section("alarm")
202 self._alarmHandler.save_settings(config, "alarm")
203 config.add_section("2 - Account Info")
204 config.set("2 - Account Info", "notifyOnMissed", repr(self.notifyOnMissed))
205 config.set("2 - Account Info", "notifyOnVoicemail", repr(self.notifyOnVoicemail))
206 config.set("2 - Account Info", "notifyOnSms", repr(self.notifyOnSms))
208 self._mainWindow.save_settings(config)
210 with open(constants._user_settings_, "wb") as configFile:
211 config.write(configFile)
213 def get_icon(self, name):
214 if self._dataPath is None:
215 for path in self._DATA_PATHS:
216 if os.path.exists(os.path.join(path, name)):
217 self._dataPath = path
219 if self._dataPath is not None:
220 icon = QtGui.QIcon(os.path.join(self._dataPath, name))
226 def fsContactsPath(self):
227 return os.path.join(constants._data_path_, "contacts")
230 def fullscreenAction(self):
231 return self._fullscreenAction
235 return self._logAction
238 def quitAction(self):
239 return self._quitAction
242 def alarmHandler(self):
243 return self._alarmHandler
246 def ledHandler(self):
247 return self._ledHandler
249 def _walk_children(self):
250 if self._mainWindow is not None:
251 return (self._mainWindow, )
255 def _close_windows(self):
256 if self._mainWindow is not None:
258 self._mainWindow.window.destroyed.disconnect(self._on_child_close)
259 self._mainWindow.close()
260 self._mainWindow = None
263 @QtCore.pyqtSlot(bool)
264 @misc_utils.log_exception(_moduleLogger)
265 def _on_app_quit(self, checked = False):
266 if self._mainWindow is not None:
268 self._mainWindow.destroy()
270 @QtCore.pyqtSlot(QtCore.QObject)
271 @misc_utils.log_exception(_moduleLogger)
272 def _on_child_close(self, obj = None):
273 if self._mainWindow is not None:
275 self._mainWindow = None
278 @QtCore.pyqtSlot(bool)
279 @misc_utils.log_exception(_moduleLogger)
280 def _on_toggle_fullscreen(self, checked = False):
281 for window in self._walk_children():
282 window.set_fullscreen(checked)
285 @QtCore.pyqtSlot(bool)
286 @misc_utils.log_exception(_moduleLogger)
287 def _on_log(self, checked = False):
288 with open(constants._user_logpath_, "r") as f:
289 logLines = f.xreadlines()
290 log = "".join(logLines)
291 self._clipboard.setText(log)
294 @QtCore.pyqtSlot(bool)
295 @misc_utils.log_exception(_moduleLogger)
296 def _on_quit(self, checked = False):
297 self._close_windows()
300 class DelayedWidget(object):
302 def __init__(self, app, settingsNames):
303 self._layout = QtGui.QVBoxLayout()
304 self._layout.setContentsMargins(0, 0, 0, 0)
305 self._widget = QtGui.QWidget()
306 self._widget.setContentsMargins(0, 0, 0, 0)
307 self._widget.setLayout(self._layout)
308 self._settings = dict((name, "") for name in settingsNames)
311 self._isEnabled = True
318 return self._child is not None
320 def set_child(self, child):
321 if self._child is not None:
322 self._layout.removeWidget(self._child.toplevel)
324 if self._child is not None:
325 self._layout.addWidget(self._child.toplevel)
327 self._child.set_settings(self._settings)
332 self._child.disable()
335 self._isEnabled = True
336 if self._child is not None:
340 self._isEnabled = False
341 if self._child is not None:
342 self._child.disable()
345 if self._child is not None:
348 def refresh(self, force=True):
349 if self._child is not None:
350 self._child.refresh(force)
352 def get_settings(self):
353 if self._child is not None:
354 return self._child.get_settings()
356 return self._settings
358 def set_settings(self, settings):
359 if self._child is not None:
360 self._child.set_settings(settings)
362 self._settings = settings
365 def _tab_factory(tab, app, session, errorLog):
367 return gv_views.__dict__[tab](app, session, errorLog)
370 class MainWindow(object):
384 assert len(_TAB_TITLES) == MAX_TABS
392 assert len(_TAB_ICONS) == MAX_TABS
395 functools.partial(_tab_factory, "Dialpad"),
396 functools.partial(_tab_factory, "History"),
397 functools.partial(_tab_factory, "Messages"),
398 functools.partial(_tab_factory, "Contacts"),
400 assert len(_TAB_CLASS) == MAX_TABS
402 # Hack to allow delay importing/loading of tabs
403 _TAB_SETTINGS_NAMES = [
407 ("selectedAddressbook", ),
409 assert len(_TAB_SETTINGS_NAMES) == MAX_TABS
411 def __init__(self, parent, app):
414 self._errorLog = qui_utils.QErrorLog()
415 self._errorDisplay = qui_utils.ErrorDisplay(self._errorLog)
417 self._session = session.Session(self._errorLog, constants._data_path_)
418 self._session.error.connect(self._on_session_error)
419 self._session.loggedIn.connect(self._on_login)
420 self._session.loggedOut.connect(self._on_logout)
421 self._session.draft.recipientsChanged.connect(self._on_recipients_changed)
422 self._defaultCredentials = "", ""
423 self._curentCredentials = "", ""
426 self._credentialsDialog = None
427 self._smsEntryDialog = None
428 self._accountDialog = None
429 self._aboutDialog = None
431 self._tabsContents = [
432 DelayedWidget(self._app, self._TAB_SETTINGS_NAMES[i])
433 for i in xrange(self.MAX_TABS)
435 for tab in self._tabsContents:
438 self._tabWidget = QtGui.QTabWidget()
439 if qui_utils.screen_orientation() == QtCore.Qt.Vertical:
440 self._tabWidget.setTabPosition(QtGui.QTabWidget.South)
442 self._tabWidget.setTabPosition(QtGui.QTabWidget.West)
443 defaultTabIconSize = self._tabWidget.iconSize()
444 defaultTabIconWidth, defaultTabIconHeight = defaultTabIconSize.width(), defaultTabIconSize.height()
445 for tabIndex, (tabTitle, tabIcon) in enumerate(
446 zip(self._TAB_TITLES, self._TAB_ICONS)
448 icon = self._app.get_icon(tabIcon)
449 if constants.IS_MAEMO and icon is not None:
453 self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, tabTitle)
455 iconSize = icon.availableSizes()[0]
456 defaultTabIconWidth = max(defaultTabIconWidth, iconSize.width())
457 defaultTabIconHeight = max(defaultTabIconHeight, iconSize.height())
458 self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, icon, tabTitle)
459 defaultTabIconWidth = max(defaultTabIconWidth, 32)
460 defaultTabIconHeight = max(defaultTabIconHeight, 32)
461 self._tabWidget.setIconSize(QtCore.QSize(defaultTabIconWidth, defaultTabIconHeight))
462 self._tabWidget.currentChanged.connect(self._on_tab_changed)
463 self._tabWidget.setContentsMargins(0, 0, 0, 0)
465 self._layout = QtGui.QVBoxLayout()
466 self._layout.setContentsMargins(0, 0, 0, 0)
467 self._layout.addWidget(self._errorDisplay.toplevel)
468 self._layout.addWidget(self._tabWidget)
470 centralWidget = QtGui.QWidget()
471 centralWidget.setLayout(self._layout)
472 centralWidget.setContentsMargins(0, 0, 0, 0)
474 self._window = QtGui.QMainWindow(parent)
475 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
476 qui_utils.set_autorient(self._window, True)
477 qui_utils.set_stackable(self._window, True)
478 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
479 self._window.setCentralWidget(centralWidget)
481 self._loginTabAction = QtGui.QAction(None)
482 self._loginTabAction.setText("Login")
483 self._loginTabAction.triggered.connect(self._on_login_requested)
485 self._importTabAction = QtGui.QAction(None)
486 self._importTabAction.setText("Import")
487 self._importTabAction.triggered.connect(self._on_import)
489 self._accountTabAction = QtGui.QAction(None)
490 self._accountTabAction.setText("Account")
491 self._accountTabAction.triggered.connect(self._on_account)
493 self._refreshTabAction = QtGui.QAction(None)
494 self._refreshTabAction.setText("Refresh")
495 self._refreshTabAction.setShortcut(QtGui.QKeySequence("CTRL+r"))
496 self._refreshTabAction.triggered.connect(self._on_refresh)
498 self._aboutAction = QtGui.QAction(None)
499 self._aboutAction.setText("About")
500 self._aboutAction.triggered.connect(self._on_about)
502 self._closeWindowAction = QtGui.QAction(None)
503 self._closeWindowAction.setText("Close")
504 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
505 self._closeWindowAction.triggered.connect(self._on_close_window)
507 if constants.IS_MAEMO:
508 fileMenu = self._window.menuBar().addMenu("&File")
509 fileMenu.addAction(self._loginTabAction)
510 fileMenu.addAction(self._refreshTabAction)
512 toolsMenu = self._window.menuBar().addMenu("&Tools")
513 toolsMenu.addAction(self._accountTabAction)
514 toolsMenu.addAction(self._importTabAction)
515 toolsMenu.addAction(self._aboutAction)
517 self._window.addAction(self._closeWindowAction)
518 self._window.addAction(self._app.quitAction)
519 self._window.addAction(self._app.fullscreenAction)
521 fileMenu = self._window.menuBar().addMenu("&File")
522 fileMenu.addAction(self._loginTabAction)
523 fileMenu.addAction(self._refreshTabAction)
524 fileMenu.addAction(self._closeWindowAction)
525 fileMenu.addAction(self._app.quitAction)
527 viewMenu = self._window.menuBar().addMenu("&View")
528 viewMenu.addAction(self._app.fullscreenAction)
530 toolsMenu = self._window.menuBar().addMenu("&Tools")
531 toolsMenu.addAction(self._accountTabAction)
532 toolsMenu.addAction(self._importTabAction)
533 toolsMenu.addAction(self._aboutAction)
535 self._window.addAction(self._app.logAction)
537 self._initialize_tab(self._tabWidget.currentIndex())
538 self.set_fullscreen(self._app.fullscreenAction.isChecked())
544 def set_default_credentials(self, username, password):
545 self._defaultCredentials = username, password
547 def get_default_credentials(self):
548 return self._defaultCredentials
550 def walk_children(self):
554 assert self._session.state == self._session.LOGGEDOUT_STATE, "Initialization messed up"
555 if self._defaultCredentials != ("", ""):
556 username, password = self._defaultCredentials[0], self._defaultCredentials[1]
557 self._curentCredentials = username, password
558 self._session.login(username, password)
560 self._prompt_for_login()
563 for child in self.walk_children():
564 child.window.destroyed.disconnect(self._on_child_close)
567 self._credentialsDialog,
568 self._smsEntryDialog,
577 if self._session.state != self._session.LOGGEDOUT_STATE:
578 self._session.logout()
580 def get_current_tab(self):
581 return self._currentTab
583 def set_current_tab(self, tabIndex):
584 self._tabWidget.setCurrentIndex(tabIndex)
586 def load_settings(self, config):
587 backendId = 2 # For backwards compatibility
588 for tabIndex, tabTitle in enumerate(self._TAB_TITLES):
589 sectionName = "%s - %s" % (backendId, tabTitle)
590 settings = self._tabsContents[tabIndex].get_settings()
591 for settingName in settings.iterkeys():
593 settingValue = config.get(sectionName, settingName)
594 except ConfigParser.NoOptionError, e:
596 "Settings file %s is missing section %s" % (
597 constants._user_settings_,
602 except ConfigParser.NoSectionError, e:
604 "Settings file %s is missing section %s" % (
605 constants._user_settings_,
611 _moduleLogger.exception("Unknown loading error")
613 settings[settingName] = settingValue
614 self._tabsContents[tabIndex].set_settings(settings)
616 def save_settings(self, config):
617 backendId = 2 # For backwards compatibility
618 for tabIndex, tabTitle in enumerate(self._TAB_TITLES):
619 sectionName = "%s - %s" % (backendId, tabTitle)
620 config.add_section(sectionName)
621 tabSettings = self._tabsContents[tabIndex].get_settings()
622 for settingName, settingValue in tabSettings.iteritems():
623 config.set(sectionName, settingName, settingValue)
627 for child in self.walk_children():
631 for child in self.walk_children():
635 def set_fullscreen(self, isFullscreen):
637 self._window.showFullScreen()
639 self._window.showNormal()
640 for child in self.walk_children():
641 child.set_fullscreen(isFullscreen)
643 def _initialize_tab(self, index):
644 assert index < self.MAX_TABS, "Invalid tab"
645 if not self._tabsContents[index].has_child():
646 tab = self._TAB_CLASS[index](self._app, self._session, self._errorLog)
647 self._tabsContents[index].set_child(tab)
648 self._tabsContents[index].refresh(force=False)
650 def _prompt_for_login(self):
651 if self._credentialsDialog is None:
653 self._credentialsDialog = dialogs.CredentialsDialog(self._app)
654 username, password = self._credentialsDialog.run(
655 self._defaultCredentials[0], self._defaultCredentials[1], self.window
657 self._curentCredentials = username, password
658 self._session.login(username, password)
660 def _show_account_dialog(self):
661 if self._accountDialog is None:
663 self._accountDialog = dialogs.AccountDialog(self._app)
664 if self._app.alarmHandler is None:
665 self._accountDialog.setIfNotificationsSupported(False)
666 if self._app.alarmHandler is not None:
667 self._accountDialog.notifications = self._app.alarmHandler.isEnabled
668 self._accountDialog.notificationTime = self._app.alarmHandler.recurrence
669 self._accountDialog.notifyOnMissed = self._app.notifyOnMissed
670 self._accountDialog.notifyOnVoicemail = self._app.notifyOnVoicemail
671 self._accountDialog.notifyOnSms = self._app.notifyOnSms
672 self._accountDialog.set_callbacks(
673 self._session.get_callback_numbers(), self._session.get_callback_number()
675 self._accountDialog.accountNumber = self._session.get_account_number()
676 response = self._accountDialog.run(self.window)
677 if response == QtGui.QDialog.Accepted:
678 if self._accountDialog.doClear:
679 self._session.logout_and_clear()
681 callbackNumber = self._accountDialog.selectedCallback
682 self._session.set_callback_number(callbackNumber)
683 if self._app.alarmHandler is not None:
684 self._app.alarmHandler.apply_settings(self._accountDialog.notifications, self._accountDialog.notificationTime)
685 self._app.notifyOnMissed = self._accountDialog.notifyOnMissed
686 self._app.notifyOnVoicemail = self._accountDialog.notifyOnVoicemail
687 self._app.notifyOnSms = self._accountDialog.notifyOnSms
688 elif response == QtGui.QDialog.Rejected:
689 _moduleLogger.info("Cancelled")
691 _moduleLogger.info("Unknown response")
693 @QtCore.pyqtSlot(str)
694 @misc_utils.log_exception(_moduleLogger)
695 def _on_session_error(self, message):
696 with qui_utils.notify_error(self._errorLog):
697 self._errorLog.push_error(message)
700 @misc_utils.log_exception(_moduleLogger)
702 with qui_utils.notify_error(self._errorLog):
703 changedAccounts = self._defaultCredentials != self._curentCredentials
704 noCallback = not self._session.get_callback_number()
705 if changedAccounts or noCallback:
706 self._show_account_dialog()
708 self._defaultCredentials = self._curentCredentials
710 for tab in self._tabsContents:
714 @misc_utils.log_exception(_moduleLogger)
715 def _on_logout(self):
716 with qui_utils.notify_error(self._errorLog):
717 for tab in self._tabsContents:
721 @misc_utils.log_exception(_moduleLogger)
722 def _on_recipients_changed(self):
723 with qui_utils.notify_error(self._errorLog):
724 if self._session.draft.get_num_contacts() == 0:
727 if self._smsEntryDialog is None:
729 self._smsEntryDialog = dialogs.SMSEntryWindow(self.window, self._app, self._session, self._errorLog)
732 @QtCore.pyqtSlot(bool)
733 @misc_utils.log_exception(_moduleLogger)
734 def _on_login_requested(self, checked = True):
735 with qui_utils.notify_error(self._errorLog):
736 self._prompt_for_login()
738 @QtCore.pyqtSlot(int)
739 @misc_utils.log_exception(_moduleLogger)
740 def _on_tab_changed(self, index):
741 with qui_utils.notify_error(self._errorLog):
742 self._currentTab = index
743 self._initialize_tab(index)
746 @QtCore.pyqtSlot(bool)
747 @misc_utils.log_exception(_moduleLogger)
748 def _on_refresh(self, checked = True):
749 with qui_utils.notify_error(self._errorLog):
750 self._tabsContents[self._currentTab].refresh(force=True)
753 @QtCore.pyqtSlot(bool)
754 @misc_utils.log_exception(_moduleLogger)
755 def _on_import(self, checked = True):
756 with qui_utils.notify_error(self._errorLog):
757 csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)")
761 shutil.copy2(csvName, self._app.fsContactsPath)
762 self._tabsContents[self.CONTACTS_TAB].update_addressbooks()
765 @QtCore.pyqtSlot(bool)
766 @misc_utils.log_exception(_moduleLogger)
767 def _on_account(self, checked = True):
768 with qui_utils.notify_error(self._errorLog):
769 self._show_account_dialog()
772 @QtCore.pyqtSlot(bool)
773 @misc_utils.log_exception(_moduleLogger)
774 def _on_about(self, checked = True):
775 with qui_utils.notify_error(self._errorLog):
776 if self._aboutDialog is None:
778 self._aboutDialog = dialogs.AboutDialog(self._app)
779 response = self._aboutDialog.run(self.window)
782 @QtCore.pyqtSlot(bool)
783 @misc_utils.log_exception(_moduleLogger)
784 def _on_close_window(self, checked = True):
789 app = QtGui.QApplication([])
790 handle = Dialcentral(app)
795 if __name__ == "__main__":
798 logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s'
799 logging.basicConfig(level=logging.DEBUG, format=logFormat)
801 os.makedirs(constants._data_path_)