Package proser ::
Module proser
|
|
1
2
3 import sys;
4 import os;
5 import signal;
6 from functools import partial
7
8 from word_completion.word_collection import WordCollection;
9 import python_qt_binding;
10 from python_qt_binding.QtGui import QApplication, QMainWindow, QDialog, QPushButton, QTextEdit, QTextCursor, QShortcut, QErrorMessage;
11 from python_qt_binding.QtGui import QMessageBox, QWidget;
12
13
14
16 '''
17 Provides popup windows for information and error messages
18 '''
19
20
21
22
23
25 super(DialogService, self).__init__(parent);
26
27
28
29
30
31
32 self.errorMsgPopup = QErrorMessage.qtHandler();
33
34
35 self.errorMsgPopup.setParent(parent, self.errorMsgPopup.windowFlags());
36
37 self.infoMsg = QMessageBox(parent=parent);
38
39
40
41
42
43 QErrorMessage
45 '''
46 Given a string, pop up an error dialog on top of the application window.
47 @param errMsg: The message
48 @type errMsg: string
49 '''
50 self.errorMsgPopup.showMessage(errMsg);
51
52
53
54
55
57 '''
58 Display a message window with an OK button on top of the application window.
59 @param text: text to display
60 @type text: string
61 '''
62 self.infoMsg.setText(text);
63 self.infoMsg.exec_();
64
65
66
68 '''
69 Creates a top level X window in which user can type. Proser provides
70 statistical word completion suggestions. The suggestions are displayed
71 on five buttons at the top of the window. Two methods are available to
72 select one of the suggestions: Click the respective onscreen button, or
73 type one of F5-F9. Completion is based on the word_completion package,
74 which uses a frequency-ranked 6000 word dictionary.
75
76 A Copy button copies the entire typed text into the X cut buffer (a.k.a. clipboard),
77 from where it may be pasted into any other window.
78
79 A special link exists between Proser and SpeakEasy. Proser provides two
80 SpeakEasy related buttons:
81 1. Erase the SpeakEasy text display
82 2. Send the entire Proser window text to SpeakEasy and have it
83 spoken by the currently selected voice. The SpeakEasy window
84 may be minimized during this operation.
85
86 Users may type their text from a physical, or any onscreen keyboard.
87 '''
88
89 VERSION = "1.0";
90 SPEAKEASY_PID_PUBLICATION_FILE = "/tmp/speakeasyPID";
91 NO_COMPLETION_TEXT = '';
92 FIRST_SHORTCUT_FUNC_KEY = 5;
93
94
95
96 REMOTE_CLEAR_TEXT_SIG = signal.SIGUSR1;
97 REMOTE_PASTE_AND_SPEAK_SIG = signal.SIGUSR2;
98
99
100 - def __init__(self, dictDir=None, userDictFilePath=None):
101
102 super(Proser,self).__init__();
103
104
105 self.completer = WordCollection(dictDir=dictDir, userDictFilePath=userDictFilePath);
106
107
108 guiPath = os.path.join(os.path.dirname(__file__), 'qt_files/Proser/proser.ui');
109 self.ui = python_qt_binding.loadUi(guiPath, self);
110 self.setWindowTitle("Proser (V" + Proser.VERSION + ")");
111 self.completionButtons = [self.wordOption1Button,
112 self.wordOption2Button,
113 self.wordOption3Button,
114 self.wordOption4Button,
115 self.wordOption5Button];
116 self.clearCompletionButtons();
117 self.dialogService = DialogService(parent=self);
118 self.connectWidgets();
119
120 self.show();
121 self.focusOnTextArea();
122
143
151
153 '''
154 Copy all Proser text to the X cut buffer (i.e. clipboard).
155 '''
156 currCursor = self.textArea.textCursor();
157 currCursor.select(QTextCursor.Document);
158 self.textArea.setTextCursor(currCursor);
159 self.textArea.copy();
160 currCursor = self.textArea.textCursor();
161 currCursor.clearSelection();
162 self.textArea.setTextCursor(currCursor);
163 self.focusOnTextArea();
164
166 '''
167 Act on notification that text in the text panel changed.
168 This notification occurs with every one of the user's keystroke.
169 In response this method updates the text completion buttons.
170 '''
171 wordSoFar = self.getWordSoFar();
172 if len(wordSoFar) == 0:
173 self.clearCompletionButtons();
174 return;
175 completions = self.completer.prefix_search(wordSoFar, cutoffRank=len(self.completionButtons));
176 if len(completions) == 0:
177 self.clearCompletionButtons();
178
179 self.clearCompletionButtons();
180 for index,button in enumerate(self.completionButtons):
181 if index >= len(completions):
182 return;
183 button.setText(completions[index]);
184
200
202 '''
203 User pressed a function key F5-F9. Invoke the actionCompletionButton() method.
204 @param buttonIndex: Index 0-4 into the array self.completionButtons.
205 @type buttonIndex: int
206 '''
207
208
209 try:
210 self.actionCompletionButton(self.completionButtons[buttonIndex]);
211 except IndexError:
212
213 pass;
214
249
250
252 '''
253 Service method to retrieve the most recent partially typed word.
254 '''
255 currCursor = self.textArea.textCursor();
256 currCursor.select(QTextCursor.WordUnderCursor);
257 wordFragment = currCursor.selectedText().encode('UTF-8');
258
259 return wordFragment;
260
267
268 - def focusOnTextArea(self):
269 '''
270 Service method to force the cursor focus into the text area.
271 '''
272 self.textArea.setFocus();
273
275 '''
276 Copy current content of the text field into the X clipboard.
277 Cause a running SpeakEasy process to paste that newly loaded X clipboard into
278 its SpeakEasy text area, and to speak the content using the current voice.
279 The method getSpeakEasyPID() is called from here, and that method will
280 raise a warning dialog if no SpeakEasy process is currently running.
281 That method will also raise the ValueError documented below.
282
283 Implementation: Send a Unix signal REMOTE_PASTE_AND_SPEAK_SIG to the
284 SpeakEasy application, if one is running.
285
286 @raise ValueError: if the file /tmp/speakeasyPID does not contain an integer. That
287 file is initialized by SpeakEasy with that process' PID. While that
288 pid might be stale, it would still be an integer, unless the file
289 is changed manually.
290 '''
291 pid = self.getSpeakEasyPID();
292 if pid is None:
293
294 return;
295 self.actionCopy();
296 try:
297 os.kill(pid, Proser.REMOTE_PASTE_AND_SPEAK_SIG);
298 except OSError:
299
300 self.dialogService.showErrorMsg("SpeakEasy application seems not to be running. Please start it.");
301
303 '''
304 Cause a running SpeakEasy process to clear its text area.
305
306 Implementation: Send a Unix signal REMOTE_CLEAR_TEXT_SIG to
307 the SpeakEasy process if one is running. Else a warning dialog is raised.
308
309 @raise ValueError: if the file /tmp/speakeasyPID does not contain an integer. That
310 file is initialized by SpeakEasy with that process' PID. While that
311 pid might be stale, it would still be an integer, unless the file
312 is changed manually.
313 '''
314 pid = self.getSpeakEasyPID();
315 if pid is None:
316
317 return;
318 try:
319 os.kill(pid, Proser.REMOTE_CLEAR_TEXT_SIG);
320 except OSError:
321
322 self.dialogService.showErrorMsg("SpeakEasy application seems not to be running. Please start it.");
323
325 '''
326 Return the PID of the SpeakEasy application, if it is running.
327 Else return None. The PID is communicated via a file. Note that
328 this file's content might be stale. So callers must protect against
329 the target process not running any more.
330
331 @raise ValueError: if the file /tmp/speakeasyPID does not contain an integer. That
332 file is initialized by SpeakEasy with that process' PID. While that
333 pid might be stale, it would still be an integer, unless the file
334 is changed manually.
335 '''
336 try:
337 pidFile = os.fdopen(os.open(Proser.SPEAKEASY_PID_PUBLICATION_FILE, os.O_RDONLY));
338 except OSError:
339 self.dialogService.showErrorMsg("SpeakEasy application seems not to be running. Please start it.");
340 return None;
341 try:
342 pid = int(pidFile.readline());
343 except ValueError:
344
345 self.dialogService.showErrorMsg("SpeakEasy PID file did not contain an integer. Please notify the developer.");
346 return None;
347
348 return pid;
349
350
351 if __name__ == '__main__':
352
353 app = QApplication(sys.argv);
354
355 proser = Proser();
356 app.exec_();
357 sys.exit();
358