window.py
1 | import sys |
2 | from PyQt5.QtWidgets import * |
3 | from PyQt5.QtGui import * |
4 | from PyQt5.QtCore import * |
5 | |
6 | import json |
7 | import re |
8 | |
9 | from .manager import ProjectManager |
10 | from .spellcheckedit import SpellCheckEdit |
11 | |
12 | class ProjectTab(QTabWidget): |
13 | def __init__(self, parent = None): |
14 | super(ProjectTab, self).__init__(parent) |
15 | |
16 | class Interface: |
17 | def __init__(self): |
18 | self.value = None |
19 | |
20 | def ok(self): |
21 | self.value = self.qd.textValue() |
22 | |
23 | def askPassword(self): |
24 | self.qd = QInputDialog() |
25 | self.qd.setLabelText(self.qd.tr("Please enter your password:")) |
26 | self.qd.setTextEchoMode(QLineEdit.Password) |
27 | self.qd.accepted.connect(self.ok) |
28 | self.qd.exec_() |
29 | return self.value |
30 | |
31 | class ProjectView(QWidget): |
32 | def __init__(self, project, showTranslated = True, showUntranslated = True, showFuzzy = True, parent = None): |
33 | super(ProjectView, self).__init__(parent) |
34 | self.project = project |
35 | self.content = self.project.content() |
36 | self.currentContent = list(self.content.keys())[0] |
37 | self.showTranslated = showTranslated |
38 | self.showUntranslated = showUntranslated |
39 | self.showFuzzy = showFuzzy |
40 | self.initUI() |
41 | |
42 | def updateContent(self): |
43 | self.treeWidget.clear() |
44 | items = [] |
45 | for entry in self.content[self.currentContent]: |
46 | if entry.isObsolete(): |
47 | continue |
48 | cont = False |
49 | if self.showTranslated and entry.isTranslated(): |
50 | cont = True |
51 | if self.showUntranslated and not entry.isTranslated(): |
52 | cont = True |
53 | if self.showFuzzy and entry.isFuzzy(): |
54 | cont = True |
55 | if not cont: |
56 | continue |
57 | item = QTreeWidgetItem([entry.msgids[0], entry.msgstrs[0]]) |
58 | item.setData(0, Qt.UserRole, entry) |
59 | items.append(item) |
60 | self.treeWidget.insertTopLevelItems(0, items) |
61 | |
62 | def initUI(self): |
63 | vbox = QVBoxLayout() |
64 | self.setLayout(vbox) |
65 | model = QStandardItemModel() |
66 | self.treeWidget = QTreeWidget() |
67 | self.treeWidget.setColumnCount(2) |
68 | self.msgid = QTextEdit() |
69 | self.msgid.setReadOnly(True) |
70 | self.msgstr = QTextEdit() |
71 | self.filechooser = QComboBox() |
72 | for project in list(self.content.keys()): |
73 | self.filechooser.addItem(project) |
74 | self.filechooser.currentIndexChanged.connect(self.changefile) |
75 | |
76 | if self.filechooser.count() > 1: |
77 | vbox.addWidget(self.filechooser) |
78 | |
79 | self.updateContent() |
80 | vbox.addWidget(self.treeWidget, 4) |
81 | self.hbox = QHBoxLayout() |
82 | self.hbox.addWidget(self.msgid) |
83 | self.hbox.addWidget(self.msgstr) |
84 | vbox.addLayout(self.hbox, 1) |
85 | size = self.treeWidget.size() |
86 | self.treeWidget.setColumnWidth(0, size.width()/2) |
87 | self.treeWidget.currentItemChanged.connect(self.selectItem) |
88 | |
89 | def changefile(self): |
90 | self.currentContent = list(self.content.keys())[self.filechooser.currentIndex()] |
91 | self.updateContent() |
92 | |
93 | def selectItem(self, current, old): |
94 | if current == None: |
95 | return |
96 | data = current.data(0, Qt.UserRole) |
97 | self.hbox.removeWidget(self.msgid) |
98 | self.hbox.removeWidget(self.msgstr) |
99 | self.msgid.deleteLater() |
100 | self.msgstr.deleteLater() |
101 | |
102 | if len(data.msgstrs) > 1: |
103 | self.msgid = QTabWidget(); |
104 | self.msgstr = QTabWidget(); |
105 | singular = QTextEdit() |
106 | singular.setReadOnly(True) |
107 | singular.setText(data.msgids[0]) |
108 | plural = QTextEdit() |
109 | plural.setReadOnly(True) |
110 | plural.setText(data.msgids[1]) |
111 | self.msgid.addTab(singular, self.tr("Singular")) |
112 | self.msgid.addTab(plural, self.tr("Plural")) |
113 | i = 0 |
114 | for msgstr in data.msgstrs: |
115 | form = SpellCheckEdit(self.project.lang) |
116 | form.setText(msgstr) |
117 | form.textChanged.connect(self.modify) |
118 | self.msgstr.addTab(form, str(i)) |
119 | i=i+1 |
120 | else: |
121 | self.msgid = QTextEdit() |
122 | self.msgid.setReadOnly(True) |
123 | self.msgstr = SpellCheckEdit(self.project.lang) |
124 | self.msgid.setText(data.msgids[0]) |
125 | self.msgstr.setText(data.msgstrs[0]) |
126 | self.msgstr.textChanged.connect(self.modify) |
127 | self.hbox.addWidget(self.msgid) |
128 | self.hbox.addWidget(self.msgstr) |
129 | |
130 | def modify(self): |
131 | item = self.treeWidget.currentItem() |
132 | data = item.data(0, Qt.UserRole) |
133 | if self.msgstr.__class__.__name__ == "SpellCheckEdit": |
134 | msgstr = self.msgstr.toPlainText() |
135 | data.update(0, msgstr) |
136 | item.setText(1, msgstr) |
137 | else: |
138 | i = 0 |
139 | for msgstr in data.msgstrs: |
140 | data.update(i, self.msgstr.widget(i).toPlainText()) |
141 | i=i+1 |
142 | item.setText(1, data.msgstr_plural[0]) |
143 | |
144 | def save(self): |
145 | self.project.save() |
146 | |
147 | def send(self): |
148 | self.project.save() |
149 | self.project.send(Interface()) |
150 | |
151 | def askmerge(self, msgid, oldstr, newstr): |
152 | # TODO: Actually do something more intelligent |
153 | return newstr |
154 | |
155 | def update(self): |
156 | self.project.save() |
157 | self.project.update(self.askmerge) |
158 | self.content = self.project.content() |
159 | self.updateContent() |
160 | |
161 | def filter(self, showTranslated, showUntranslated, showFuzzy): |
162 | self.showTranslated = showTranslated |
163 | self.showUntranslated = showUntranslated |
164 | self.showFuzzy = showFuzzy |
165 | self.updateContent() |
166 | |
167 | class NewWindow(QDialog): |
168 | def __init__(self, manager, parent = None): |
169 | super().__init__(parent) |
170 | self.name = "" |
171 | self.lang = "" |
172 | self.system = 0 |
173 | self.manager = manager |
174 | self.askNew = False |
175 | self.initUI() |
176 | |
177 | def initUI(self): |
178 | hbox = QHBoxLayout() |
179 | predefinedbox = QVBoxLayout() |
180 | self.searchfield = QLineEdit() |
181 | predefinedbox.addWidget(self.searchfield) |
182 | self.predefinedprojects = QListWidget() |
183 | with open('offlate/data.json') as f: |
184 | self.projectdata = json.load(f) |
185 | for d in self.projectdata: |
186 | item = QListWidgetItem(d['name']) |
187 | item.setData(Qt.UserRole, d) |
188 | self.predefinedprojects.addItem(item) |
189 | predefinedbox.addWidget(self.predefinedprojects) |
190 | |
191 | contentbox = QVBoxLayout() |
192 | formbox = QGroupBox(self.tr("Project information")) |
193 | self.formLayout = QFormLayout() |
194 | formbox.setLayout(self.formLayout) |
195 | |
196 | self.nameWidget = QLineEdit() |
197 | self.langWidget = QLineEdit() |
198 | self.formLayout.addRow(QLabel(self.tr("Name:")), self.nameWidget) |
199 | self.formLayout.addRow(QLabel(self.tr("Target Language:")), self.langWidget) |
200 | self.combo = QComboBox() |
201 | self.combo.addItem(self.tr("The Translation Project")) |
202 | self.combo.addItem(self.tr("Transifex")) |
203 | self.formLayout.addRow(self.combo) |
204 | |
205 | self.nameWidget.textChanged.connect(self.modify) |
206 | self.langWidget.textChanged.connect(self.modify) |
207 | |
208 | hhbox = QHBoxLayout() |
209 | cancel = QPushButton(self.tr("Cancel")) |
210 | self.okbutton = QPushButton(self.tr("OK")) |
211 | self.okbutton.setEnabled(False) |
212 | hhbox.addWidget(cancel) |
213 | hhbox.addWidget(self.okbutton) |
214 | contentbox.addWidget(formbox) |
215 | contentbox.addLayout(hhbox) |
216 | hbox.addLayout(predefinedbox) |
217 | hbox.addLayout(contentbox) |
218 | |
219 | self.additionalFields = [] |
220 | self.additionalFields.append([]) |
221 | self.additionalFields.append([]) |
222 | self.transifexOrganisation = QLineEdit() |
223 | self.transifexOrganisation.textChanged.connect(self.modify) |
224 | transifexOrganisationLabel = QLabel(self.tr("Organization")) |
225 | self.additionalFields[1].append({'label': transifexOrganisationLabel, |
226 | 'widget': self.transifexOrganisation}) |
227 | |
228 | self.setLayout(hbox) |
229 | |
230 | self.predefinedprojects.currentItemChanged.connect(self.fill) |
231 | cancel.clicked.connect(self.close) |
232 | self.okbutton.clicked.connect(self.ok) |
233 | self.searchfield.textChanged.connect(self.filter) |
234 | self.combo.currentIndexChanged.connect(self.othersystem) |
235 | |
236 | def ok(self): |
237 | self.askNew = True |
238 | self.close() |
239 | |
240 | def fill(self): |
241 | item = self.predefinedprojects.currentItem() |
242 | data = item.data(Qt.UserRole) |
243 | self.nameWidget.setText(data['name']) |
244 | self.combo.setCurrentIndex(int(data['system'])) |
245 | if data['system'] == 1: |
246 | self.transifexOrganisation.setText(data['organisation']) |
247 | |
248 | def filter(self): |
249 | search = self.searchfield.text() |
250 | self.predefinedprojects.clear() |
251 | regexp = re.compile(".*"+search) |
252 | for d in self.projectdata: |
253 | if regexp.match(d['name']): |
254 | item = QListWidgetItem(d['name']) |
255 | item.setData(Qt.UserRole, d) |
256 | self.predefinedprojects.addItem(item) |
257 | |
258 | def modify(self): |
259 | enable = False |
260 | if self.nameWidget.text() != '' and self.langWidget.text() != '': |
261 | enable = True |
262 | for widget in self.additionalFields[self.combo.currentIndex()]: |
263 | if widget['widget'].text() == '': |
264 | enable = False |
265 | break |
266 | self.okbutton.setEnabled(enable) |
267 | |
268 | def wantNew(self): |
269 | return self.askNew |
270 | |
271 | def getProjectName(self): |
272 | return self.nameWidget.text() |
273 | |
274 | def getProjectLang(self): |
275 | return self.langWidget.text() |
276 | |
277 | def getProjectSystem(self): |
278 | return self.combo.currentIndex() |
279 | |
280 | def getProjectInfo(self): |
281 | if self.getProjectSystem() == 0: |
282 | return {} |
283 | if self.getProjectSystem() == 1: |
284 | return {'organization': self.additionalFields[1][0]['widget'].text()} |
285 | return {} |
286 | |
287 | def othersystem(self): |
288 | for system in self.additionalFields: |
289 | for widget in system: |
290 | self.formLayout.takeRow(widget['widget']) |
291 | widget['widget'].hide() |
292 | widget['label'].hide() |
293 | self.formLayout.invalidate() |
294 | for widget in self.additionalFields[self.combo.currentIndex()]: |
295 | self.formLayout.addRow(widget['label'], widget['widget']) |
296 | widget['widget'].show() |
297 | widget['label'].show() |
298 | self.modify() |
299 | |
300 | class SettingsWindow(QDialog): |
301 | def __init__(self, preferences, parent = None): |
302 | super().__init__(parent) |
303 | self.data = preferences |
304 | self.done = False |
305 | self.initUI() |
306 | |
307 | def initUI(self): |
308 | vbox = QVBoxLayout() |
309 | |
310 | tab = QTabWidget() |
311 | self.addTPTab(tab) |
312 | self.addTransifexTab(tab) |
313 | |
314 | buttonbox = QHBoxLayout() |
315 | cancel = QPushButton(self.tr("Cancel")) |
316 | ok = QPushButton(self.tr("OK")) |
317 | buttonbox.addWidget(cancel) |
318 | buttonbox.addWidget(ok) |
319 | |
320 | vbox.addWidget(tab) |
321 | vbox.addLayout(buttonbox) |
322 | self.setLayout(vbox) |
323 | cancel.clicked.connect(self.close) |
324 | ok.clicked.connect(self.ok) |
325 | |
326 | def addTransifexTab(self, tab): |
327 | formBox = QGroupBox(self.tr("Transifex")) |
328 | formLayout = QFormLayout() |
329 | self.TransifexToken = QLineEdit() |
330 | |
331 | if not "Transifex" in self.data: |
332 | self.data["Transifex"] = {} |
333 | try: |
334 | self.TransifexToken.setText(self.data["Transifex"]["token"]) |
335 | except Exception: |
336 | pass |
337 | |
338 | self.TransifexToken.textChanged.connect(self.updateTransifex) |
339 | label = QLabel(self.tr("You can get a token from <a href=\"#\">https://www.transifex.com/user/settings/api/</a>")) |
340 | label.linkActivated.connect(self.openTransifex) |
341 | |
342 | formLayout.addRow(QLabel(self.tr("Token:")), self.TransifexToken) |
343 | formLayout.addRow(label) |
344 | |
345 | formBox.setLayout(formLayout) |
346 | tab.addTab(formBox, "Transifex") |
347 | |
348 | def openTransifex(self): |
349 | QDesktopServices().openUrl(QUrl("https://www.transifex.com/user/settings/api/")); |
350 | |
351 | def updateTransifex(self): |
352 | self.data["Transifex"] = {} |
353 | self.data["Transifex"]["token"] = self.TransifexToken.text() |
354 | |
355 | def addTPTab(self, tab): |
356 | formBox = QGroupBox(self.tr("Translation Project")) |
357 | formLayout = QFormLayout() |
358 | |
359 | self.TPemail = QLineEdit() |
360 | self.TPuser = QLineEdit() |
361 | self.TPserver = QLineEdit() |
362 | self.TPfullname = QLineEdit() |
363 | |
364 | if not "TP" in self.data: |
365 | self.data["TP"] = {} |
366 | |
367 | if 'email' in self.data['TP']: |
368 | self.TPemail.setText(self.data["TP"]["email"]) |
369 | if 'user' in self.data['TP']: |
370 | self.TPuser.setText(self.data["TP"]["user"]) |
371 | if 'server' in self.data['TP']: |
372 | self.TPserver.setText(self.data["TP"]["server"]) |
373 | if 'fullname' in self.data['TP']: |
374 | self.TPfullname.setText(self.data["TP"]["fullname"]) |
375 | |
376 | self.TPemail.textChanged.connect(self.updateTP) |
377 | self.TPuser.textChanged.connect(self.updateTP) |
378 | self.TPserver.textChanged.connect(self.updateTP) |
379 | self.TPfullname.textChanged.connect(self.updateTP) |
380 | |
381 | formLayout.addRow(QLabel(self.tr("Email:")), self.TPemail) |
382 | formLayout.addRow(QLabel(self.tr("Server:")), self.TPserver) |
383 | formLayout.addRow(QLabel(self.tr("User Name:")), self.TPuser) |
384 | formLayout.addRow(QLabel(self.tr("Full Name (John Doe <john@doe.me>):")), self.TPfullname) |
385 | |
386 | formBox.setLayout(formLayout) |
387 | tab.addTab(formBox, "TP") |
388 | |
389 | def updateTP(self): |
390 | self.data["TP"] = {} |
391 | self.data["TP"]["email"] = self.TPemail.text() |
392 | self.data["TP"]["user"] = self.TPuser.text() |
393 | self.data["TP"]["server"] = self.TPserver.text() |
394 | self.data["TP"]["fullname"] = self.TPfullname.text() |
395 | |
396 | def ok(self): |
397 | self.done = True |
398 | self.close() |
399 | |
400 | class Window(QMainWindow): |
401 | def __init__(self): |
402 | super().__init__() |
403 | self.manager = ProjectManager() |
404 | self.initUI() |
405 | |
406 | def initOpenProjects(self, menu): |
407 | l = self.manager.listProjects() |
408 | for p in l: |
409 | name = p['name'] |
410 | act = QAction(name, self) |
411 | act.triggered.connect((lambda name: (lambda : self.open(name)))(name)) |
412 | menu.addAction(act) |
413 | |
414 | def open(self, name): |
415 | project = self.manager.getProject(name) |
416 | self.tabs.addTab(ProjectView(project), name) |
417 | |
418 | def save(self): |
419 | self.tabs.currentWidget().save() |
420 | |
421 | def new(self): |
422 | w = NewWindow(self.manager) |
423 | w.exec_() |
424 | if not w.wantNew(): |
425 | return |
426 | self.manager.createProject(w.getProjectName(), w.getProjectLang(), |
427 | w.getProjectSystem(), w.getProjectInfo()) |
428 | self.open(w.getProjectName()) |
429 | |
430 | def send(self): |
431 | self.tabs.currentWidget().send() |
432 | |
433 | def update(self): |
434 | self.tabs.currentWidget().update() |
435 | |
436 | def closeProject(self): |
437 | self.tabs.removeTab(self.tabs.currentIndex()) |
438 | |
439 | def settings(self): |
440 | w = SettingsWindow(self.manager.getConf()) |
441 | w.exec_() |
442 | if w.done: |
443 | self.manager.updateSettings(w.data) |
444 | |
445 | def filter(self): |
446 | for i in range(0, self.tabs.count()): |
447 | self.tabs.widget(i).filter( |
448 | self.showTranslatedAct.isChecked(), |
449 | self.showUntranslatedAct.isChecked(), |
450 | self.showFuzzyAct.isChecked()) |
451 | |
452 | def initUI(self): |
453 | # Build menu |
454 | exitAct = QAction(QIcon('exit.png'), self.tr('Exit'), self) |
455 | exitAct.setShortcut('Ctrl+Q') |
456 | exitAct.setStatusTip(self.tr('Exit application')) |
457 | exitAct.triggered.connect(qApp.quit) |
458 | |
459 | saveAct = QAction(QIcon('save.png'), self.tr('Save'), self) |
460 | saveAct.setShortcut('Ctrl+S') |
461 | saveAct.setStatusTip(self.tr('Save current project')) |
462 | saveAct.triggered.connect(self.save) |
463 | |
464 | newAct = QAction(QIcon('new.png'), self.tr('New'), self) |
465 | newAct.setShortcut('Ctrl+N') |
466 | newAct.setStatusTip(self.tr('New project')) |
467 | newAct.triggered.connect(self.new) |
468 | |
469 | updateAct = QAction(QIcon('download.png'), self.tr('Update'), self) |
470 | updateAct.setShortcut('Ctrl+U') |
471 | updateAct.setStatusTip(self.tr('Get modifications from upstream')) |
472 | updateAct.triggered.connect(self.update) |
473 | |
474 | sendAct = QAction(QIcon('close.png'), self.tr('Close'), self) |
475 | sendAct.setStatusTip(self.tr('Close current project')) |
476 | sendAct.triggered.connect(self.closeProject) |
477 | |
478 | closeAct = QAction(QIcon('upload.png'), self.tr('Send'), self) |
479 | closeAct.setShortcut('Ctrl+E') |
480 | closeAct.setStatusTip(self.tr('Send modifications upstream')) |
481 | closeAct.triggered.connect(self.send) |
482 | |
483 | settingsAct = QAction(QIcon('settings.png'), self.tr('Settings'), self) |
484 | settingsAct.setShortcut('Ctrl+P') |
485 | settingsAct.setStatusTip(self.tr('Set parameters')) |
486 | settingsAct.triggered.connect(self.settings) |
487 | |
488 | self.showTranslatedAct = QAction(self.tr('Show Translated'), self, checkable=True) |
489 | self.showTranslatedAct.setChecked(True) |
490 | self.showTranslatedAct.triggered.connect(self.filter) |
491 | self.showFuzzyAct = QAction(self.tr('Show Fuzzy'), self, checkable=True) |
492 | self.showFuzzyAct.setChecked(True) |
493 | self.showFuzzyAct.triggered.connect(self.filter) |
494 | self.showUntranslatedAct = QAction(self.tr('Show Empty Translation'), self, checkable=True) |
495 | self.showUntranslatedAct.setChecked(True) |
496 | self.showUntranslatedAct.triggered.connect(self.filter) |
497 | |
498 | self.statusBar() |
499 | |
500 | openMenu = QMenu(self.tr('Open'), self) |
501 | self.initOpenProjects(openMenu) |
502 | |
503 | menubar = self.menuBar() |
504 | fileMenu = menubar.addMenu(self.tr('&File')) |
505 | fileMenu.addAction(newAct) |
506 | fileMenu.addMenu(openMenu) |
507 | fileMenu.addSeparator() |
508 | fileMenu.addAction(exitAct) |
509 | |
510 | projectMenu = menubar.addMenu(self.tr('&Project')) |
511 | projectMenu.addAction(updateAct) |
512 | projectMenu.addAction(saveAct) |
513 | projectMenu.addAction(sendAct) |
514 | projectMenu.addSeparator() |
515 | projectMenu.addAction(closeAct) |
516 | |
517 | editMenu = menubar.addMenu(self.tr('&Edit')) |
518 | editMenu.addAction(settingsAct) |
519 | |
520 | viewMenu = menubar.addMenu(self.tr('&View')) |
521 | viewMenu.addAction(self.showTranslatedAct) |
522 | viewMenu.addAction(self.showUntranslatedAct) |
523 | viewMenu.addAction(self.showFuzzyAct) |
524 | |
525 | self.tabs = ProjectTab() |
526 | |
527 | self.setCentralWidget(self.tabs) |
528 | |
529 | self.setGeometry(0, 0, 800, 600) |
530 | self.setWindowTitle('Offlate') |
531 | self.show() |
532 | |
533 | def main(): |
534 | app = QApplication(sys.argv) |
535 | translator = QTranslator() |
536 | if translator.load(QLocale(), "offlate", "_", "i18n"): |
537 | app.installTranslator(translator); |
538 | |
539 | w = Window() |
540 | |
541 | sys.exit(app.exec_()) |
542 | |
543 |