Cleaning up dead code
[ejpi] / src / ejpi_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 simplejson
9 import string
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 from libraries import qtpie
20 from libraries import qtpieboard
21 import plugin_utils
22 import history
23 import qhistory
24
25
26 _moduleLogger = logging.getLogger(__name__)
27
28
29 IS_MAEMO = True
30
31
32 class Calculator(object):
33
34         def __init__(self, app):
35                 self._app = app
36                 self._recent = []
37                 self._hiddenCategories = set()
38                 self._hiddenUnits = {}
39                 self._clipboard = QtGui.QApplication.clipboard()
40                 self._mainWindow = None
41
42                 self._fullscreenAction = QtGui.QAction(None)
43                 self._fullscreenAction.setText("Fullscreen")
44                 self._fullscreenAction.setCheckable(True)
45                 self._fullscreenAction.setShortcut(QtGui.QKeySequence("CTRL+Enter"))
46                 self._fullscreenAction.toggled.connect(self._on_toggle_fullscreen)
47
48                 self._logAction = QtGui.QAction(None)
49                 self._logAction.setText("Log")
50                 self._logAction.setShortcut(QtGui.QKeySequence("CTRL+l"))
51                 self._logAction.triggered.connect(self._on_log)
52
53                 self._quitAction = QtGui.QAction(None)
54                 self._quitAction.setText("Quit")
55                 self._quitAction.setShortcut(QtGui.QKeySequence("CTRL+q"))
56                 self._quitAction.triggered.connect(self._on_quit)
57
58                 self._app.lastWindowClosed.connect(self._on_app_quit)
59                 self.load_settings()
60
61                 self._mainWindow = MainWindow(None, self)
62                 self._mainWindow.window.destroyed.connect(self._on_child_close)
63
64         def load_settings(self):
65                 try:
66                         with open(constants._user_settings_, "r") as settingsFile:
67                                 settings = simplejson.load(settingsFile)
68                 except IOError, e:
69                         _moduleLogger.info("No settings")
70                         settings = {}
71                 except ValueError:
72                         _moduleLogger.info("Settings were corrupt")
73                         settings = {}
74
75                 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
76
77         def save_settings(self):
78                 settings = {
79                         "isFullScreen": self._fullscreenAction.isChecked(),
80                 }
81                 with open(constants._user_settings_, "w") as settingsFile:
82                         simplejson.dump(settings, settingsFile)
83
84         @property
85         def fullscreenAction(self):
86                 return self._fullscreenAction
87
88         @property
89         def logAction(self):
90                 return self._logAction
91
92         @property
93         def quitAction(self):
94                 return self._quitAction
95
96         def _close_windows(self):
97                 if self._mainWindow is not None:
98                         self._mainWindow.window.destroyed.disconnect(self._on_child_close)
99                         self._mainWindow.close()
100                         self._mainWindow = None
101
102         @misc_utils.log_exception(_moduleLogger)
103         def _on_app_quit(self, checked = False):
104                 self.save_settings()
105
106         @misc_utils.log_exception(_moduleLogger)
107         def _on_child_close(self, obj = None):
108                 self._mainWindow = None
109
110         @misc_utils.log_exception(_moduleLogger)
111         def _on_toggle_fullscreen(self, checked = False):
112                 for window in self._walk_children():
113                         window.set_fullscreen(checked)
114
115         @misc_utils.log_exception(_moduleLogger)
116         def _on_log(self, checked = False):
117                 with open(constants._user_logpath_, "r") as f:
118                         logLines = f.xreadlines()
119                         log = "".join(logLines)
120                         self._clipboard.setText(log)
121
122         @misc_utils.log_exception(_moduleLogger)
123         def _on_quit(self, checked = False):
124                 self._close_windows()
125
126
127 class QErrorDisplay(object):
128
129         def __init__(self):
130                 self._messages = []
131
132                 icon = QtGui.QIcon.fromTheme("gtk-dialog-error")
133                 self._severityIcon = icon.pixmap(32, 32)
134                 self._severityLabel = QtGui.QLabel()
135                 self._severityLabel.setPixmap(self._severityIcon)
136
137                 self._message = QtGui.QLabel()
138                 self._message.setText("Boo")
139
140                 icon = QtGui.QIcon.fromTheme("gtk-close")
141                 self._closeLabel = QtGui.QPushButton(icon, "")
142                 self._closeLabel.clicked.connect(self._on_close)
143
144                 self._controlLayout = QtGui.QHBoxLayout()
145                 self._controlLayout.addWidget(self._severityLabel)
146                 self._controlLayout.addWidget(self._message)
147                 self._controlLayout.addWidget(self._closeLabel)
148
149                 self._topLevelLayout = QtGui.QHBoxLayout()
150                 self._topLevelLayout.addLayout(self._controlLayout)
151                 self._widget = QtGui.QWidget()
152                 self._widget.setLayout(self._topLevelLayout)
153                 self._hide_message()
154
155         @property
156         def toplevel(self):
157                 return self._widget
158
159         def push_message(self, message):
160                 self._messages.append(message)
161                 if 1 == len(self._messages):
162                         self._show_message(message)
163
164         def push_exception(self):
165                 userMessage = str(sys.exc_info()[1])
166                 _moduleLogger.exception(userMessage)
167                 self.push_message(userMessage)
168
169         def pop_message(self):
170                 del self._messages[0]
171                 if 0 == len(self._messages):
172                         self._hide_message()
173                 else:
174                         self._message.setText(self._messages[0])
175
176         def _on_close(self, *args):
177                 self.pop_message()
178
179         def _show_message(self, message):
180                 self._message.setText(message)
181                 self._widget.show()
182
183         def _hide_message(self):
184                 self._message.setText("")
185                 self._widget.hide()
186
187
188 class QValueEntry(object):
189
190         def __init__(self):
191                 self._widget = QtGui.QLineEdit("")
192                 maeqt.mark_numbers_preferred(self._widget)
193
194         @property
195         def toplevel(self):
196                 return self._widget
197
198         @property
199         def entry(self):
200                 return self._widget
201
202         def get_value(self):
203                 value = str(self._widget.text()).strip()
204                 if any(
205                         0 < value.find(whitespace)
206                         for whitespace in string.whitespace
207                 ):
208                         self.clear()
209                         raise ValueError('Invalid input "%s"' % value)
210                 return value
211
212         def set_value(self, value):
213                 value = value.strip()
214                 if any(
215                         0 < value.find(whitespace)
216                         for whitespace in string.whitespace
217                 ):
218                         raise ValueError('Invalid input "%s"' % value)
219                 self._widget.setText(value)
220
221         def append(self, value):
222                 value = value.strip()
223                 if any(
224                         0 < value.find(whitespace)
225                         for whitespace in string.whitespace
226                 ):
227                         raise ValueError('Invalid input "%s"' % value)
228                 self.set_value(self.get_value() + value)
229
230         def pop(self):
231                 value = self.get_value()[0:-1]
232                 self.set_value(value)
233
234         def clear(self):
235                 self.set_value("")
236
237         value = property(get_value, set_value, clear)
238
239
240 class MainWindow(object):
241
242         _plugin_search_paths = [
243                 os.path.join(os.path.dirname(__file__), "plugins/"),
244         ]
245
246         _user_history = "%s/history.stack" % constants._data_path_
247
248         def __init__(self, parent, app):
249                 self._app = app
250
251                 self._errorDisplay = QErrorDisplay()
252                 self._historyView = qhistory.QCalcHistory(self._errorDisplay)
253                 self._userEntry = QValueEntry()
254                 self._userEntry.entry.returnPressed.connect(self._on_push)
255                 self._userEntryLayout = QtGui.QHBoxLayout()
256                 self._userEntryLayout.addWidget(self._userEntry.toplevel)
257
258                 self._controlLayout = QtGui.QVBoxLayout()
259                 self._controlLayout.addWidget(self._errorDisplay.toplevel)
260                 self._controlLayout.addWidget(self._historyView.toplevel)
261                 self._controlLayout.addLayout(self._userEntryLayout)
262
263                 self._keyboardTabs = QtGui.QTabWidget()
264
265                 if maeqt.screen_orientation() == QtCore.Qt.Vertical:
266                         defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
267                         self._keyboardTabs.setTabPosition(QtGui.QTabWidget.East)
268                 else:
269                         defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
270                         self._keyboardTabs.setTabPosition(QtGui.QTabWidget.North)
271                 self._layout = QtGui.QBoxLayout(defaultLayoutOrientation)
272                 self._layout.addLayout(self._controlLayout)
273                 self._layout.addWidget(self._keyboardTabs)
274
275                 centralWidget = QtGui.QWidget()
276                 centralWidget.setLayout(self._layout)
277
278                 self._window = QtGui.QMainWindow(parent)
279                 self._window.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
280                 #maeqt.set_autorient(self._window, True)
281                 maeqt.set_stackable(self._window, True)
282                 self._window.setWindowTitle("%s" % constants.__pretty_app_name__)
283                 self._window.setCentralWidget(centralWidget)
284                 self._window.destroyed.connect(self._on_close_window)
285
286                 self._copyItemAction = QtGui.QAction(None)
287                 self._copyItemAction.setText("Copy")
288                 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
289                 self._copyItemAction.triggered.connect(self._on_copy)
290
291                 self._pasteItemAction = QtGui.QAction(None)
292                 self._pasteItemAction.setText("Paste")
293                 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
294                 self._pasteItemAction.triggered.connect(self._on_paste)
295
296                 self._closeWindowAction = QtGui.QAction(None)
297                 self._closeWindowAction.setText("Close")
298                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
299                 self._closeWindowAction.triggered.connect(self._on_close_window)
300
301                 if IS_MAEMO:
302                         #fileMenu = self._window.menuBar().addMenu("&File")
303
304                         #viewMenu = self._window.menuBar().addMenu("&View")
305
306                         self._window.addAction(self._copyItemAction)
307                         self._window.addAction(self._pasteItemAction)
308                         self._window.addAction(self._closeWindowAction)
309                         self._window.addAction(self._app.quitAction)
310                         self._window.addAction(self._app.fullscreenAction)
311                 else:
312                         fileMenu = self._window.menuBar().addMenu("&Units")
313                         fileMenu.addAction(self._closeWindowAction)
314                         fileMenu.addAction(self._app.quitAction)
315
316                         viewMenu = self._window.menuBar().addMenu("&View")
317                         viewMenu.addAction(self._app.fullscreenAction)
318
319                 self._window.addAction(self._app.logAction)
320
321                 self._constantPlugins = plugin_utils.ConstantPluginManager()
322                 self._constantPlugins.add_path(*self._plugin_search_paths)
323                 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
324                         try:
325                                 pluginId = self._constantPlugins.lookup_plugin(pluginName)
326                                 self._constantPlugins.enable_plugin(pluginId)
327                         except:
328                                 _moduleLogger.info("Failed to load plugin %s" % pluginName)
329
330                 self._operatorPlugins = plugin_utils.OperatorPluginManager()
331                 self._operatorPlugins.add_path(*self._plugin_search_paths)
332                 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
333                         try:
334                                 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
335                                 self._operatorPlugins.enable_plugin(pluginId)
336                         except:
337                                 _moduleLogger.info("Failed to load plugin %s" % pluginName)
338
339                 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
340                 self._keyboardPlugins.add_path(*self._plugin_search_paths)
341                 self._activeKeyboards = []
342
343                 self._history = history.RpnCalcHistory(
344                         self._historyView,
345                         self._userEntry, self._errorDisplay,
346                         self._constantPlugins.constants, self._operatorPlugins.operators
347                 )
348                 self._load_history()
349
350                 # Basic keyboard stuff
351                 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
352                 self._handler.register_command_handler("push", self._on_push)
353                 self._handler.register_command_handler("unpush", self._on_unpush)
354                 self._handler.register_command_handler("backspace", self._on_entry_backspace)
355                 self._handler.register_command_handler("clear", self._on_entry_clear)
356
357                 # Main keyboard
358                 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
359                 self._keyboardPlugins.enable_plugin(entryKeyboardId)
360                 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
361                 entryKeyboard = entryPlugin.setup(self._history, self._handler)
362                 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
363
364                 # Plugins
365                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
366                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
367                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
368                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
369
370                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
371
372                 self._window.show()
373                 self._set_plugin_kb(0)
374
375         @property
376         def window(self):
377                 return self._window
378
379         def walk_children(self):
380                 return ()
381
382         def show(self):
383                 self._window.show()
384                 for child in self.walk_children():
385                         child.show()
386
387         def hide(self):
388                 for child in self.walk_children():
389                         child.hide()
390                 self._window.hide()
391
392         def close(self):
393                 for child in self.walk_children():
394                         child.window.destroyed.disconnect(self._on_child_close)
395                         child.close()
396                 self._window.close()
397
398         def set_fullscreen(self, isFullscreen):
399                 if isFullscreen:
400                         self._window.showFullScreen()
401                 else:
402                         self._window.showNormal()
403                 for child in self.walk_children():
404                         child.set_fullscreen(isFullscreen)
405
406         def enable_plugin(self, pluginId):
407                 self._keyboardPlugins.enable_plugin(pluginId)
408                 pluginData = self._keyboardPlugins.plugin_info(pluginId)
409                 pluginName = pluginData[0]
410                 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
411                 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
412                 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
413                         absIconPath = os.path.join(iconPath, relIcon)
414                         if os.path.exists(absIconPath):
415                                 icon = QtGui.QIcon(absIconPath)
416                                 break
417                 else:
418                         icon = None
419                 pluginKeyboard = plugin.setup(self._history, self._handler)
420
421                 self._activeKeyboards.append({
422                         "pluginName": pluginName,
423                         "plugin": plugin,
424                         "pluginKeyboard": pluginKeyboard,
425                 })
426                 if icon is None:
427                         self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
428                 else:
429                         self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
430
431         def _set_plugin_kb(self, pluginIndex):
432                 plugin = self._activeKeyboards[pluginIndex]
433
434                 # @todo Switch the keyboard tab
435                 pluginKeyboard = plugin["pluginKeyboard"]
436
437         def _load_history(self):
438                 serialized = []
439                 try:
440                         with open(self._user_history, "rU") as f:
441                                 serialized = (
442                                         (part.strip() for part in line.split(" "))
443                                         for line in f.readlines()
444                                 )
445                 except IOError, e:
446                         if e.errno != 2:
447                                 raise
448                 self._history.deserialize_stack(serialized)
449
450         def _save_history(self):
451                 serialized = self._history.serialize_stack()
452                 with open(self._user_history, "w") as f:
453                         for lineData in serialized:
454                                 line = " ".join(data for data in lineData)
455                                 f.write("%s\n" % line)
456
457         @misc_utils.log_exception(_moduleLogger)
458         def _on_copy(self, *args):
459                 eqNode = self._historyView.peek()
460                 resultNode = eqNode.simplify()
461                 self._app._clipboard.setText(str(resultNode))
462
463         @misc_utils.log_exception(_moduleLogger)
464         def _on_paste(self, *args):
465                 result = str(self._app._clipboard.text())
466                 self._userEntry.append(result)
467
468         @misc_utils.log_exception(_moduleLogger)
469         def _on_entry_direct(self, keys, modifiers):
470                 if "shift" in modifiers:
471                         keys = keys.upper()
472                 self._userEntry.append(keys)
473
474         @misc_utils.log_exception(_moduleLogger)
475         def _on_push(self, *args):
476                 self._history.push_entry()
477
478         @misc_utils.log_exception(_moduleLogger)
479         def _on_unpush(self, *args):
480                 self._historyView.unpush()
481
482         @misc_utils.log_exception(_moduleLogger)
483         def _on_entry_backspace(self, *args):
484                 self._userEntry.pop()
485
486         @misc_utils.log_exception(_moduleLogger)
487         def _on_entry_clear(self, *args):
488                 self._userEntry.clear()
489
490         @misc_utils.log_exception(_moduleLogger)
491         def _on_clear_all(self, *args):
492                 self._history.clear()
493
494         @misc_utils.log_exception(_moduleLogger)
495         def _on_close_window(self, checked = True):
496                 self._save_history()
497
498
499 def run():
500         app = QtGui.QApplication([])
501         handle = Calculator(app)
502         qtpie.init_pies()
503         return app.exec_()
504
505
506 if __name__ == "__main__":
507         logging.basicConfig(level = logging.DEBUG)
508         try:
509                 os.makedirs(constants._data_path_)
510         except OSError, e:
511                 if e.errno != 17:
512                         raise
513
514         val = run()
515         sys.exit(val)