Creating a hollow shell of a UI
[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 misc as misc_utils
18
19
20 _moduleLogger = logging.getLogger(__name__)
21
22
23 IS_MAEMO = True
24
25
26 class Dialcentral(object):
27
28         _DATA_PATHS = [
29                 os.path.dirname(__file__),
30                 os.path.join(os.path.dirname(__file__), "../data"),
31                 os.path.join(os.path.dirname(__file__), "../lib"),
32                 '/usr/share/%s' % constants.__app_name__,
33                 '/usr/lib/%s' % constants.__app_name__,
34         ]
35
36         def __init__(self, app):
37                 self._app = app
38                 self._recent = []
39                 self._hiddenCategories = set()
40                 self._hiddenUnits = {}
41                 self._clipboard = QtGui.QApplication.clipboard()
42
43                 self._mainWindow = None
44
45                 self._fullscreenAction = QtGui.QAction(None)
46                 self._fullscreenAction.setText("Fullscreen")
47                 self._fullscreenAction.setCheckable(True)
48                 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
49                 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
50
51                 self._logAction = QtGui.QAction(None)
52                 self._logAction.setText("Log")
53                 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
54                 self._logAction.triggered.connect(self._on_log)
55
56                 self._quitAction = QtGui.QAction(None)
57                 self._quitAction.setText("Quit")
58                 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
59                 self._quitAction.triggered.connect(self._on_quit)
60
61                 self._app.lastWindowClosed.connect(self._on_app_quit)
62                 self.load_settings()
63
64                 self._mainWindow = MainWindow(None, self)
65                 self._mainWindow.window.destroyed.connect(self._on_child_close)
66
67         def load_settings(self):
68                 try:
69                         with open(constants._user_settings_, "r") as settingsFile:
70                                 settings = simplejson.load(settingsFile)
71                 except IOError, e:
72                         _moduleLogger.info("No settings")
73                         settings = {}
74                 except ValueError:
75                         _moduleLogger.info("Settings were corrupt")
76                         settings = {}
77
78                 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
79
80         def save_settings(self):
81                 settings = {
82                         "isFullScreen": self._fullscreenAction.isChecked(),
83                 }
84                 with open(constants._user_settings_, "w") as settingsFile:
85                         simplejson.dump(settings, settingsFile)
86
87         @property
88         def fullscreenAction(self):
89                 return self._fullscreenAction
90
91         @property
92         def logAction(self):
93                 return self._logAction
94
95         @property
96         def quitAction(self):
97                 return self._quitAction
98
99         def _close_windows(self):
100                 if self._mainWindow is not None:
101                         self._mainWindow.window.destroyed.disconnect(self._on_child_close)
102                         self._mainWindow.close()
103                         self._mainWindow = None
104
105         @misc_utils.log_exception(_moduleLogger)
106         def _on_app_quit(self, checked = False):
107                 self.save_settings()
108
109         @misc_utils.log_exception(_moduleLogger)
110         def _on_child_close(self, obj = None):
111                 self._mainWindow = None
112
113         @misc_utils.log_exception(_moduleLogger)
114         def _on_toggle_fullscreen(self, checked = False):
115                 for window in self._walk_children():
116                         window.set_fullscreen(checked)
117
118         @misc_utils.log_exception(_moduleLogger)
119         def _on_log(self, checked = False):
120                 with open(constants._user_logpath_, "r") as f:
121                         logLines = f.xreadlines()
122                         log = "".join(logLines)
123                         self._clipboard.setText(log)
124
125         @misc_utils.log_exception(_moduleLogger)
126         def _on_quit(self, checked = False):
127                 self._close_windows()
128
129
130 class QErrorDisplay(object):
131
132         def __init__(self):
133                 self._messages = []
134
135                 errorIcon = maeqt.get_theme_icon(("dialog-error", "app_install_error", "gtk-dialog-error"))
136                 self._severityIcon = errorIcon.pixmap(32, 32)
137                 self._severityLabel = QtGui.QLabel()
138                 self._severityLabel.setPixmap(self._severityIcon)
139
140                 self._message = QtGui.QLabel()
141                 self._message.setText("Boo")
142
143                 closeIcon = maeqt.get_theme_icon(("window-close", "general_close", "gtk-close"))
144                 self._closeLabel = QtGui.QPushButton(closeIcon, "")
145                 self._closeLabel.clicked.connect(self._on_close)
146
147                 self._controlLayout = QtGui.QHBoxLayout()
148                 self._controlLayout.addWidget(self._severityLabel)
149                 self._controlLayout.addWidget(self._message)
150                 self._controlLayout.addWidget(self._closeLabel)
151
152                 self._topLevelLayout = QtGui.QHBoxLayout()
153                 self._topLevelLayout.addLayout(self._controlLayout)
154                 self._widget = QtGui.QWidget()
155                 self._widget.setLayout(self._topLevelLayout)
156                 self._hide_message()
157
158         @property
159         def toplevel(self):
160                 return self._widget
161
162         def push_message(self, message):
163                 self._messages.append(message)
164                 if 1 == len(self._messages):
165                         self._show_message(message)
166
167         def push_exception(self):
168                 userMessage = str(sys.exc_info()[1])
169                 _moduleLogger.exception(userMessage)
170                 self.push_message(userMessage)
171
172         def pop_message(self):
173                 del self._messages[0]
174                 if 0 == len(self._messages):
175                         self._hide_message()
176                 else:
177                         self._message.setText(self._messages[0])
178
179         def _on_close(self, *args):
180                 self.pop_message()
181
182         def _show_message(self, message):
183                 self._message.setText(message)
184                 self._widget.show()
185
186         def _hide_message(self):
187                 self._message.setText("")
188                 self._widget.hide()
189
190
191 class CredentialsDialog(object):
192
193         def __init__(self):
194                 self._usernameField = QtGui.QLineEdit()
195                 self._passwordField = QtGui.QLineEdit()
196                 self._passwordField.setEchoMode(QtGui.QLineEdit.PasswordEchoOnEdit)
197
198                 self._credLayout = QtGui.QGridLayout()
199                 self._credLayout.addWidget(QtGui.QLabel("Username"), 0, 0)
200                 self._credLayout.addWidget(self._usernameField, 0, 1)
201                 self._credLayout.addWidget(QtGui.QLabel("Password"), 1, 0)
202                 self._credLayout.addWidget(self._passwordField, 1, 1)
203
204                 self._loginButton = QtGui.QPushButton("&Login")
205                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
206                 self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole)
207
208                 self._layout = QtGui.QVBoxLayout()
209                 self._layout.addLayout(self._credLayout)
210                 self._layout.addLayout(self._buttonLayout)
211
212                 centralWidget = QtGui.QWidget()
213                 centralWidget.setLayout(self._layout)
214
215                 self._dialog = QtGui.QDialog()
216                 self._dialog.setWindowTitle("Login")
217                 self._dialog.setCentralWidget(centralWidget)
218                 maeqt.set_autorient(self._dialog, True)
219                 self._buttonLayout.accepted.connect(self._dialog.accept)
220                 self._buttonLayout.rejected.connect(self._dialog.reject)
221
222         def run(self, defaultUsername, defaultPassword, parent=None):
223                 self._dialog.setParent(parent)
224                 self._usernameField.setText(defaultUsername)
225                 self._passwordField.setText(defaultPassword)
226
227                 response = self._dialog.exec_()
228                 if response == QtGui.QDialog.Accepted:
229                         return str(self._usernameField.text()), str(self._passwordField.text())
230                 elif response == QtGui.QDialog.Rejected:
231                         raise RuntimeError("Login Cancelled")
232
233
234 class AccountDialog(object):
235
236         def __init__(self):
237                 self._accountNumberLabel = QtGui.QLabel("NUMBER NOT SET")
238                 self._clearButton = QtGui.QPushButton("Clear Account")
239                 self._clearButton.clicked.connect(self._on_clear)
240                 self._doClear = False
241
242                 self._credLayout = QtGui.QGridLayout()
243                 self._credLayout.addWidget(QtGui.QLabel("Account"), 0, 0)
244                 self._credLayout.addWidget(self._accountNumberLabel, 0, 1)
245                 self._credLayout.addWidget(QtGui.QLabel("Callback"), 1, 0)
246                 self._credLayout.addWidget(self._clearButton, 2, 1)
247
248                 self._loginButton = QtGui.QPushButton("&Login")
249                 self._buttonLayout = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Cancel)
250                 self._buttonLayout.addButton(self._loginButton, QtGui.QDialogButtonBox.AcceptRole)
251
252                 self._layout = QtGui.QVBoxLayout()
253                 self._layout.addLayout(self._credLayout)
254                 self._layout.addLayout(self._buttonLayout)
255
256                 centralWidget = QtGui.QWidget()
257                 centralWidget.setLayout(self._layout)
258
259                 self._dialog = QtGui.QDialog()
260                 self._dialog.setWindowTitle("Login")
261                 self._dialog.setCentralWidget(centralWidget)
262                 maeqt.set_autorient(self._dialog, True)
263                 self._buttonLayout.accepted.connect(self._dialog.accept)
264                 self._buttonLayout.rejected.connect(self._dialog.reject)
265
266         @property
267         def doClear(self):
268                 return self._doClear
269
270         accountNumber = property(
271                 lambda self: str(self._accountNumberLabel.text()),
272                 lambda self, num: self._accountNumberLabel.setText(num),
273         )
274
275         def run(self, defaultUsername, defaultPassword, parent=None):
276                 self._doClear = False
277                 self._dialog.setParent(parent)
278                 self._usernameField.setText(defaultUsername)
279                 self._passwordField.setText(defaultPassword)
280
281                 response = self._dialog.exec_()
282                 if response == QtGui.QDialog.Accepted:
283                         return str(self._usernameField.text()), str(self._passwordField.text())
284                 elif response == QtGui.QDialog.Rejected:
285                         raise RuntimeError("Login Cancelled")
286
287         def _on_clear(self, checked = False):
288                 self._doClear = True
289                 self._dialog.accept()
290
291
292 class MainWindow(object):
293
294         KEYPAD_TAB = 0
295         RECENT_TAB = 1
296         MESSAGES_TAB = 2
297         CONTACTS_TAB = 3
298         ACCOUNT_TAB = 4
299
300         def __init__(self, parent, app):
301                 self._fsContactsPath = os.path.join(constants._data_path_, "contacts")
302                 self._app = app
303
304                 self._errorDisplay = QErrorDisplay()
305
306                 self._tabs = QtGui.QTabWidget()
307                 if maeqt.screen_orientation() == QtCore.Qt.Vertical:
308                         self._tabs.setTabPosition(QtGui.QTabWidget.South)
309                 else:
310                         self._tabs.setTabPosition(QtGui.QTabWidget.West)
311
312                 self._layout = QtGui.QVBoxLayout()
313                 self._layout.addWidget(self._errorDisplay.toplevel)
314                 self._layout.addWidget(self._tabs)
315
316                 centralWidget = QtGui.QWidget()
317                 centralWidget.setLayout(self._layout)
318
319                 self._window = QtGui.QMainWindow(parent)
320                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
321                 maeqt.set_autorient(self._window, True)
322                 maeqt.set_stackable(self._window, True)
323                 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
324                 self._window.setCentralWidget(centralWidget)
325
326                 self._loginTabAction = QtGui.QAction(None)
327                 self._loginTabAction.setText("Login")
328                 self._loginTabAction.triggered.connect(self._on_login)
329
330                 self._importTabAction = QtGui.QAction(None)
331                 self._importTabAction.setText("Import")
332                 self._importTabAction.triggered.connect(self._on_import)
333
334                 self._refreshTabAction = QtGui.QAction(None)
335                 self._refreshTabAction.setText("Refresh")
336                 self._refreshTabAction.setShortcut(QtGui.QKeySequence("CTRL+r"))
337                 self._refreshTabAction.triggered.connect(self._on_refresh)
338
339                 self._closeWindowAction = QtGui.QAction(None)
340                 self._closeWindowAction.setText("Close")
341                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
342                 self._closeWindowAction.triggered.connect(self._on_close_window)
343
344                 if IS_MAEMO:
345                         fileMenu = self._window.menuBar().addMenu("&File")
346                         fileMenu.addAction(self._loginTabAction)
347                         fileMenu.addAction(self._refreshTabAction)
348
349                         toolsMenu = self._window.menuBar().addMenu("&Tools")
350                         toolsMenu.addAction(self._importTabAction)
351
352                         self._window.addAction(self._closeWindowAction)
353                         self._window.addAction(self._app.quitAction)
354                         self._window.addAction(self._app.fullscreenAction)
355                 else:
356                         fileMenu = self._window.menuBar().addMenu("&File")
357                         fileMenu.addAction(self._loginTabAction)
358                         fileMenu.addAction(self._refreshTabAction)
359                         fileMenu.addAction(self._closeWindowAction)
360                         fileMenu.addAction(self._app.quitAction)
361
362                         viewMenu = self._window.menuBar().addMenu("&View")
363                         viewMenu.addAction(self._app.fullscreenAction)
364
365                         toolsMenu = self._window.menuBar().addMenu("&Tools")
366                         toolsMenu.addAction(self._importTabAction)
367
368                 self._window.addAction(self._app.logAction)
369
370                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
371                 self._window.show()
372
373         @property
374         def window(self):
375                 return self._window
376
377         def walk_children(self):
378                 return ()
379
380         def show(self):
381                 self._window.show()
382                 for child in self.walk_children():
383                         child.show()
384
385         def hide(self):
386                 for child in self.walk_children():
387                         child.hide()
388                 self._window.hide()
389
390         def close(self):
391                 for child in self.walk_children():
392                         child.window.destroyed.disconnect(self._on_child_close)
393                         child.close()
394                 self._window.close()
395
396         def set_fullscreen(self, isFullscreen):
397                 if isFullscreen:
398                         self._window.showFullScreen()
399                 else:
400                         self._window.showNormal()
401                 for child in self.walk_children():
402                         child.set_fullscreen(isFullscreen)
403
404         def _populate_tab(self, index):
405                 pass
406
407         @misc_utils.log_exception(_moduleLogger)
408         def _on_login(self, checked = True):
409                 pass
410
411         @misc_utils.log_exception(_moduleLogger)
412         def _on_tab_changed(self, index):
413                 self._populate_tab(index)
414
415         @misc_utils.log_exception(_moduleLogger)
416         def _on_refresh(self, checked = True):
417                 index = self._tabs.currentIndex()
418                 self._populate_tab(index)
419
420         @misc_utils.log_exception(_moduleLogger)
421         def _on_import(self, checked = True):
422                 csvName = QtGui.QFileDialog.getOpenFileName(self._window, caption="Import", filter="CSV Files (*.csv)")
423                 if not csvName:
424                         return
425                 shutil.copy2(csvName, self._fsContactsPath)
426
427         @misc_utils.log_exception(_moduleLogger)
428         def _on_close_window(self, checked = True):
429                 self.close()
430
431
432 def run():
433         app = QtGui.QApplication([])
434         handle = Dialcentral(app)
435         return app.exec_()
436
437
438 if __name__ == "__main__":
439         logging.basicConfig(level = logging.DEBUG)
440         try:
441                 os.makedirs(constants._data_path_)
442         except OSError, e:
443                 if e.errno != 17:
444                         raise
445
446         val = run()
447         sys.exit(val)