X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=src%2Fdialcentral_qt.py;h=da2a7a9791465fd20d41c548a61129a52ea6e055;hb=3d7c7cdca856a8f21dcbdc8461275b26b6fd23ac;hp=11aeff542a49d7d89f23f7ca6c94af87d4a9bc36;hpb=2b305008d0ae100c2f3851f9017ad57ba9707011;p=gc-dialer diff --git a/src/dialcentral_qt.py b/src/dialcentral_qt.py index 11aeff5..da2a7a9 100755 --- a/src/dialcentral_qt.py +++ b/src/dialcentral_qt.py @@ -3,35 +3,55 @@ from __future__ import with_statement -import sys import os -import shutil -import simplejson +import base64 +import ConfigParser +import functools import logging from PyQt4 import QtGui from PyQt4 import QtCore import constants -import maeqt from util import qtpie +from util import qui_utils from util import misc as misc_utils +import session + _moduleLogger = logging.getLogger(__name__) -IS_MAEMO = True +class LedWrapper(object): + + def __init__(self): + self._ledHandler = None + self._init = False + + def off(self): + self._lazy_init() + if self._ledHandler is not None: + self._ledHandler.off() + + def _lazy_init(self): + if self._init: + return + self._init = True + try: + import led_handler + self._ledHandler = led_handler.LedHandler() + except Exception, e: + _moduleLogger.exception('Unable to initialize LED Handling: "%s"' % str(e)) + self._ledHandler = None + class Dialcentral(object): _DATA_PATHS = [ - os.path.dirname(__file__), + os.path.join(os.path.dirname(__file__), "../share"), os.path.join(os.path.dirname(__file__), "../data"), - os.path.join(os.path.dirname(__file__), "../lib"), - '/usr/share/%s' % constants.__app_name__, - '/usr/lib/%s' % constants.__app_name__, ] def __init__(self, app): @@ -40,6 +60,11 @@ class Dialcentral(object): self._hiddenCategories = set() self._hiddenUnits = {} self._clipboard = QtGui.QApplication.clipboard() + self._dataPath = None + self._ledHandler = LedWrapper() + self.notifyOnMissed = False + self.notifyOnVoicemail = False + self.notifyOnSms = False self._mainWindow = None @@ -49,6 +74,12 @@ class Dialcentral(object): self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter")) self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen) + self._orientationAction = QtGui.QAction(None) + self._orientationAction.setText("Orientation") + self._orientationAction.setCheckable(True) + self._orientationAction.setShortcut(QtGui.QKeySequence("CTRL+o")) + self._orientationAction.toggled.connect(self._on_toggle_orientation) + self._logAction = QtGui.QAction(None) self._logAction.setText("Log") self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l")) @@ -60,36 +91,160 @@ class Dialcentral(object): self._quitAction.triggered.connect(self._on_quit) self._app.lastWindowClosed.connect(self._on_app_quit) - self.load_settings() - self._mainWindow = MainWindow(None, self) self._mainWindow.window.destroyed.connect(self._on_child_close) + try: + import alarm_handler + if alarm_handler.AlarmHandler is not alarm_handler._NoneAlarmHandler: + self._alarmHandler = alarm_handler.AlarmHandler() + else: + self._alarmHandler = None + except (ImportError, OSError): + self._alarmHandler = None + except Exception: + _moduleLogger.exception("Notification failure") + self._alarmHandler = None + if self._alarmHandler is None: + _moduleLogger.info("No notification support") + + self.load_settings() + + self._mainWindow.show() + self._idleDelay = QtCore.QTimer() + self._idleDelay.setSingleShot(True) + self._idleDelay.setInterval(0) + self._idleDelay.timeout.connect(lambda: self._mainWindow.start()) + self._idleDelay.start() + def load_settings(self): try: - with open(constants._user_settings_, "r") as settingsFile: - settings = simplejson.load(settingsFile) + config = ConfigParser.SafeConfigParser() + config.read(constants._user_settings_) except IOError, e: _moduleLogger.info("No settings") - settings = {} + return except ValueError: _moduleLogger.info("Settings were corrupt") - settings = {} + return + except ConfigParser.MissingSectionHeaderError: + _moduleLogger.info("Settings were corrupt") + return + except Exception: + _moduleLogger.exception("Unknown loading error") - self._fullscreenAction.setChecked(settings.get("isFullScreen", False)) + blobs = "", "" + isFullscreen = False + isPortrait = qui_utils.screen_orientation() == QtCore.Qt.Vertical + tabIndex = 0 + try: + blobs = [ + config.get(constants.__pretty_app_name__, "bin_blob_%i" % i) + for i in xrange(len(self._mainWindow.get_default_credentials())) + ] + isFullscreen = config.getboolean(constants.__pretty_app_name__, "fullscreen") + tabIndex = config.getint(constants.__pretty_app_name__, "tab") + isPortrait = config.getboolean(constants.__pretty_app_name__, "portrait") + except ConfigParser.NoOptionError, e: + _moduleLogger.info( + "Settings file %s is missing option %s" % ( + constants._user_settings_, + e.option, + ), + ) + except ConfigParser.NoSectionError, e: + _moduleLogger.info( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + ) + except Exception: + _moduleLogger.exception("Unknown loading error") + + if self._alarmHandler is not None: + try: + self._alarmHandler.load_settings(config, "alarm") + self.notifyOnMissed = config.getboolean("2 - Account Info", "notifyOnMissed") + self.notifyOnVoicemail = config.getboolean("2 - Account Info", "notifyOnVoicemail") + self.notifyOnSms = config.getboolean("2 - Account Info", "notifyOnSms") + except ConfigParser.NoOptionError, e: + _moduleLogger.info( + "Settings file %s is missing option %s" % ( + constants._user_settings_, + e.option, + ), + ) + except ConfigParser.NoSectionError, e: + _moduleLogger.info( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + ) + except Exception: + _moduleLogger.exception("Unknown loading error") + + creds = ( + base64.b64decode(blob) + for blob in blobs + ) + self._mainWindow.set_default_credentials(*creds) + self._fullscreenAction.setChecked(isFullscreen) + self._orientationAction.setChecked(isPortrait) + self._mainWindow.set_current_tab(tabIndex) + self._mainWindow.load_settings(config) def save_settings(self): - settings = { - "isFullScreen": self._fullscreenAction.isChecked(), - } - with open(constants._user_settings_, "w") as settingsFile: - simplejson.dump(settings, settingsFile) + _moduleLogger.info("Saving settings") + config = ConfigParser.SafeConfigParser() + + config.add_section(constants.__pretty_app_name__) + config.set(constants.__pretty_app_name__, "tab", str(self._mainWindow.get_current_tab())) + config.set(constants.__pretty_app_name__, "fullscreen", str(self._fullscreenAction.isChecked())) + config.set(constants.__pretty_app_name__, "portrait", str(self._orientationAction.isChecked())) + for i, value in enumerate(self._mainWindow.get_default_credentials()): + blob = base64.b64encode(value) + config.set(constants.__pretty_app_name__, "bin_blob_%i" % i, blob) + + if self._alarmHandler is not None: + config.add_section("alarm") + self._alarmHandler.save_settings(config, "alarm") + config.add_section("2 - Account Info") + config.set("2 - Account Info", "notifyOnMissed", repr(self.notifyOnMissed)) + config.set("2 - Account Info", "notifyOnVoicemail", repr(self.notifyOnVoicemail)) + config.set("2 - Account Info", "notifyOnSms", repr(self.notifyOnSms)) + + self._mainWindow.save_settings(config) + + with open(constants._user_settings_, "wb") as configFile: + config.write(configFile) + + def get_icon(self, name): + if self._dataPath is None: + for path in self._DATA_PATHS: + if os.path.exists(os.path.join(path, name)): + self._dataPath = path + break + if self._dataPath is not None: + icon = QtGui.QIcon(os.path.join(self._dataPath, name)) + return icon + else: + return None + + @property + def fsContactsPath(self): + return os.path.join(constants._data_path_, "contacts") @property def fullscreenAction(self): return self._fullscreenAction @property + def orientationAction(self): + return self._orientationAction + + @property def logAction(self): return self._logAction @@ -97,25 +252,58 @@ class Dialcentral(object): def quitAction(self): return self._quitAction + @property + def alarmHandler(self): + return self._alarmHandler + + @property + def ledHandler(self): + return self._ledHandler + + def _walk_children(self): + if self._mainWindow is not None: + return (self._mainWindow, ) + else: + return () + def _close_windows(self): if self._mainWindow is not None: + self.save_settings() self._mainWindow.window.destroyed.disconnect(self._on_child_close) self._mainWindow.close() self._mainWindow = None + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) @misc_utils.log_exception(_moduleLogger) def _on_app_quit(self, checked = False): - self.save_settings() + if self._mainWindow is not None: + self.save_settings() + self._mainWindow.destroy() + @QtCore.pyqtSlot(QtCore.QObject) @misc_utils.log_exception(_moduleLogger) def _on_child_close(self, obj = None): - self._mainWindow = None + if self._mainWindow is not None: + self.save_settings() + self._mainWindow = None + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) @misc_utils.log_exception(_moduleLogger) def _on_toggle_fullscreen(self, checked = False): for window in self._walk_children(): window.set_fullscreen(checked) + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) + @misc_utils.log_exception(_moduleLogger) + def _on_toggle_orientation(self, checked = False): + for window in self._walk_children(): + window.set_orientation(checked) + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) @misc_utils.log_exception(_moduleLogger) def _on_log(self, checked = False): with open(constants._user_logpath_, "r") as f: @@ -123,249 +311,22 @@ class Dialcentral(object): log = "".join(logLines) self._clipboard.setText(log) + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) @misc_utils.log_exception(_moduleLogger) def _on_quit(self, checked = False): self._close_windows() -class QErrorDisplay(object): - - def __init__(self): - self._messages = [] - - errorIcon = maeqt.get_theme_icon(("dialog-error", "app_install_error", "gtk-dialog-error")) - self._severityIcon = errorIcon.pixmap(32, 32) - self._severityLabel = QtGui.QLabel() - self._severityLabel.setPixmap(self._severityIcon) - - self._message = QtGui.QLabel() - self._message.setText("Boo") - - closeIcon = maeqt.get_theme_icon(("window-close", "general_close", "gtk-close")) - self._closeLabel = QtGui.QPushButton(closeIcon, "") - self._closeLabel.clicked.connect(self._on_close) - - self._controlLayout = QtGui.QHBoxLayout() - self._controlLayout.addWidget(self._severityLabel) - self._controlLayout.addWidget(self._message) - self._controlLayout.addWidget(self._closeLabel) - - self._topLevelLayout = QtGui.QHBoxLayout() - self._topLevelLayout.addLayout(self._controlLayout) - self._widget = QtGui.QWidget() - self._widget.setLayout(self._topLevelLayout) - self._hide_message() - - @property - def toplevel(self): - return self._widget - - def push_message(self, message): - self._messages.append(message) - if 1 == len(self._messages): - self._show_message(message) - - def push_exception(self): - userMessage = str(sys.exc_info()[1]) - _moduleLogger.exception(userMessage) - self.push_message(userMessage) - - def pop_message(self): - del self._messages[0] - if 0 == len(self._messages): - self._hide_message() - else: - self._message.setText(self._messages[0]) - - def _on_close(self, *args): - self.pop_message() - - def _show_message(self, message): - self._message.setText(message) - self._widget.show() - - def _hide_message(self): - self._message.setText("") - self._widget.hide() - - -class CredentialsDialog(object): - - def __init__(self): - self._usernameField = QtGui.QLineEdit() - self._passwordField = QtGui.QLineEdit() - self._passwordField.setEchoMode(QtGui.QLineEdit.PasswordEchoOnEdit) - - self._credLayout = QtGui.QGridLayout() - self._credLayout.addWidget(QtGui.QLabel("Username"), 0, 0) - self._credLayout.addWidget(self._usernameField, 0, 1) - self._credLayout.addWidget(QtGui.QLabel("Password"), 1, 0) - self._credLayout.addWidget(self._passwordField, 1, 1) - - self._loginButton = QtGui.QPushButton("&Login") - self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel) - self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole) - - self._layout = QtGui.QVBoxLayout() - self._layout.addLayout(self._credLayout) - self._layout.addLayout(self._buttonLayout) - - centralWidget = QtGui.QWidget() - centralWidget.setLayout(self._layout) - - self._dialog = QtGui.QDialog() - self._dialog.setWindowTitle("Login") - self._dialog.setCentralWidget(centralWidget) - maeqt.set_autorient(self._dialog, True) - self._buttonLayout.accepted.connect(self._dialog.accept) - self._buttonLayout.rejected.connect(self._dialog.reject) - - def run(self, defaultUsername, defaultPassword, parent=None): - self._dialog.setParent(parent) - self._usernameField.setText(defaultUsername) - self._passwordField.setText(defaultPassword) - - response = self._dialog.exec_() - if response == QtGui.QDialog.Accepted: - return str(self._usernameField.text()), str(self._passwordField.text()) - elif response == QtGui.QDialog.Rejected: - raise RuntimeError("Login Cancelled") - - -class AccountDialog(object): - - def __init__(self): - self._accountNumberLabel = QtGui.QLabel("NUMBER NOT SET") - self._clearButton = QtGui.QPushButton("Clear Account") - self._clearButton.clicked.connect(self._on_clear) - self._doClear = False - - self._credLayout = QtGui.QGridLayout() - self._credLayout.addWidget(QtGui.QLabel("Account"), 0, 0) - self._credLayout.addWidget(self._accountNumberLabel, 0, 1) - self._credLayout.addWidget(QtGui.QLabel("Callback"), 1, 0) - self._credLayout.addWidget(self._clearButton, 2, 1) - - self._loginButton = QtGui.QPushButton("&Login") - self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel) - self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole) - - self._layout = QtGui.QVBoxLayout() - self._layout.addLayout(self._credLayout) - self._layout.addLayout(self._buttonLayout) - - centralWidget = QtGui.QWidget() - centralWidget.setLayout(self._layout) - - self._dialog = QtGui.QDialog() - self._dialog.setWindowTitle("Login") - self._dialog.setCentralWidget(centralWidget) - maeqt.set_autorient(self._dialog, True) - self._buttonLayout.accepted.connect(self._dialog.accept) - self._buttonLayout.rejected.connect(self._dialog.reject) - - @property - def doClear(self): - return self._doClear - - accountNumber = property( - lambda self: str(self._accountNumberLabel.text()), - lambda self, num: self._accountNumberLabel.setText(num), - ) - - def run(self, defaultUsername, defaultPassword, parent=None): - self._doClear = False - self._dialog.setParent(parent) - self._usernameField.setText(defaultUsername) - self._passwordField.setText(defaultPassword) - - response = self._dialog.exec_() - if response == QtGui.QDialog.Accepted: - return str(self._usernameField.text()), str(self._passwordField.text()) - elif response == QtGui.QDialog.Rejected: - raise RuntimeError("Login Cancelled") - - def _on_clear(self, checked = False): - self._doClear = True - self._dialog.accept() - - -class SMSEntryWindow(object): - - def __init__(self, parent, app): - self._contacts = [] - self._app = app - - self._history = QtGui.QListView() - self._smsEntry = QtGui.QTextEdit() - self._smsEntry.textChanged.connect(self._on_letter_count_changed) - - self._entryLayout = QtGui.QVBoxLayout() - self._entryLayout.addWidget(self._history) - self._entryLayout.addWidget(self._smsEntry) - self._entryWidget = QtGui.QWidget() - self._entryWidget.setLayout(self._entryLayout) - self._scrollEntry = QtGui.QScrollArea() - self._scrollEntry.setWidget(self._entryWidget) - self._scrollEntry.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignBottom) - self._scrollEntry.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - self._scrollEntry.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - self._characterCountLabel = QtGui.QLabel("Letters: %s" % 0) - self._numberSelector = None - self._smsButton = QtGui.QPushButton("SMS") - self._dialButton = QtGui.QPushButton("Dial") - - self._buttonLayout = QtGui.QHBoxLayout() - self._buttonLayout.addWidget(self._characterCountLabel) - self._buttonLayout.addWidget(self._smsButton) - self._buttonLayout.addWidget(self._dialButton) - - self._layout = QtGui.QVBoxLayout() - self._layout.addLayout(self._entryLayout) - self._layout.addLayout(self._buttonLayout) - - centralWidget = QtGui.QWidget() - centralWidget.setLayout(self._layout) - - self._window = QtGui.QMainWindow(parent) - self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, False) - maeqt.set_autorient(self._window, True) - maeqt.set_stackable(self._window, True) - self._window.setWindowTitle("Contact") - self._window.setCentralWidget(centralWidget) - - def _update_letter_count(self): - count = self._smsEntry.toPlainText().size() - self._characterCountLabel.setText("Letters: %s" % count) - - def _update_button_state(self): - if len(self._contacts) == 0: - self._dialButton.setEnabled(False) - self._smsButton.setEnabled(False) - elif len(self._contacts) == 1: - count = self._smsEntry.toPlainText().size() - if count == 0: - self._dialButton.setEnabled(True) - self._smsButton.setEnabled(False) - else: - self._dialButton.setEnabled(False) - self._smsButton.setEnabled(True) - else: - self._dialButton.setEnabled(False) - self._smsButton.setEnabled(True) - - def _on_letter_count_changed(self): - self._update_letter_count() - self._update_button_state() - - class DelayedWidget(object): - def __init__(self, app): + def __init__(self, app, settingsNames): self._layout = QtGui.QVBoxLayout() + self._layout.setContentsMargins(0, 0, 0, 0) self._widget = QtGui.QWidget() + self._widget.setContentsMargins(0, 0, 0, 0) self._widget.setLayout(self._layout) + self._settings = dict((name, "") for name in settingsNames) self._child = None self._isEnabled = True @@ -384,6 +345,8 @@ class DelayedWidget(object): if self._child is not None: self._layout.addWidget(self._child.toplevel) + self._child.set_settings(self._settings) + if self._isEnabled: self._child.enable() else: @@ -399,296 +362,30 @@ class DelayedWidget(object): if self._child is not None: self._child.disable() - def refresh(self): + def clear(self): if self._child is not None: - self._child.refresh() - - -class Dialpad(object): - - def __init__(self, app): - self._app = app + self._child.clear() - self._plus = self._generate_key_button("+", "") - self._entry = QtGui.QLineEdit() - - backAction = QtGui.QAction(None) - backAction.setText("Back") - backAction.triggered.connect(self._on_backspace) - backPieItem = qtpie.QActionPieItem(backAction) - clearAction = QtGui.QAction(None) - clearAction.setText("Clear") - clearAction.triggered.connect(self._on_clear_text) - clearPieItem = qtpie.QActionPieItem(clearAction) - self._back = qtpie.QPieButton(backPieItem) - self._back.set_center(backPieItem) - self._back.insertItem(qtpie.PieFiling.NULL_CENTER) - self._back.insertItem(clearPieItem) - self._back.insertItem(qtpie.PieFiling.NULL_CENTER) - self._back.insertItem(qtpie.PieFiling.NULL_CENTER) - - self._entryLayout = QtGui.QHBoxLayout() - self._entryLayout.addWidget(self._plus, 0, QtCore.Qt.AlignCenter) - self._entryLayout.addWidget(self._entry, 10) - self._entryLayout.addWidget(self._back, 0, QtCore.Qt.AlignCenter) - - self._smsButton = QtGui.QPushButton("SMS") - self._smsButton.clicked.connect(self._on_sms_clicked) - self._callButton = QtGui.QPushButton("Call") - self._callButton.clicked.connect(self._on_call_clicked) - - self._padLayout = QtGui.QGridLayout() - rows = [0, 0, 0, 1, 1, 1, 2, 2, 2] - columns = [0, 1, 2] * 3 - keys = [ - ("1", ""), - ("2", "ABC"), - ("3", "DEF"), - ("4", "GHI"), - ("5", "JKL"), - ("6", "MNO"), - ("7", "PQRS"), - ("8", "TUV"), - ("9", "WXYZ"), - ] - for (num, letters), (row, column) in zip(keys, zip(rows, columns)): - self._padLayout.addWidget( - self._generate_key_button(num, letters), row, column, QtCore.Qt.AlignCenter - ) - self._padLayout.addWidget(self._smsButton, 3, 0) - self._padLayout.addWidget( - self._generate_key_button("0", ""), 3, 1, QtCore.Qt.AlignCenter - ) - self._padLayout.addWidget(self._callButton, 3, 2) - - self._layout = QtGui.QVBoxLayout() - self._layout.addLayout(self._entryLayout) - self._layout.addLayout(self._padLayout) - self._widget = QtGui.QWidget() - self._widget.setLayout(self._layout) - - @property - def toplevel(self): - return self._widget - - def enable(self): - self._smsButton.setEnabled(True) - self._callButton.setEnabled(True) + def refresh(self, force=True): + if self._child is not None: + self._child.refresh(force) - def disable(self): - self._smsButton.setEnabled(False) - self._callButton.setEnabled(False) - - def refresh(self): - pass - - def _generate_key_button(self, center, letters): - centerPieItem = self._generate_button_slice(center) - button = qtpie.QPieButton(centerPieItem) - button.set_center(centerPieItem) - - if len(letters) == 0: - for i in xrange(8): - pieItem = qtpie.PieFiling.NULL_CENTER - button.insertItem(pieItem) - elif len(letters) in [3, 4]: - for i in xrange(6 - len(letters)): - pieItem = qtpie.PieFiling.NULL_CENTER - button.insertItem(pieItem) - - for letter in letters: - pieItem = self._generate_button_slice(letter) - button.insertItem(pieItem) - - for i in xrange(2): - pieItem = qtpie.PieFiling.NULL_CENTER - button.insertItem(pieItem) + def get_settings(self): + if self._child is not None: + return self._child.get_settings() else: - raise NotImplementedError("Cannot handle %r" % letters) - return button - - def _generate_button_slice(self, letter): - action = QtGui.QAction(None) - action.setText(letter) - action.triggered.connect(lambda: self._on_keypress(letter)) - pieItem = qtpie.QActionPieItem(action) - return pieItem + return self._settings - @misc_utils.log_exception(_moduleLogger) - def _on_keypress(self, key): - self._entry.insert(key) - - @misc_utils.log_exception(_moduleLogger) - def _on_backspace(self, toggled = False): - self._entry.backspace() - - @misc_utils.log_exception(_moduleLogger) - def _on_clear_text(self, toggled = False): - self._entry.clear() - - @misc_utils.log_exception(_moduleLogger) - def _on_sms_clicked(self, checked = False): - number = str(self._entry.text()) - self._entry.clear() - print "sms", number - - @misc_utils.log_exception(_moduleLogger) - def _on_call_clicked(self, checked = False): - number = str(self._entry.text()) - self._entry.clear() - print "call", number - - -class History(object): - - DATE_IDX = 0 - ACTION_IDX = 1 - NUMBER_IDX = 2 - FROM_IDX = 3 - MAX_IDX = 4 - - HISTORY_ITEM_TYPES = ["All", "Received", "Missed", "Placed"] - HISTORY_COLUMNS = ["When", "What", "Number", "From"] - assert len(HISTORY_COLUMNS) == MAX_IDX - - def __init__(self, app): - self._selectedFilter = self.HISTORY_ITEM_TYPES[0] - self._app = app - - self._listSelection = QtGui.QComboBox() - self._listSelection.addItems(self.HISTORY_ITEM_TYPES) - self._listSelection.setCurrentIndex(self.HISTORY_ITEM_TYPES.index(self._selectedFilter)) - self._listSelection.currentIndexChanged.connect(self._on_filter_changed) - - self._itemStore = QtGui.QStandardItemModel() - self._itemStore.setHorizontalHeaderLabels(self.HISTORY_COLUMNS) - - self._itemView = QtGui.QTreeView() - self._itemView.setModel(self._itemStore) - self._itemView.setUniformRowHeights(True) - self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) - self._itemView.setHeaderHidden(True) - self._itemView.activated.connect(self._on_row_activated) - - self._layout = QtGui.QVBoxLayout() - self._layout.addWidget(self._listSelection) - self._layout.addWidget(self._itemView) - self._widget = QtGui.QWidget() - self._widget.setLayout(self._layout) - - @property - def toplevel(self): - return self._widget - - def enable(self): - pass - - def disable(self): - pass - - def refresh(self): - pass - - @misc_utils.log_exception(_moduleLogger) - def _on_filter_changed(self, newItem): - self._selectedFilter = str(newItem) - - @misc_utils.log_exception(_moduleLogger) - def _on_row_activated(self, index): - rowIndex = index.row() - - -class Messages(object): - - def __init__(self, app): - self._app = app - - self._itemStore = QtGui.QStandardItemModel() - self._itemStore.setHorizontalHeaderLabels(["Messages"]) - - self._itemView = QtGui.QTreeView() - self._itemView.setModel(self._itemStore) - self._itemView.setUniformRowHeights(True) - self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) - self._itemView.setHeaderHidden(True) - self._itemView.activated.connect(self._on_row_activated) - - self._layout = QtGui.QVBoxLayout() - self._layout.addWidget(self._itemView) - self._widget = QtGui.QWidget() - self._widget.setLayout(self._layout) - - @property - def toplevel(self): - return self._widget - - def enable(self): - pass - - def disable(self): - pass - - def refresh(self): - pass - - @misc_utils.log_exception(_moduleLogger) - def _on_row_activated(self, index): - rowIndex = index.row() - - -class Contacts(object): - - def __init__(self, app): - self._selectedFilter = "" - self._app = app - - self._listSelection = QtGui.QComboBox() - self._listSelection.addItems([]) - #self._listSelection.setCurrentIndex(self.HISTORY_ITEM_TYPES.index(self._selectedFilter)) - self._listSelection.currentIndexChanged.connect(self._on_filter_changed) - - self._itemStore = QtGui.QStandardItemModel() - self._itemStore.setHorizontalHeaderLabels(["Contacts"]) - - self._itemView = QtGui.QTreeView() - self._itemView.setModel(self._itemStore) - self._itemView.setUniformRowHeights(True) - self._itemView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self._itemView.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows) - self._itemView.setSelectionMode(QtGui.QAbstractItemView.SingleSelection) - self._itemView.setHeaderHidden(True) - self._itemView.activated.connect(self._on_row_activated) - - self._layout = QtGui.QVBoxLayout() - self._layout.addWidget(self._listSelection) - self._layout.addWidget(self._itemView) - self._widget = QtGui.QWidget() - self._widget.setLayout(self._layout) - - @property - def toplevel(self): - return self._widget - - def enable(self): - pass - - def disable(self): - pass - - def refresh(self): - pass + def set_settings(self, settings): + if self._child is not None: + self._child.set_settings(settings) + else: + self._settings = settings - @misc_utils.log_exception(_moduleLogger) - def _on_filter_changed(self, newItem): - self._selectedFilter = str(newItem) - @misc_utils.log_exception(_moduleLogger) - def _on_row_activated(self, index): - rowIndex = index.row() +def _tab_factory(tab, app, session, errorLog): + import gv_views + return gv_views.__dict__[tab](app, session, errorLog) class MainWindow(object): @@ -707,77 +404,140 @@ class MainWindow(object): ] assert len(_TAB_TITLES) == MAX_TABS + _TAB_ICONS = [ + "dialpad.png", + "history.png", + "messages.png", + "contacts.png", + ] + assert len(_TAB_ICONS) == MAX_TABS + _TAB_CLASS = [ - Dialpad, - History, - Messages, - Contacts, + functools.partial(_tab_factory, "Dialpad"), + functools.partial(_tab_factory, "History"), + functools.partial(_tab_factory, "Messages"), + functools.partial(_tab_factory, "Contacts"), ] assert len(_TAB_CLASS) == MAX_TABS + # Hack to allow delay importing/loading of tabs + _TAB_SETTINGS_NAMES = [ + (), + ("filter", ), + ("status", "type"), + ("selectedAddressbook", ), + ] + assert len(_TAB_SETTINGS_NAMES) == MAX_TABS + def __init__(self, parent, app): - self._fsContactsPath = os.path.join(constants._data_path_, "contacts") self._app = app - self._errorDisplay = QErrorDisplay() + self._errorLog = qui_utils.QErrorLog() + self._errorDisplay = qui_utils.ErrorDisplay(self._errorLog) + + self._session = session.Session(self._errorLog, constants._data_path_) + self._session.error.connect(self._on_session_error) + self._session.loggedIn.connect(self._on_login) + self._session.loggedOut.connect(self._on_logout) + self._session.draft.recipientsChanged.connect(self._on_recipients_changed) + self._defaultCredentials = "", "" + self._curentCredentials = "", "" + self._currentTab = 0 + + self._credentialsDialog = None + self._smsEntryDialog = None + self._accountDialog = None + self._aboutDialog = None self._tabsContents = [ - DelayedWidget(self._app) + DelayedWidget(self._app, self._TAB_SETTINGS_NAMES[i]) for i in xrange(self.MAX_TABS) ] + for tab in self._tabsContents: + tab.disable() self._tabWidget = QtGui.QTabWidget() - if maeqt.screen_orientation() == QtCore.Qt.Vertical: + if qui_utils.screen_orientation() == QtCore.Qt.Vertical: self._tabWidget.setTabPosition(QtGui.QTabWidget.South) else: self._tabWidget.setTabPosition(QtGui.QTabWidget.West) - for tabIndex, tabTitle in enumerate(self._TAB_TITLES): - self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, tabTitle) + defaultTabIconSize = self._tabWidget.iconSize() + defaultTabIconWidth, defaultTabIconHeight = defaultTabIconSize.width(), defaultTabIconSize.height() + for tabIndex, (tabTitle, tabIcon) in enumerate( + zip(self._TAB_TITLES, self._TAB_ICONS) + ): + icon = self._app.get_icon(tabIcon) + if constants.IS_MAEMO and icon is not None: + tabTitle = "" + + if icon is None: + self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, tabTitle) + else: + iconSize = icon.availableSizes()[0] + defaultTabIconWidth = max(defaultTabIconWidth, iconSize.width()) + defaultTabIconHeight = max(defaultTabIconHeight, iconSize.height()) + self._tabWidget.addTab(self._tabsContents[tabIndex].toplevel, icon, tabTitle) + defaultTabIconWidth = max(defaultTabIconWidth, 32) + defaultTabIconHeight = max(defaultTabIconHeight, 32) + self._tabWidget.setIconSize(QtCore.QSize(defaultTabIconWidth, defaultTabIconHeight)) self._tabWidget.currentChanged.connect(self._on_tab_changed) + self._tabWidget.setContentsMargins(0, 0, 0, 0) self._layout = QtGui.QVBoxLayout() + self._layout.setContentsMargins(0, 0, 0, 0) self._layout.addWidget(self._errorDisplay.toplevel) self._layout.addWidget(self._tabWidget) centralWidget = QtGui.QWidget() centralWidget.setLayout(self._layout) + centralWidget.setContentsMargins(0, 0, 0, 0) self._window = QtGui.QMainWindow(parent) self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) - maeqt.set_autorient(self._window, True) - maeqt.set_stackable(self._window, True) + qui_utils.set_stackable(self._window, True) self._window.setWindowTitle("%s" % constants.__pretty_app_name__) self._window.setCentralWidget(centralWidget) self._loginTabAction = QtGui.QAction(None) self._loginTabAction.setText("Login") - self._loginTabAction.triggered.connect(self._on_login) + self._loginTabAction.triggered.connect(self._on_login_requested) self._importTabAction = QtGui.QAction(None) self._importTabAction.setText("Import") self._importTabAction.triggered.connect(self._on_import) + self._accountTabAction = QtGui.QAction(None) + self._accountTabAction.setText("Account") + self._accountTabAction.triggered.connect(self._on_account) + self._refreshTabAction = QtGui.QAction(None) self._refreshTabAction.setText("Refresh") self._refreshTabAction.setShortcut(QtGui.QKeySequence("CTRL+r")) self._refreshTabAction.triggered.connect(self._on_refresh) + self._aboutAction = QtGui.QAction(None) + self._aboutAction.setText("About") + self._aboutAction.triggered.connect(self._on_about) + self._closeWindowAction = QtGui.QAction(None) self._closeWindowAction.setText("Close") self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w")) self._closeWindowAction.triggered.connect(self._on_close_window) - if IS_MAEMO: + if constants.IS_MAEMO: fileMenu = self._window.menuBar().addMenu("&File") fileMenu.addAction(self._loginTabAction) fileMenu.addAction(self._refreshTabAction) toolsMenu = self._window.menuBar().addMenu("&Tools") + toolsMenu.addAction(self._accountTabAction) toolsMenu.addAction(self._importTabAction) + toolsMenu.addAction(self._aboutAction) self._window.addAction(self._closeWindowAction) self._window.addAction(self._app.quitAction) self._window.addAction(self._app.fullscreenAction) + self._window.addAction(self._app.orientationAction) else: fileMenu = self._window.menuBar().addMenu("&File") fileMenu.addAction(self._loginTabAction) @@ -789,21 +549,103 @@ class MainWindow(object): viewMenu.addAction(self._app.fullscreenAction) toolsMenu = self._window.menuBar().addMenu("&Tools") + toolsMenu.addAction(self._accountTabAction) toolsMenu.addAction(self._importTabAction) + toolsMenu.addAction(self._aboutAction) + + self._window.addAction(self._app.orientationAction) self._window.addAction(self._app.logAction) self._initialize_tab(self._tabWidget.currentIndex()) self.set_fullscreen(self._app.fullscreenAction.isChecked()) - self._window.show() + self.set_orientation(self._app.orientationAction.isChecked()) @property def window(self): return self._window + def set_default_credentials(self, username, password): + self._defaultCredentials = username, password + + def get_default_credentials(self): + return self._defaultCredentials + def walk_children(self): return () + def start(self): + assert self._session.state == self._session.LOGGEDOUT_STATE, "Initialization messed up" + if self._defaultCredentials != ("", ""): + username, password = self._defaultCredentials[0], self._defaultCredentials[1] + self._curentCredentials = username, password + self._session.login(username, password) + else: + self._prompt_for_login() + + def close(self): + for child in self.walk_children(): + child.window.destroyed.disconnect(self._on_child_close) + child.close() + for diag in ( + self._credentialsDialog, + self._smsEntryDialog, + self._accountDialog, + self._aboutDialog, + ): + if diag is not None: + diag.close() + self._window.close() + + def destroy(self): + if self._session.state != self._session.LOGGEDOUT_STATE: + self._session.logout() + + def get_current_tab(self): + return self._currentTab + + def set_current_tab(self, tabIndex): + self._tabWidget.setCurrentIndex(tabIndex) + + def load_settings(self, config): + backendId = 2 # For backwards compatibility + for tabIndex, tabTitle in enumerate(self._TAB_TITLES): + sectionName = "%s - %s" % (backendId, tabTitle) + settings = self._tabsContents[tabIndex].get_settings() + for settingName in settings.iterkeys(): + try: + settingValue = config.get(sectionName, settingName) + except ConfigParser.NoOptionError, e: + _moduleLogger.info( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + ) + return + except ConfigParser.NoSectionError, e: + _moduleLogger.info( + "Settings file %s is missing section %s" % ( + constants._user_settings_, + e.section, + ), + ) + return + except Exception: + _moduleLogger.exception("Unknown loading error") + return + settings[settingName] = settingValue + self._tabsContents[tabIndex].set_settings(settings) + + def save_settings(self, config): + backendId = 2 # For backwards compatibility + for tabIndex, tabTitle in enumerate(self._TAB_TITLES): + sectionName = "%s - %s" % (backendId, tabTitle) + config.add_section(sectionName) + tabSettings = self._tabsContents[tabIndex].get_settings() + for settingName, settingValue in tabSettings.iteritems(): + config.set(sectionName, settingName, settingValue) + def show(self): self._window.show() for child in self.walk_children(): @@ -814,12 +656,6 @@ class MainWindow(object): child.hide() self._window.hide() - def close(self): - for child in self.walk_children(): - child.window.destroyed.disconnect(self._on_child_close) - child.close() - self._window.close() - def set_fullscreen(self, isFullscreen): if isFullscreen: self._window.showFullScreen() @@ -828,31 +664,156 @@ class MainWindow(object): for child in self.walk_children(): child.set_fullscreen(isFullscreen) + def set_orientation(self, isPortrait): + if isPortrait: + self._tabWidget.setTabPosition(QtGui.QTabWidget.South) + qui_utils.set_window_orientation(self.window, QtCore.Qt.Vertical) + else: + self._tabWidget.setTabPosition(QtGui.QTabWidget.West) + qui_utils.set_window_orientation(self.window, QtCore.Qt.Horizontal) + for child in self.walk_children(): + child.set_orientation(isPortrait) + def _initialize_tab(self, index): - assert index < self.MAX_TABS + assert index < self.MAX_TABS, "Invalid tab" if not self._tabsContents[index].has_child(): - self._tabsContents[index].set_child(self._TAB_CLASS[index](self._app)) + tab = self._TAB_CLASS[index](self._app, self._session, self._errorLog) + self._tabsContents[index].set_child(tab) + self._tabsContents[index].refresh(force=False) + + def _prompt_for_login(self): + if self._credentialsDialog is None: + import dialogs + self._credentialsDialog = dialogs.CredentialsDialog(self._app) + username, password = self._credentialsDialog.run( + self._defaultCredentials[0], self._defaultCredentials[1], self.window + ) + self._curentCredentials = username, password + self._session.login(username, password) + + def _show_account_dialog(self): + if self._accountDialog is None: + import dialogs + self._accountDialog = dialogs.AccountDialog(self._app) + if self._app.alarmHandler is None: + self._accountDialog.setIfNotificationsSupported(False) + if self._app.alarmHandler is not None: + self._accountDialog.notifications = self._app.alarmHandler.isEnabled + self._accountDialog.notificationTime = self._app.alarmHandler.recurrence + self._accountDialog.notifyOnMissed = self._app.notifyOnMissed + self._accountDialog.notifyOnVoicemail = self._app.notifyOnVoicemail + self._accountDialog.notifyOnSms = self._app.notifyOnSms + self._accountDialog.set_callbacks( + self._session.get_callback_numbers(), self._session.get_callback_number() + ) + self._accountDialog.accountNumber = self._session.get_account_number() + response = self._accountDialog.run(self.window) + if response == QtGui.QDialog.Accepted: + if self._accountDialog.doClear: + self._session.logout_and_clear() + else: + callbackNumber = self._accountDialog.selectedCallback + self._session.set_callback_number(callbackNumber) + if self._app.alarmHandler is not None: + self._app.alarmHandler.apply_settings(self._accountDialog.notifications, self._accountDialog.notificationTime) + self._app.notifyOnMissed = self._accountDialog.notifyOnMissed + self._app.notifyOnVoicemail = self._accountDialog.notifyOnVoicemail + self._app.notifyOnSms = self._accountDialog.notifyOnSms + elif response == QtGui.QDialog.Rejected: + _moduleLogger.info("Cancelled") + else: + _moduleLogger.info("Unknown response") + @QtCore.pyqtSlot(str) @misc_utils.log_exception(_moduleLogger) - def _on_login(self, checked = True): - pass + def _on_session_error(self, message): + with qui_utils.notify_error(self._errorLog): + self._errorLog.push_error(message) + @QtCore.pyqtSlot() + @misc_utils.log_exception(_moduleLogger) + def _on_login(self): + with qui_utils.notify_error(self._errorLog): + changedAccounts = self._defaultCredentials != self._curentCredentials + noCallback = not self._session.get_callback_number() + if changedAccounts or noCallback: + self._show_account_dialog() + + self._defaultCredentials = self._curentCredentials + + for tab in self._tabsContents: + tab.enable() + + @QtCore.pyqtSlot() + @misc_utils.log_exception(_moduleLogger) + def _on_logout(self): + with qui_utils.notify_error(self._errorLog): + for tab in self._tabsContents: + tab.disable() + + @QtCore.pyqtSlot() + @misc_utils.log_exception(_moduleLogger) + def _on_recipients_changed(self): + with qui_utils.notify_error(self._errorLog): + if self._session.draft.get_num_contacts() == 0: + return + + if self._smsEntryDialog is None: + import dialogs + self._smsEntryDialog = dialogs.SMSEntryWindow(self.window, self._app, self._session, self._errorLog) + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) + @misc_utils.log_exception(_moduleLogger) + def _on_login_requested(self, checked = True): + with qui_utils.notify_error(self._errorLog): + self._prompt_for_login() + + @QtCore.pyqtSlot(int) @misc_utils.log_exception(_moduleLogger) def _on_tab_changed(self, index): - self._initialize_tab(index) + with qui_utils.notify_error(self._errorLog): + self._currentTab = index + self._initialize_tab(index) + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) @misc_utils.log_exception(_moduleLogger) def _on_refresh(self, checked = True): - index = self._tabWidget.currentIndex() - self._tabsContents[index].refresh() + with qui_utils.notify_error(self._errorLog): + self._tabsContents[self._currentTab].refresh(force=True) + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) @misc_utils.log_exception(_moduleLogger) def _on_import(self, checked = True): - csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)") - if not csvName: - return - shutil.copy2(csvName, self._fsContactsPath) + with qui_utils.notify_error(self._errorLog): + csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)") + if not csvName: + return + import shutil + shutil.copy2(csvName, self._app.fsContactsPath) + self._tabsContents[self.CONTACTS_TAB].update_addressbooks() + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) + @misc_utils.log_exception(_moduleLogger) + def _on_account(self, checked = True): + with qui_utils.notify_error(self._errorLog): + self._show_account_dialog() + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) + @misc_utils.log_exception(_moduleLogger) + def _on_about(self, checked = True): + with qui_utils.notify_error(self._errorLog): + if self._aboutDialog is None: + import dialogs + self._aboutDialog = dialogs.AboutDialog(self._app) + response = self._aboutDialog.run(self.window) + + @QtCore.pyqtSlot() + @QtCore.pyqtSlot(bool) @misc_utils.log_exception(_moduleLogger) def _on_close_window(self, checked = True): self.close() @@ -866,7 +827,10 @@ def run(): if __name__ == "__main__": - logging.basicConfig(level = logging.DEBUG) + import sys + + logFormat = '(%(relativeCreated)5d) %(levelname)-5s %(threadName)s.%(name)s.%(funcName)s: %(message)s' + logging.basicConfig(level=logging.DEBUG, format=logFormat) try: os.makedirs(constants._data_path_) except OSError, e: