offlate/offlate/window.py

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