Porting to qwrappers
[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
14 import constants
15 from util import misc as misc_utils
16
17 from util import qui_utils
18 from util import qwrappers
19 from util import qtpie
20 from util import qtpieboard
21 import plugin_utils
22 import history
23 import qhistory
24
25
26 _moduleLogger = logging.getLogger(__name__)
27
28
29 class Calculator(qwrappers.ApplicationWrapper):
30
31         def __init__(self, app):
32                 self._recent = []
33                 self._hiddenCategories = set()
34                 self._hiddenUnits = {}
35                 qwrappers.ApplicationWrapper.__init__(self, app, constants)
36
37         def load_settings(self):
38                 try:
39                         with open(constants._user_settings_, "r") as settingsFile:
40                                 settings = simplejson.load(settingsFile)
41                 except IOError, e:
42                         _moduleLogger.info("No settings")
43                         settings = {}
44                 except ValueError:
45                         _moduleLogger.info("Settings were corrupt")
46                         settings = {}
47
48                 self._fullscreenAction.setChecked(settings.get("isFullScreen", False))
49
50         def save_settings(self):
51                 settings = {
52                         "isFullScreen": self._fullscreenAction.isChecked(),
53                 }
54                 with open(constants._user_settings_, "w") as settingsFile:
55                         simplejson.dump(settings, settingsFile)
56
57         @property
58         def dataPath(self):
59                 return self._dataPath
60
61         def _new_main_window(self):
62                 return MainWindow(None, self)
63
64         @misc_utils.log_exception(_moduleLogger)
65         def _on_about(self, checked = True):
66                 raise NotImplementedError("Booh")
67
68
69 class QValueEntry(object):
70
71         def __init__(self):
72                 self._widget = QtGui.QLineEdit("")
73                 qui_utils.mark_numbers_preferred(self._widget)
74
75         @property
76         def toplevel(self):
77                 return self._widget
78
79         @property
80         def entry(self):
81                 return self._widget
82
83         def get_value(self):
84                 value = str(self._widget.text()).strip()
85                 if any(
86                         0 < value.find(whitespace)
87                         for whitespace in string.whitespace
88                 ):
89                         self.clear()
90                         raise ValueError('Invalid input "%s"' % value)
91                 return value
92
93         def set_value(self, value):
94                 value = value.strip()
95                 if any(
96                         0 < value.find(whitespace)
97                         for whitespace in string.whitespace
98                 ):
99                         raise ValueError('Invalid input "%s"' % value)
100                 self._widget.setText(value)
101
102         def append(self, value):
103                 value = value.strip()
104                 if any(
105                         0 < value.find(whitespace)
106                         for whitespace in string.whitespace
107                 ):
108                         raise ValueError('Invalid input "%s"' % value)
109                 self.set_value(self.get_value() + value)
110
111         def pop(self):
112                 value = self.get_value()[0:-1]
113                 self.set_value(value)
114
115         def clear(self):
116                 self.set_value("")
117
118         value = property(get_value, set_value, clear)
119
120
121 class MainWindow(qwrappers.WindowWrapper):
122
123         _plugin_search_paths = [
124                 os.path.join(os.path.dirname(__file__), "plugins/"),
125         ]
126
127         _user_history = "%s/history.stack" % constants._data_path_
128
129         def __init__(self, parent, app):
130                 qwrappers.WindowWrapper.__init__(self, parent, app)
131
132                 self._historyView = qhistory.QCalcHistory(self._app.errorLog)
133                 self._userEntry = QValueEntry()
134                 self._userEntry.entry.returnPressed.connect(self._on_push)
135                 self._userEntryLayout = QtGui.QHBoxLayout()
136                 self._userEntryLayout.addWidget(self._userEntry.toplevel, 10)
137
138                 self._controlLayout = QtGui.QVBoxLayout()
139                 self._controlLayout.setContentsMargins(0, 0, 0, 0)
140                 self._controlLayout.addWidget(self._historyView.toplevel, 1000)
141                 self._controlLayout.addLayout(self._userEntryLayout, 0)
142
143                 self._keyboardTabs = QtGui.QTabWidget()
144
145                 self._layout.addLayout(self._controlLayout)
146                 self._layout.addWidget(self._keyboardTabs)
147
148                 self._copyItemAction = QtGui.QAction(None)
149                 self._copyItemAction.setText("Copy")
150                 self._copyItemAction.setShortcut(QtGui.QKeySequence("CTRL+c"))
151                 self._copyItemAction.triggered.connect(self._on_copy)
152
153                 self._pasteItemAction = QtGui.QAction(None)
154                 self._pasteItemAction.setText("Paste")
155                 self._pasteItemAction.setShortcut(QtGui.QKeySequence("CTRL+v"))
156                 self._pasteItemAction.triggered.connect(self._on_paste)
157
158                 self._closeWindowAction = QtGui.QAction(None)
159                 self._closeWindowAction.setText("Close")
160                 self._closeWindowAction.setShortcut(QtGui.QKeySequence("CTRL+w"))
161                 self._closeWindowAction.triggered.connect(self._on_close_window)
162
163                 self._window.addAction(self._copyItemAction)
164                 self._window.addAction(self._pasteItemAction)
165
166                 self._constantPlugins = plugin_utils.ConstantPluginManager()
167                 self._constantPlugins.add_path(*self._plugin_search_paths)
168                 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
169                         try:
170                                 pluginId = self._constantPlugins.lookup_plugin(pluginName)
171                                 self._constantPlugins.enable_plugin(pluginId)
172                         except:
173                                 _moduleLogger.info("Failed to load plugin %s" % pluginName)
174
175                 self._operatorPlugins = plugin_utils.OperatorPluginManager()
176                 self._operatorPlugins.add_path(*self._plugin_search_paths)
177                 for pluginName in ["Builtins", "Trigonometry", "Computer", "Alphabet"]:
178                         try:
179                                 pluginId = self._operatorPlugins.lookup_plugin(pluginName)
180                                 self._operatorPlugins.enable_plugin(pluginId)
181                         except:
182                                 _moduleLogger.info("Failed to load plugin %s" % pluginName)
183
184                 self._keyboardPlugins = plugin_utils.KeyboardPluginManager()
185                 self._keyboardPlugins.add_path(*self._plugin_search_paths)
186                 self._activeKeyboards = []
187
188                 self._history = history.RpnCalcHistory(
189                         self._historyView,
190                         self._userEntry, self._errorDisplay,
191                         self._constantPlugins.constants, self._operatorPlugins.operators
192                 )
193                 self._load_history()
194
195                 # Basic keyboard stuff
196                 self._handler = qtpieboard.KeyboardHandler(self._on_entry_direct)
197                 self._handler.register_command_handler("push", self._on_push)
198                 self._handler.register_command_handler("unpush", self._on_unpush)
199                 self._handler.register_command_handler("backspace", self._on_entry_backspace)
200                 self._handler.register_command_handler("clear", self._on_entry_clear)
201
202                 # Main keyboard
203                 entryKeyboardId = self._keyboardPlugins.lookup_plugin("Entry")
204                 self._keyboardPlugins.enable_plugin(entryKeyboardId)
205                 entryPlugin = self._keyboardPlugins.keyboards["Entry"].construct_keyboard()
206                 entryKeyboard = entryPlugin.setup(self._history, self._handler)
207                 self._userEntryLayout.addWidget(entryKeyboard.toplevel)
208
209                 # Plugins
210                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Builtins"))
211                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Trigonometry"))
212                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Computer"))
213                 self.enable_plugin(self._keyboardPlugins.lookup_plugin("Alphabet"))
214
215                 self.set_fullscreen(self._app.fullscreenAction.isChecked())
216                 self.set_orientation(self._app.orientationAction.isChecked())
217
218         def walk_children(self):
219                 return ()
220
221         def set_orientation(self, isPortrait):
222                 qwrappers.WindowWrapper.set_orientation(self, isPortrait)
223                 if isPortrait:
224                         defaultLayoutOrientation = QtGui.QBoxLayout.TopToBottom
225                         tabPosition = QtGui.QTabWidget.South
226                 else:
227                         defaultLayoutOrientation = QtGui.QBoxLayout.LeftToRight
228                         tabPosition = QtGui.QTabWidget.North
229                 self._layout.setDirection(defaultLayoutOrientation)
230                 self._keyboardTabs.setTabPosition(tabPosition)
231
232         def enable_plugin(self, pluginId):
233                 self._keyboardPlugins.enable_plugin(pluginId)
234                 pluginData = self._keyboardPlugins.plugin_info(pluginId)
235                 pluginName = pluginData[0]
236                 plugin = self._keyboardPlugins.keyboards[pluginName].construct_keyboard()
237                 relIcon = self._keyboardPlugins.keyboards[pluginName].icon
238                 for iconPath in self._keyboardPlugins.keyboards[pluginName].iconPaths:
239                         absIconPath = os.path.join(iconPath, relIcon)
240                         if os.path.exists(absIconPath):
241                                 icon = QtGui.QIcon(absIconPath)
242                                 break
243                 else:
244                         icon = None
245                 pluginKeyboard = plugin.setup(self._history, self._handler)
246
247                 self._activeKeyboards.append({
248                         "pluginName": pluginName,
249                         "plugin": plugin,
250                         "pluginKeyboard": pluginKeyboard,
251                 })
252                 if icon is None:
253                         self._keyboardTabs.addTab(pluginKeyboard.toplevel, pluginName)
254                 else:
255                         self._keyboardTabs.addTab(pluginKeyboard.toplevel, icon, "")
256
257         def close(self):
258                 qwrappers.WindowWrapper.close(self)
259                 self._save_history()
260
261         def _load_history(self):
262                 serialized = []
263                 try:
264                         with open(self._user_history, "rU") as f:
265                                 serialized = (
266                                         (part.strip() for part in line.split(" "))
267                                         for line in f.readlines()
268                                 )
269                 except IOError, e:
270                         if e.errno != 2:
271                                 raise
272                 self._history.deserialize_stack(serialized)
273
274         def _save_history(self):
275                 serialized = self._history.serialize_stack()
276                 with open(self._user_history, "w") as f:
277                         for lineData in serialized:
278                                 line = " ".join(data for data in lineData)
279                                 f.write("%s\n" % line)
280
281         @misc_utils.log_exception(_moduleLogger)
282         def _on_child_close(self, something = None):
283                 self._child = None
284
285         @misc_utils.log_exception(_moduleLogger)
286         def _on_copy(self, *args):
287                 eqNode = self._historyView.peek()
288                 resultNode = eqNode.simplify()
289                 self._app._clipboard.setText(str(resultNode))
290
291         @misc_utils.log_exception(_moduleLogger)
292         def _on_paste(self, *args):
293                 result = str(self._app._clipboard.text())
294                 self._userEntry.append(result)
295
296         @misc_utils.log_exception(_moduleLogger)
297         def _on_entry_direct(self, keys, modifiers):
298                 if "shift" in modifiers:
299                         keys = keys.upper()
300                 self._userEntry.append(keys)
301
302         @misc_utils.log_exception(_moduleLogger)
303         def _on_push(self, *args):
304                 self._history.push_entry()
305
306         @misc_utils.log_exception(_moduleLogger)
307         def _on_unpush(self, *args):
308                 self._historyView.unpush()
309
310         @misc_utils.log_exception(_moduleLogger)
311         def _on_entry_backspace(self, *args):
312                 self._userEntry.pop()
313
314         @misc_utils.log_exception(_moduleLogger)
315         def _on_entry_clear(self, *args):
316                 self._userEntry.clear()
317
318         @misc_utils.log_exception(_moduleLogger)
319         def _on_clear_all(self, *args):
320                 self._history.clear()
321
322
323 def run():
324         app = QtGui.QApplication([])
325         handle = Calculator(app)
326         qtpie.init_pies()
327         return app.exec_()
328
329
330 if __name__ == "__main__":
331         logging.basicConfig(level = logging.DEBUG)
332         try:
333                 os.makedirs(constants._data_path_)
334         except OSError, e:
335                 if e.errno != 17:
336                         raise
337
338         val = run()
339         sys.exit(val)