Add project manager.

Julien LepillerThu Aug 22 17:41:44+0200 2019

937e3ae

Add project manager. This commit adds a project manager. Now, on startup, a new window, called the project manager, is shown, with options to open, edit and remove projects, as well as creating new projects, editing preferences and showing a short about window. This commit also separates UI elements in their own file, and puts them in a dedicated directory at offlate/ui.

Makefile

3838
3939
update-langs:
4040
	for l in $(LANGS); do \
41-
		pylupdate5 offlate/*.py -ts offlate/locales/offlate_$${l}.ts ;\
41+
		pylupdate5 offlate/ui/*.py -ts offlate/locales/offlate_$${l}.ts ;\
4242
		lrelease offlate/locales/offlate_$${l}.ts ;\
4343
	done

offlate/icon.png unknown status 1

Binary data

offlate/locales/offlate_fr.ts

11
<?xml version="1.0" encoding="utf-8"?>
22
<!DOCTYPE TS><TS version="2.0">
33
<context>
4+
    <name>AboutWindow</name>
5+
    <message>
6+
        <location filename="../ui/about.py" line="43"/>
7+
        <source>Offlate is a translation interface for offline translation of projects using online platforms. Offlate is free software, you can redistribute it under the GPL v3 license or any later version.</source>
8+
        <translation>Offlate est une interface de traduction hors-ligne de projets qui utilisent des plateformes en ligne. Offlate est un logiciel libre, vous pouvez le redistribuer sous la licence GPL v3 ou toute version ult??rieure.</translation>
9+
    </message>
10+
    <message>
11+
        <location filename="../ui/about.py" line="49"/>
12+
        <source>Copyright &#xc2;&#xa9; 2018, 2019 Julien Lepiller</source>
13+
        <translation>Copyright ???? 2018, 2019 Julien Lepiller</translation>
14+
    </message>
15+
    <message>
16+
        <location filename="../ui/about.py" line="51"/>
17+
        <source>Report an issue</source>
18+
        <translation>Rapporter un probl??me</translation>
19+
    </message>
20+
    <message>
21+
        <location filename="../ui/about.py" line="52"/>
22+
        <source>Close this window</source>
23+
        <translation>Fermer cette fen??tre</translation>
24+
    </message>
25+
</context>
26+
<context>
27+
    <name>EditorWindow</name>
28+
    <message>
29+
        <location filename="../ui/editor.py" line="285"/>
30+
        <source>Unsupported / Unknown project</source>
31+
        <translation>Projet inconnu ou non support??</translation>
32+
    </message>
33+
    <message>
34+
        <location filename="../ui/editor.py" line="307"/>
35+
        <source>{} translated on {} total ({}%).</source>
36+
        <translation>{} traduits sur {} ({} %).</translation>
37+
    </message>
38+
    <message>
39+
        <location filename="../ui/editor.py" line="360"/>
40+
        <source>Exit</source>
41+
        <translation>Quitter</translation>
42+
    </message>
43+
    <message>
44+
        <location filename="../ui/editor.py" line="362"/>
45+
        <source>Exit application</source>
46+
        <translation>Quitter l&apos;application</translation>
47+
    </message>
48+
    <message>
49+
        <location filename="../ui/editor.py" line="365"/>
50+
        <source>Save</source>
51+
        <translation>Sauvegarder</translation>
52+
    </message>
53+
    <message>
54+
        <location filename="../ui/editor.py" line="367"/>
55+
        <source>Save current project</source>
56+
        <translation>Sauvegarder le projet actuel</translation>
57+
    </message>
58+
    <message>
59+
        <location filename="../ui/editor.py" line="370"/>
60+
        <source>New</source>
61+
        <translation>Nouveau</translation>
62+
    </message>
63+
    <message>
64+
        <location filename="../ui/editor.py" line="372"/>
65+
        <source>New project</source>
66+
        <translation>Nouveau projet</translation>
67+
    </message>
68+
    <message>
69+
        <location filename="../ui/editor.py" line="375"/>
70+
        <source>Manage Projects</source>
71+
        <translation>G??rer les projets</translation>
72+
    </message>
73+
    <message>
74+
        <location filename="../ui/editor.py" line="378"/>
75+
        <source>Open project manager</source>
76+
        <translation>Ouvrir le gestionnaire de projets</translation>
77+
    </message>
78+
    <message>
79+
        <location filename="../ui/editor.py" line="381"/>
80+
        <source>Update</source>
81+
        <translation>Mettre ?? jour</translation>
82+
    </message>
83+
    <message>
84+
        <location filename="../ui/editor.py" line="383"/>
85+
        <source>Get modifications from upstream</source>
86+
        <translation>R??cup??rer les modifications en amont</translation>
87+
    </message>
88+
    <message>
89+
        <location filename="../ui/editor.py" line="386"/>
90+
        <source>Close</source>
91+
        <translation>Fermer</translation>
92+
    </message>
93+
    <message>
94+
        <location filename="../ui/editor.py" line="387"/>
95+
        <source>Close current project</source>
96+
        <translation>Fermer le projet actuel</translation>
97+
    </message>
98+
    <message>
99+
        <location filename="../ui/editor.py" line="390"/>
100+
        <source>Send</source>
101+
        <translation>Envoyer</translation>
102+
    </message>
103+
    <message>
104+
        <location filename="../ui/editor.py" line="392"/>
105+
        <source>Send modifications upstream</source>
106+
        <translation>Envoyer les modifications en amont</translation>
107+
    </message>
108+
    <message>
109+
        <location filename="../ui/editor.py" line="395"/>
110+
        <source>Settings</source>
111+
        <translation>Param??tres</translation>
112+
    </message>
113+
    <message>
114+
        <location filename="../ui/editor.py" line="397"/>
115+
        <source>Set parameters</source>
116+
        <translation>Configurer les param??tres</translation>
117+
    </message>
118+
    <message>
119+
        <location filename="../ui/editor.py" line="400"/>
120+
        <source>Show Translated</source>
121+
        <translation>Montrer les cha??nes traduites</translation>
122+
    </message>
123+
    <message>
124+
        <location filename="../ui/editor.py" line="403"/>
125+
        <source>Show Fuzzy</source>
126+
        <translation>Montrer les traductions floues</translation>
127+
    </message>
128+
    <message>
129+
        <location filename="../ui/editor.py" line="406"/>
130+
        <source>Show Empty Translation</source>
131+
        <translation>Montrer les traductions vides</translation>
132+
    </message>
133+
    <message>
134+
        <location filename="../ui/editor.py" line="409"/>
135+
        <source>Use a monospace font</source>
136+
        <translation>Utiliser une police ?? chasse fixe</translation>
137+
    </message>
138+
    <message>
139+
        <location filename="../ui/editor.py" line="429"/>
140+
        <source>Open</source>
141+
        <translation>Ouvrir</translation>
142+
    </message>
143+
    <message>
144+
        <location filename="../ui/editor.py" line="433"/>
145+
        <source>&amp;File</source>
146+
        <translation>&amp;Fichier</translation>
147+
    </message>
148+
    <message>
149+
        <location filename="../ui/editor.py" line="441"/>
150+
        <source>&amp;Project</source>
151+
        <translation>&amp;Projet</translation>
152+
    </message>
153+
    <message>
154+
        <location filename="../ui/editor.py" line="448"/>
155+
        <source>&amp;Edit</source>
156+
        <translation>&amp;??dition</translation>
157+
    </message>
158+
    <message>
159+
        <location filename="../ui/editor.py" line="451"/>
160+
        <source>&amp;View</source>
161+
        <translation>&amp;Affichage</translation>
162+
    </message>
163+
</context>
164+
<context>
165+
    <name>ModalWait</name>
166+
    <message>
167+
        <location filename="../ui/manager.py" line="37"/>
168+
        <source>Please wait&#xe2;&#x80;&#xa6;</source>
169+
        <translation>Patientez??????</translation>
170+
    </message>
171+
</context>
172+
<context>
173+
    <name>MultipleLineEdit</name>
174+
    <message>
175+
        <location filename="../ui/multiplelineedit.py" line="59"/>
176+
        <source>Add</source>
177+
        <translation>Ajouter</translation>
178+
    </message>
179+
    <message>
180+
        <location filename="../ui/multiplelineedit.py" line="61"/>
181+
        <source>Remove</source>
182+
        <translation>Supprimer</translation>
183+
    </message>
184+
</context>
185+
<context>
4186
    <name>NewWindow</name>
5187
    <message>
6-
        <location filename="../window.py" line="292"/>
188+
        <location filename="../ui/new.py" line="68"/>
7189
        <source>The Translation Project</source>
8190
        <translation>Le Projet de Traduction</translation>
9191
    </message>
10192
    <message>
11-
        <location filename="../window.py" line="300"/>
193+
        <location filename="../ui/new.py" line="78"/>
12194
        <source>Cancel</source>
13195
        <translation>Annuler</translation>
14196
    </message>
15197
    <message>
16-
        <location filename="../window.py" line="301"/>
198+
        <location filename="../ui/new.py" line="79"/>
17199
        <source>OK</source>
18200
        <translation>OK</translation>
19201
    </message>
20202
    <message>
21-
        <location filename="../window.py" line="283"/>
203+
        <location filename="../ui/new.py" line="57"/>
22204
        <source>Project information</source>
23205
        <translation>Informations sur le projet</translation>
24206
    </message>
25207
    <message>
26-
        <location filename="../window.py" line="289"/>
208+
        <location filename="../ui/new.py" line="65"/>
27209
        <source>Name:</source>
28210
        <translation>Nom :</translation>
29211
    </message>
30212
    <message>
31-
        <location filename="../window.py" line="290"/>
213+
        <location filename="../ui/new.py" line="66"/>
32214
        <source>Target Language:</source>
33215
        <translation>Langue cible :</translation>
34216
    </message>
35217
    <message>
36-
        <location filename="../window.py" line="293"/>
218+
        <location filename="../ui/new.py" line="69"/>
37219
        <source>Transifex</source>
38220
        <translation>Transifex</translation>
39221
    </message>
40222
    <message>
41-
        <location filename="../window.py" line="315"/>
223+
        <location filename="../ui/new.py" line="98"/>
42224
        <source>Organization</source>
43225
        <translation>Organisation</translation>
44226
    </message>
227+
    <message>
228+
        <location filename="../ui/new.py" line="70"/>
229+
        <source>Gitlab</source>
230+
        <translation>Gitlab</translation>
231+
    </message>
232+
    <message>
233+
        <location filename="../ui/new.py" line="105"/>
234+
        <source>hosting website</source>
235+
        <translation>site h??bergeant le projet</translation>
236+
    </message>
237+
    <message>
238+
        <location filename="../ui/new.py" line="110"/>
239+
        <source>project part</source>
240+
        <translation>partie du projet</translation>
241+
    </message>
242+
    <message>
243+
        <location filename="../ui/new.py" line="118"/>
244+
        <source>Translation type</source>
245+
        <translation>Type de traduction</translation>
246+
    </message>
247+
    <message>
248+
        <location filename="../ui/new.py" line="123"/>
249+
        <source>Locations</source>
250+
        <translation>Emplacements</translation>
251+
    </message>
252+
</context>
253+
<context>
254+
    <name>ProjectManagerWidget</name>
255+
    <message>
256+
        <location filename="../ui/manager.py" line="86"/>
257+
        <source>Open</source>
258+
        <translation>Ouvrir</translation>
259+
    </message>
260+
    <message>
261+
        <location filename="../ui/manager.py" line="87"/>
262+
        <source>Edit</source>
263+
        <translation>Modifier</translation>
264+
    </message>
265+
    <message>
266+
        <location filename="../ui/manager.py" line="88"/>
267+
        <source>Remove</source>
268+
        <translation>Supprimer</translation>
269+
    </message>
270+
    <message>
271+
        <location filename="../ui/manager.py" line="96"/>
272+
        <source>New Project</source>
273+
        <translation>Nouveau projet</translation>
274+
    </message>
275+
    <message>
276+
        <location filename="../ui/manager.py" line="97"/>
277+
        <source>Settings</source>
278+
        <translation>Param??tres</translation>
279+
    </message>
280+
    <message>
281+
        <location filename="../ui/manager.py" line="98"/>
282+
        <source>About Offlate</source>
283+
        <translation>?? propos d&apos;Offlate</translation>
284+
    </message>
285+
    <message>
286+
        <location filename="../ui/manager.py" line="99"/>
287+
        <source>Exit</source>
288+
        <translation>Quitter</translation>
289+
    </message>
290+
</context>
291+
<context>
292+
    <name>ProjectManagerWindow</name>
293+
    <message>
294+
        <location filename="../ui/manager.py" line="59"/>
295+
        <source>Offlate Project Manager</source>
296+
        <translation>Gestionnaire de projets d&apos;Offlate</translation>
297+
    </message>
45298
</context>
46299
<context>
47300
    <name>ProjectView</name>
48301
    <message>
49-
        <location filename="../window.py" line="178"/>
302+
        <location filename="../ui/editor.py" line="187"/>
50303
        <source>Singular</source>
51304
        <translation>Singulier</translation>
52305
    </message>
53306
    <message>
54-
        <location filename="../window.py" line="179"/>
307+
        <location filename="../ui/editor.py" line="188"/>
55308
        <source>Plural</source>
56309
        <translation>Pluriel</translation>
57310
    </message>
58311
    <message>
59-
        <location filename="../window.py" line="96"/>
312+
        <location filename="../ui/editor.py" line="105"/>
60313
        <source>Copy</source>
61314
        <translation>Copier</translation>
62315
    </message>

64317
<context>
65318
    <name>SettingsWindow</name>
66319
    <message>
67-
        <location filename="../window.py" line="406"/>
320+
        <location filename="../ui/settings.py" line="36"/>
68321
        <source>Cancel</source>
69322
        <translation>Annuler</translation>
70323
    </message>
71324
    <message>
72-
        <location filename="../window.py" line="407"/>
325+
        <location filename="../ui/settings.py" line="37"/>
73326
        <source>OK</source>
74327
        <translation>OK</translation>
75328
    </message>
76329
    <message>
77-
        <location filename="../window.py" line="472"/>
330+
        <location filename="../ui/settings.py" line="102"/>
78331
        <source>Email:</source>
79332
        <translation>Adresse de courriel :</translation>
80333
    </message>
81334
    <message>
82-
        <location filename="../window.py" line="473"/>
335+
        <location filename="../ui/settings.py" line="103"/>
83336
        <source>Server:</source>
84337
        <translation>Serveur :</translation>
85338
    </message>
86339
    <message>
87-
        <location filename="../window.py" line="474"/>
340+
        <location filename="../ui/settings.py" line="104"/>
88341
        <source>User Name:</source>
89342
        <translation>Nom d&apos;utilisateur :</translation>
90343
    </message>
91344
    <message>
92-
        <location filename="../window.py" line="418"/>
345+
        <location filename="../ui/settings.py" line="48"/>
93346
        <source>Transifex</source>
94347
        <translation>Transifex</translation>
95348
    </message>
96349
    <message>
97-
        <location filename="../window.py" line="430"/>
350+
        <location filename="../ui/settings.py" line="60"/>
98351
        <source>You can get a token from &lt;a href=&quot;#&quot;&gt;https://www.transifex.com/user/settings/api/&lt;/a&gt;</source>
99352
        <translation>Vous pouvez r??cup??rer un jeton sur &lt;a href=&quot;#&quot;&gt;https://www.transifex.com/user/settings/api/&lt;/a&gt;</translation>
100353
    </message>
101354
    <message>
102-
        <location filename="../window.py" line="433"/>
355+
        <location filename="../ui/settings.py" line="63"/>
103356
        <source>Token:</source>
104357
        <translation>Jeton :</translation>
105358
    </message>
106359
    <message>
107-
        <location filename="../window.py" line="447"/>
360+
        <location filename="../ui/settings.py" line="77"/>
108361
        <source>Translation Project</source>
109362
        <translation>Projet de traduction</translation>
110363
    </message>
111364
    <message>
112-
        <location filename="../window.py" line="475"/>
365+
        <location filename="../ui/settings.py" line="105"/>
113366
        <source>Full Name (John Doe &lt;john@doe.me&gt;):</source>
114367
        <translation>Nom complet (Jean Dupont &lt;jean@dupont.me&gt;) :</translation>
115368
    </message>

117370
<context>
118371
    <name>SpellCheckEdit</name>
119372
    <message>
120-
        <location filename="../spellcheckedit.py" line="27"/>
373+
        <location filename="../ui/spellcheckedit.py" line="43"/>
121374
        <source>Spelling Suggestions</source>
122375
        <translation>Suggestions</translation>
123376
    </message>
124377
    <message>
125-
        <location filename="../spellcheckedit.py" line="28"/>
378+
        <location filename="../ui/spellcheckedit.py" line="44"/>
126379
        <source>No Suggestions</source>
127380
        <translation>Pas de suggestion</translation>
128381
    </message>

132385
    <message>
133386
        <location filename="../window.py" line="584"/>
134387
        <source>Exit application</source>
135-
        <translation>Quitter l&apos;application</translation>
388+
        <translation type="obsolete">Quitter l&apos;application</translation>
136389
    </message>
137390
    <message>
138391
        <location filename="../window.py" line="589"/>
139392
        <source>Save current project</source>
140-
        <translation>Sauvegarder le projet actuel</translation>
393+
        <translation type="obsolete">Sauvegarder le projet actuel</translation>
141394
    </message>
142395
    <message>
143396
        <location filename="../window.py" line="594"/>
144397
        <source>New project</source>
145-
        <translation>Nouveau projet</translation>
398+
        <translation type="obsolete">Nouveau projet</translation>
146399
    </message>
147400
    <message>
148401
        <location filename="../window.py" line="599"/>
149402
        <source>Get modifications from upstream</source>
150-
        <translation>R??cup??rer les modifications en amont</translation>
403+
        <translation type="obsolete">R??cup??rer les modifications en amont</translation>
151404
    </message>
152405
    <message>
153406
        <location filename="../window.py" line="603"/>
154407
        <source>Close current project</source>
155-
        <translation>Fermer le projet actuel</translation>
408+
        <translation type="obsolete">Fermer le projet actuel</translation>
156409
    </message>
157410
    <message>
158411
        <location filename="../window.py" line="608"/>
159412
        <source>Send modifications upstream</source>
160-
        <translation>Envoyer les modifications en amont</translation>
413+
        <translation type="obsolete">Envoyer les modifications en amont</translation>
161414
    </message>
162415
    <message>
163416
        <location filename="../window.py" line="613"/>
164417
        <source>Set parameters</source>
165-
        <translation>Configurer les param??tres</translation>
418+
        <translation type="obsolete">Configurer les param??tres</translation>
166419
    </message>
167420
    <message>
168421
        <location filename="../window.py" line="616"/>
169422
        <source>Show Translated</source>
170-
        <translation>Montrer traduites</translation>
423+
        <translation type="obsolete">Montrer traduites</translation>
171424
    </message>
172425
    <message>
173426
        <location filename="../window.py" line="619"/>
174427
        <source>Show Fuzzy</source>
175-
        <translation>Montrer floues</translation>
428+
        <translation type="obsolete">Montrer floues</translation>
176429
    </message>
177430
    <message>
178431
        <location filename="../window.py" line="622"/>
179432
        <source>Show Empty Translation</source>
180-
        <translation>Montrer les traductions vides</translation>
433+
        <translation type="obsolete">Montrer les traductions vides</translation>
181434
    </message>
182435
    <message>
183436
        <location filename="../window.py" line="649"/>
184437
        <source>&amp;File</source>
185-
        <translation>&amp;Fichier</translation>
438+
        <translation type="obsolete">&amp;Fichier</translation>
186439
    </message>
187440
    <message>
188441
        <location filename="../window.py" line="655"/>
189442
        <source>&amp;Project</source>
190-
        <translation>&amp;Projet</translation>
443+
        <translation type="obsolete">&amp;Projet</translation>
191444
    </message>
192445
    <message>
193446
        <location filename="../window.py" line="662"/>
194447
        <source>&amp;Edit</source>
195-
        <translation>&amp;??dition</translation>
448+
        <translation type="obsolete">&amp;??dition</translation>
196449
    </message>
197450
    <message>
198451
        <location filename="../window.py" line="665"/>
199452
        <source>&amp;View</source>
200-
        <translation>&amp;Affichage</translation>
453+
        <translation type="obsolete">&amp;Affichage</translation>
201454
    </message>
202455
    <message>
203456
        <location filename="../window.py" line="645"/>
204457
        <source>Open</source>
205-
        <translation>Ouvrir</translation>
458+
        <translation type="obsolete">Ouvrir</translation>
206459
    </message>
207460
    <message>
208461
        <location filename="../window.py" line="582"/>
209462
        <source>Exit</source>
210-
        <translation>Quitter</translation>
463+
        <translation type="obsolete">Quitter</translation>
211464
    </message>
212465
    <message>
213466
        <location filename="../window.py" line="587"/>
214467
        <source>Save</source>
215-
        <translation>Sauvegarder</translation>
468+
        <translation type="obsolete">Sauvegarder</translation>
216469
    </message>
217470
    <message>
218471
        <location filename="../window.py" line="592"/>
219472
        <source>New</source>
220-
        <translation>Nouveau</translation>
473+
        <translation type="obsolete">Nouveau</translation>
221474
    </message>
222475
    <message>
223476
        <location filename="../window.py" line="597"/>
224477
        <source>Update</source>
225-
        <translation>Mettre ?? jour</translation>
478+
        <translation type="obsolete">Mettre ?? jour</translation>
226479
    </message>
227480
    <message>
228481
        <location filename="../window.py" line="602"/>
229482
        <source>Close</source>
230-
        <translation>Fermer</translation>
483+
        <translation type="obsolete">Fermer</translation>
231484
    </message>
232485
    <message>
233486
        <location filename="../window.py" line="606"/>
234487
        <source>Send</source>
235-
        <translation>Envoyer</translation>
488+
        <translation type="obsolete">Envoyer</translation>
236489
    </message>
237490
    <message>
238491
        <location filename="../window.py" line="611"/>
239492
        <source>Settings</source>
240-
        <translation>Param??tres</translation>
493+
        <translation type="obsolete">Param??tres</translation>
241494
    </message>
242495
    <message>
243496
        <location filename="../window.py" line="625"/>
244497
        <source>Use a monospace font</source>
245-
        <translation>Utiliser une police ?? chasse fixe</translation>
498+
        <translation type="obsolete">Utiliser une police ?? chasse fixe</translation>
246499
    </message>
247500
    <message>
248501
        <location filename="../window.py" line="532"/>
249502
        <source>{} translated on {} total ({}%).</source>
250-
        <translation>{} traduits sur {} ({} %).</translation>
503+
        <translation type="obsolete">{} traduits sur {} ({} %).</translation>
251504
    </message>
252505
    <message>
253506
        <location filename="../window.py" line="510"/>
254507
        <source>Unsupported / Unknown project</source>
255-
        <translation>Projet inconnu ou non support??</translation>
508+
        <translation type="obsolete">Projet inconnu ou non support??</translation>
256509
    </message>
257510
</context>
258511
<context>
259512
    <name>self.qd</name>
260513
    <message>
261-
        <location filename="../window.py" line="29"/>
514+
        <location filename="../ui/editor.py" line="38"/>
262515
        <source>Please enter your password:</source>
263-
        <translation>Veuillez entrer votre mot de passe :</translation>
516+
        <translation>Saisissez votre mot de passe :</translation>
264517
    </message>
265518
</context>
266519
</TS>

offlate/manager.py

6464
            with open(self.basedir + '/projects.json') as f:
6565
                self.projects = json.load(f)
6666
                for p in self.projects:
67-
                    if p['system'] == TRANSLATION_PROJECT:
68-
                        if not "TP" in self.settings.conf:
69-
                            self.settings.conf["TP"] = {}
70-
                        self.project_list[p['name']] = \
71-
                            TPProject(self.settings.conf["TP"], p['name'], p['lang'], p['info'])
72-
                        self.project_list[p['name']].open(self.basedir+'/'+p['name'])
73-
                    if p['system'] == TRANSIFEX:
74-
                        if not "Transifex" in self.settings.conf:
75-
                            self.settings.conf['Transifex'] = {}
76-
                        self.project_list[p['name']] = \
77-
                            TransifexProject(self.settings.conf['Transifex'], p['name'], p['lang'], p['info'])
78-
                        self.project_list[p['name']].open(self.basedir+'/'+p['name'])
79-
                    if p['system'] == GITLAB:
80-
                        if not "Gitlab" in self.settings.conf:
81-
                            self.settings.conf['Gitlab'] = {}
82-
                        self.project_list[p['name']] = \
83-
                            GitlabProject(self.settings.conf['Gitlab'], p['name'], p['lang'], p['info'])
84-
                        self.project_list[p['name']].open(self.basedir+'/'+p['name'])
67+
                    proj = self.loadProject(p['name'], p['lang'], p['system'],
68+
                            p['info'])
69+
                    proj.open(self.basedir+'/'+p['name'])
8570
        except Exception as e:
8671
            print(e)
8772
            with open(self.basedir + '/projects.json', 'w') as f:

9176
        projectpath = self.basedir + '/' + name
9277
        Path(projectpath).mkdir(parents=True)
9378
        try:
79+
            proj = self.loadProject(name, lang, system, data)
80+
            proj.initialize(projectpath)
9481
            if system == TRANSLATION_PROJECT:
95-
                if not "TP" in self.settings.conf:
96-
                    self.settings.conf["TP"] = {}
97-
                proj = TPProject(self.settings.conf["TP"], name, lang)
98-
                proj.initialize(projectpath)
99-
                self.project_list[name] = proj
10082
                self.projects.append({"name": name, "lang": lang, "system": system,
101-
                        "info": {"version": proj.version}})
102-
            if system == TRANSIFEX:
103-
                if not 'Transifex' in self.settings.conf:
104-
                    self.settings.conf['Transifex'] = {}
105-
                proj = TransifexProject(self.settings.conf['Transifex'], name, lang, data)
106-
                proj.initialize(projectpath)
107-
                self.project_list[name] = proj
83+
                    "info": {"version": proj.version}})
84+
            else:
10885
                self.projects.append({"name": name, "lang": lang, "system": system,
109-
                        "info": data})
110-
            if system == GITLAB:
111-
                if not 'Gitlab' in self.settings.conf:
112-
                    self.settings.conf['Gitlab'] = {}
113-
                proj = GitlabProject(self.settings.conf['Gitlab'], name, lang, data)
114-
                proj.initialize(projectpath)
115-
                self.project_list[name] = proj
116-
                self.projects.append({"name": name, "lang": lang, "system": system,
117-
                        "info": data})
86+
                    "info": data})
11887
        except UnsupportedFormatException:
11988
            rmdir(projectpath)
12089
        self.writeProjects()
12190
91+
    def loadProject(self, name, lang, system, data):
92+
        if system == TRANSLATION_PROJECT:
93+
            if not "TP" in self.settings.conf:
94+
                self.settings.conf["TP"] = {}
95+
            proj = TPProject(self.settings.conf["TP"], name, lang, data)
96+
        if system == TRANSIFEX:
97+
            if not 'Transifex' in self.settings.conf:
98+
                self.settings.conf['Transifex'] = {}
99+
            proj = TransifexProject(self.settings.conf['Transifex'], name, lang, data)
100+
        if system == GITLAB:
101+
            if not 'Gitlab' in self.settings.conf:
102+
                self.settings.conf['Gitlab'] = {}
103+
            proj = GitlabProject(self.settings.conf['Gitlab'], name, lang, data)
104+
        self.project_list[name] = proj
105+
        return proj
106+
122107
    def update(self):
123108
        for p in self.projects:
124109
            proj = self.project_list[p['name']]

143128
144129
    def getConf(self):
145130
        return self.settings.conf
131+
132+
    def remove(self, name):
133+
        rmdir(self.basedir + '/' + name)
134+
        self.projects = [x for x in self.projects if x['name'] != name]
135+
        self.writeProjects()
136+
137+
    def updateProject(self, name, lang, system, info):
138+
        rmdir(self.basedir + '/' + name)
139+
        self.projects = [x for x in self.projects if x['name'] != name]
140+
        self.createProject(name, lang, system, info)

offlate/multiplelineedit.py unknown status 2

1-
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2-
#
3-
#   This program is free software: you can redistribute it and/or modify
4-
#   it under the terms of the GNU Affero General Public License as
5-
#   published by the Free Software Foundation, either version 3 of the
6-
#   License, or (at your option) any later version.
7-
#
8-
#   This program is distributed in the hope that it will be useful,
9-
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10-
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11-
#   GNU Affero General Public License for more details.
12-
#
13-
#   You should have received a copy of the GNU Affero General Public License
14-
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15-
####
16-
17-
from PyQt5.QtWidgets import *
18-
from PyQt5.QtGui import *
19-
from PyQt5.QtCore import *
20-
21-
class MultipleLineEdit(QWidget):
22-
    textChanged = pyqtSignal()
23-
24-
    def __init__(self, parent = None):
25-
        super(MultipleLineEdit, self).__init__(parent)
26-
        self.initUI()
27-
28-
    def setText(self, texts):
29-
        self.treeWidget.clear()
30-
        items = []
31-
        for text in texts:
32-
            item = QTreeWidgetItem([text])
33-
            items.append(item)
34-
        self.treeWidget.insertTopLevelItems(0, items)
35-
36-
    def addLine(self, args):
37-
        text = self.newtext.text()
38-
        items = [QTreeWidgetItem([text])]
39-
        self.treeWidget.insertTopLevelItems(0, items)
40-
41-
    def deleteLine(self, args):
42-
        self.treeWidget.takeTopLevelItem(self.treeWidget.currentIndex().row())
43-
44-
    def content(self):
45-
        number = self.treeWidget.topLevelItemCount()
46-
        items = []
47-
        for i in range(0, number):
48-
            items.append(self.treeWidget.topLevelItem(i).text(0))
49-
        return items
50-
51-
    def initUI(self):
52-
        vbox = QVBoxLayout()
53-
        hbox = QHBoxLayout()
54-
        self.setLayout(vbox)
55-
        self.treeWidget = QTreeWidget()
56-
        self.treeWidget.setColumnCount(1)
57-
        vbox.addWidget(self.treeWidget)
58-
        self.newtext = QLineEdit()
59-
        addbutton = QPushButton(self.tr("Add"))
60-
        addbutton.clicked.connect(self.addLine)
61-
        removebutton = QPushButton(self.tr("Remove"))
62-
        removebutton.clicked.connect(self.deleteLine, 0)
63-
        hbox.addWidget(self.newtext)
64-
        hbox.addWidget(addbutton)
65-
        hbox.addWidget(removebutton)
66-
        vbox.addLayout(hbox)

offlate/spellcheckedit.py unknown status 2

1-
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2-
#
3-
#   This program is free software: you can redistribute it and/or modify
4-
#   it under the terms of the GNU Affero General Public License as
5-
#   published by the Free Software Foundation, either version 3 of the
6-
#   License, or (at your option) any later version.
7-
#
8-
#   This program is distributed in the hope that it will be useful,
9-
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10-
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11-
#   GNU Affero General Public License for more details.
12-
#
13-
#   You should have received a copy of the GNU Affero General Public License
14-
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15-
####
16-
17-
from PyQt5.QtWidgets import *
18-
from PyQt5.QtGui import *
19-
from PyQt5.QtCore import *
20-
21-
import enchant
22-
import re
23-
import sys
24-
25-
class SpellCheckEdit(QTextEdit):
26-
    def __init__(self, lang, *args):
27-
        QTextEdit.__init__(self, *args)
28-
        self.dict = enchant.Dict(lang)
29-
        self.highlighter = Highlighter(self.document())
30-
        self.highlighter.setDict(self.dict)
31-
32-
    def contextMenuEvent(self, event):
33-
        popup_menu = self.createStandardContextMenu()
34-
35-
        # Select the word under the cursor.
36-
        cursor = self.textCursor()
37-
        cursor.select(QTextCursor.WordUnderCursor)
38-
        self.setTextCursor(cursor)
39-
40-
        if self.textCursor().hasSelection():
41-
            text = self.textCursor().selectedText()
42-
            if not self.dict.check(text):
43-
                spell_menu = QMenu(self.tr('Spelling Suggestions'))
44-
                nospell = QAction(self.tr('No Suggestions'))
45-
                nospell.setEnabled(False)
46-
                for word in self.dict.suggest(text):
47-
                    action = QAction(word)
48-
                    action.triggered.connect((lambda word: (lambda : self.correctWord(word)))(word))
49-
                    spell_menu.addAction(action)
50-
                # If there are suggestions, use the spell_menu. Otherwise, show
51-
                # there is no suggestion.
52-
                popup_menu.insertSeparator(popup_menu.actions()[0])
53-
                if len(spell_menu.actions()) != 0:
54-
                    popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
55-
                else:
56-
                    popup_menu.insertAction(popup_menu.actions()[0], nospell)
57-
58-
        popup_menu.exec_(event.globalPos())
59-
60-
    def correctWord(self, word):
61-
        cursor = self.textCursor()
62-
        cursor.beginEditBlock()
63-
64-
        cursor.removeSelectedText()
65-
        cursor.insertText(word)
66-
67-
        cursor.endEditBlock()
68-
69-
class Highlighter(QSyntaxHighlighter):
70-
    def __init__(self, *args):
71-
        QSyntaxHighlighter.__init__(self, *args)
72-
73-
    def setDict(self, dico):
74-
        self.dict = dico
75-
76-
    def highlightBlock(self, text):
77-
        if self.dict == None:
78-
            return
79-
80-
        format = QTextCharFormat()
81-
        format.setUnderlineColor(Qt.red)
82-
        format.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
83-
84-
        for word_object in re.finditer(r'\b[^\W\d_]+\b', text):
85-
            if not self.dict.check(word_object.group()):
86-
                self.setFormat(word_object.start(), word_object.end() - word_object.start(), format)
87-
88-
89-
if __name__ == '__main__':
90-
    app = QApplication(sys.argv)
91-
    w = SpellCheckEdit()
92-
    w.show()
93-
    sys.exit(app.exec_())

offlate/tagclickedit.py unknown status 2

1-
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2-
#
3-
#   This program is free software: you can redistribute it and/or modify
4-
#   it under the terms of the GNU Affero General Public License as
5-
#   published by the Free Software Foundation, either version 3 of the
6-
#   License, or (at your option) any later version.
7-
#
8-
#   This program is distributed in the hope that it will be useful,
9-
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10-
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11-
#   GNU Affero General Public License for more details.
12-
#
13-
#   You should have received a copy of the GNU Affero General Public License
14-
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15-
####
16-
17-
from PyQt5.QtWidgets import *
18-
from PyQt5.QtGui import *
19-
from PyQt5.QtCore import *
20-
21-
import re
22-
import sys
23-
from urllib.parse import quote
24-
import html
25-
26-
class TagClickEdit(QTextBrowser):
27-
    def __init__(self, *args):
28-
        QTextBrowser.__init__(self, *args)
29-
30-
    def createLinks(self):
31-
        text = self.toHtml()
32-
        for word_object in re.finditer(r'@[a-z]+{[^}]*}', text):
33-
            rep = word_object.string[word_object.span()[0] : word_object.span()[1]]
34-
            text = text.replace(rep, '<a href="#' + quote(html.unescape(rep)) + '">' + rep + '</a>')
35-
        self.setHtml(text)
36-
37-
if __name__ == '__main__':
38-
    app = QApplication(sys.argv)
39-
    w = TagClickEdit()
40-
    w.setText("GNU@tie{}Hello provides the @command{hello} command.")
41-
    w.createLinks()
42-
    w.show()
43-
    sys.exit(app.exec_())

offlate/ui/about.py unknown status 1

1+
#   Copyright (c) 2019 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
import os
18+
import webbrowser
19+
20+
from PyQt5.QtWidgets import *
21+
from PyQt5.QtGui import *
22+
from PyQt5.QtCore import *
23+
24+
class AboutWindow(QDialog):
25+
    def __init__(self, parent = None):
26+
        super().__init__(parent)
27+
        self.initUI()
28+
29+
    def initUI(self):
30+
        filename = os.path.dirname(__file__) + '/../icon.png'
31+
        icon = QPixmap(filename)
32+
        label = QLabel(self)
33+
        label.setPixmap(icon)
34+
        label.setAlignment(Qt.AlignCenter)
35+
        name = QLabel(self)
36+
        name.setText("Offlate")
37+
        font = name.font()
38+
        font.setPointSize(64)
39+
        font.setBold(True)
40+
        name.setFont(font)
41+
42+
        explain = QLabel(self)
43+
        explain.setText(self.tr("Offlate is a translation interface \
44+
for offline translation of projects using online platforms. Offlate is free \
45+
software, you can redistribute it under the GPL v3 license or any later version."))
46+
        explain.setWordWrap(True)
47+
48+
        copyright = QLabel(self)
49+
        copyright.setText(self.tr("Copyright ?? 2018, 2019 Julien Lepiller"))
50+
51+
        issue_button = QPushButton(self.tr("Report an issue"))
52+
        ok_button = QPushButton(self.tr("Close this window"))
53+
54+
        vbox = QVBoxLayout()
55+
        vbox.addWidget(label)
56+
        vbox.addWidget(name)
57+
        vbox.addWidget(explain)
58+
        vbox.addWidget(copyright)
59+
        vbox.addWidget(issue_button)
60+
        vbox.addWidget(ok_button)
61+
62+
        self.setGeometry(10, 10, 300, 200)
63+
        self.setLayout(vbox)
64+
65+
        # Actions
66+
        ok_button.clicked.connect(self.close)
67+
        issue_button.clicked.connect(self.issue)
68+
69+
    def issue(self):
70+
        webbrowser.open('https://framagit.org/tyreunom/offlate/issues')

offlate/ui/editor.py unknown status 1

1+
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
from PyQt5.QtWidgets import *
18+
from PyQt5.QtGui import *
19+
from PyQt5.QtCore import *
20+
21+
from .spellcheckedit import SpellCheckEdit
22+
23+
import math
24+
25+
class ProjectTab(QTabWidget):
26+
    def __init__(self, parent = None):
27+
        super(ProjectTab, self).__init__(parent)
28+
29+
class Interface:
30+
    def __init__(self):
31+
        self.value = None
32+
33+
    def ok(self):
34+
        self.value = self.qd.textValue()
35+
36+
    def askPassword(self):
37+
        self.qd = QInputDialog()
38+
        self.qd.setLabelText(self.qd.tr("Please enter your password:"))
39+
        self.qd.setTextEchoMode(QLineEdit.Password)
40+
        self.qd.accepted.connect(self.ok)
41+
        self.qd.exec_()
42+
        return self.value
43+
44+
class ProjectView(QWidget):
45+
    translationModified = pyqtSignal()
46+
47+
    def __init__(self, project, showTranslated = True, showUntranslated = True,
48+
                showFuzzy = True, monospace = False, parent = None):
49+
        super(ProjectView, self).__init__(parent)
50+
        self.project = project
51+
        self.content = self.project.content()
52+
        self.currentContent = list(self.content.keys())[0]
53+
        self.showTranslated = showTranslated
54+
        self.showUntranslated = showUntranslated
55+
        self.showFuzzy = showFuzzy
56+
        self.monospace = monospace
57+
        self.fuzzyColor = QBrush(QColor(255, 127, 80))
58+
        self.emptyColor = QBrush(QColor(255, 240, 235))
59+
        self.initUI()
60+
61+
    def updateContent(self):
62+
        self.treeWidget.clear()
63+
        items = []
64+
        for entry in self.content[self.currentContent]:
65+
            if entry.isObsolete():
66+
                continue
67+
            cont = False
68+
            if self.showTranslated and entry.isTranslated():
69+
                cont = True
70+
            if self.showUntranslated and not entry.isTranslated():
71+
                cont = True
72+
            if self.showFuzzy and entry.isFuzzy():
73+
                cont = True
74+
            if not cont:
75+
                continue
76+
            item = QTreeWidgetItem([entry.msgids[0].replace('\n', ' '),
77+
                                    entry.msgstrs[0].replace('\n', ' ')])
78+
            if entry.isFuzzy():
79+
                item.setForeground(1, self.fuzzyColor)
80+
            if not entry.isTranslated():
81+
                item.setBackground(1, self.emptyColor)
82+
            item.setFont(0, QFont("sans-serif", 10))
83+
            item.setFont(1, QFont("sans-serif", 10))
84+
            item.setSizeHint(0, QSize(-1, 22))
85+
            item.setData(0, Qt.UserRole, entry)
86+
            items.append(item)
87+
        self.treeWidget.insertTopLevelItems(0, items)
88+
        self.translationModified.emit()
89+
90+
    def initUI(self):
91+
        vbox = QVBoxLayout()
92+
        self.setLayout(vbox)
93+
        model = QStandardItemModel()
94+
        self.treeWidget = QTreeWidget()
95+
        self.treeWidget.setColumnCount(2)
96+
        self.msgid = QTextEdit()
97+
        self.msgid.setReadOnly(True)
98+
        self.msgstr = SpellCheckEdit(self.project.lang)
99+
        self.filechooser = QComboBox()
100+
        for project in list(self.content.keys()):
101+
            self.filechooser.addItem(project)
102+
        self.filechooser.currentIndexChanged.connect(self.changefile)
103+
104+
        self.buttons = QVBoxLayout()
105+
        self.copyButton = QPushButton(self.tr("Copy"))
106+
        self.copyButton.clicked.connect(self.copy)
107+
        self.buttons.addWidget(self.copyButton)
108+
109+
        if self.filechooser.count() > 1:
110+
            vbox.addWidget(self.filechooser)
111+
112+
        self.updateContent()
113+
        vbox.addWidget(self.treeWidget, 4)
114+
        self.hbox = QHBoxLayout()
115+
        self.hbox.addWidget(self.msgid)
116+
        self.hbox.addLayout(self.buttons)
117+
        self.hbox.addWidget(self.msgstr)
118+
        vbox.addLayout(self.hbox, 1)
119+
        size = self.treeWidget.size()
120+
        self.treeWidget.setColumnWidth(0, size.width()/2)
121+
        self.treeWidget.currentItemChanged.connect(self.selectItem)
122+
123+
    def changefile(self):
124+
        self.currentContent = list(self.content.keys())[self.filechooser.currentIndex()]
125+
        self.updateContent()
126+
127+
    def nextItem(self):
128+
        index = self.treeWidget.currentIndex()
129+
        nextItem = self.treeWidget.itemFromIndex(self.treeWidget.indexBelow(index))
130+
        self.treeWidget.setCurrentItem(nextItem)
131+
132+
    def previousItem(self):
133+
        index = self.treeWidget.currentIndex()
134+
        nextItem = self.treeWidget.itemFromIndex(self.treeWidget.indexAbove(index))
135+
        self.treeWidget.setCurrentItem(nextItem)
136+
137+
    def copy(self):
138+
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
139+
            text = self.msgid.toPlainText()
140+
            self.msgstr.setText(text)
141+
        else:
142+
            text = self.msgid.currentWidget().toPlainText()
143+
            self.msgstr.currentWidget().setText(text)
144+
145+
    def copyTag(self, tag):
146+
        tag = tag.toDisplayString()[1:]
147+
        tag = unquote(tag)
148+
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
149+
            self.msgstr.insertPlainText(tag)
150+
            self.msgstr.setFocus(True)
151+
        else:
152+
            self.msgstr.currentWidget.insertPlainText(tag)
153+
            self.msgstr.currentWidget.setFocus(True)
154+
155+
    def selectItem(self, current, old):
156+
        if current == None:
157+
            return
158+
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
159+
            self.msgstr.clearFocus()
160+
        else:
161+
            self.msgstr.currentWidget.clearFocus()
162+
        data = current.data(0, Qt.UserRole)
163+
        self.hbox.removeWidget(self.msgid)
164+
        self.hbox.removeItem(self.buttons)
165+
        self.hbox.removeWidget(self.msgstr)
166+
        self.msgid.deleteLater()
167+
        self.msgstr.deleteLater()
168+
169+
        font = "monospace" if self.monospace else "sans-serif"
170+
        focuser = None
171+
172+
        if len(data.msgstrs) > 1:
173+
            self.msgid = QTabWidget();
174+
            self.msgstr = QTabWidget();
175+
            singular = TagClickEdit()
176+
            singular.setFont(QFont(font))
177+
            singular.setReadOnly(True)
178+
            singular.setText(data.msgids[0])
179+
            singular.createLinks()
180+
            singular.anchorClicked.connect(self.copyTag)
181+
            plural = TagClickEdit()
182+
            plural.setFont(QFont(font))
183+
            plural.setReadOnly(True)
184+
            plural.setText(data.msgids[1])
185+
            plural.createLinks()
186+
            plural.anchorClicked.connect(self.copyTag)
187+
            self.msgid.addTab(singular, self.tr("Singular"))
188+
            self.msgid.addTab(plural, self.tr("Plural"))
189+
            i = 0
190+
            for msgstr in data.msgstrs:
191+
                form = SpellCheckEdit(self.project.lang)
192+
                form.setFont(QFont(font))
193+
                form.setText(msgstr)
194+
                form.textChanged.connect(self.modify)
195+
                self.msgstr.addTab(form, str(i))
196+
                if i == 0:
197+
                    focuser = form
198+
                i=i+1
199+
        else:
200+
            self.msgid = TagClickEdit()
201+
            self.msgid.setFont(QFont(font))
202+
            self.msgid.setReadOnly(True)
203+
            self.msgid.setText(data.msgids[0])
204+
            self.msgid.createLinks()
205+
            self.msgid.anchorClicked.connect(self.copyTag)
206+
            self.msgstr = SpellCheckEdit(self.project.lang)
207+
            self.msgstr.setFont(QFont(font))
208+
            self.msgstr.setText(data.msgstrs[0])
209+
            self.msgstr.textChanged.connect(self.modify)
210+
            focuser = self.msgstr
211+
        self.hbox.addWidget(self.msgid)
212+
        self.hbox.addLayout(self.buttons)
213+
        self.hbox.addWidget(self.msgstr)
214+
        focuser.setFocus()
215+
216+
    def modify(self):
217+
        item = self.treeWidget.currentItem()
218+
        data = item.data(0, Qt.UserRole)
219+
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
220+
            msgstr = self.msgstr.toPlainText()
221+
            data.update(0, msgstr)
222+
            item.setText(1, msgstr.replace('\n', ' '))
223+
        else:
224+
            i = 0
225+
            for msgstr in data.msgstrs:
226+
                data.update(i, self.msgstr.widget(i).toPlainText())
227+
                i=i+1
228+
            item.setText(1, data.get(0).replace('\n', ' '))
229+
        item.setForeground(1, QBrush())
230+
        if data.isTranslated():
231+
            item.setBackground(1, QBrush())
232+
        else:
233+
            item.setBackground(1, self.emptyColor)
234+
        self.translationModified.emit()
235+
236+
237+
    def save(self):
238+
        self.project.save()
239+
240+
    def send(self):
241+
        self.project.save()
242+
        self.project.send(Interface())
243+
244+
    def askmerge(self, msgid, oldstr, newstr):
245+
        # TODO: Actually do something more intelligent
246+
        return newstr
247+
248+
    def update(self):
249+
        self.project.save()
250+
        self.project.update(self.askmerge)
251+
        self.content = self.project.content()
252+
        self.updateContent()
253+
254+
    def filter(self, showTranslated, showUntranslated, showFuzzy):
255+
        self.showTranslated = showTranslated
256+
        self.showUntranslated = showUntranslated
257+
        self.showFuzzy = showFuzzy
258+
        self.updateContent()
259+
260+
    def setFont(self, monospace):
261+
        self.monospace = monospace
262+
        current = self.treeWidget.currentItem()
263+
        self.selectItem(current, current)
264+
265+
class EditorWindow(QMainWindow):
266+
    def __init__(self, projectManagerWindow, manager):
267+
        super().__init__()
268+
        self.manager = manager
269+
        self.projectManagerWindow = projectManagerWindow
270+
        self.initUI()
271+
272+
    def initOpenProjects(self, menu):
273+
        l = self.manager.listProjects()
274+
        for p in l:
275+
            name = p['name']
276+
            act = QAction(name, self) 
277+
            act.triggered.connect((lambda name: (lambda : self.open(name)))(name))
278+
            menu.addAction(act)
279+
280+
    def open(self, name):
281+
        try:
282+
            project = self.manager.getProject(name)
283+
        except Exception:
284+
            self.qd = QErrorMessage()
285+
            self.qd.showMessage(self.tr("Unsupported / Unknown project"))
286+
            self.qd.exec_()
287+
            return
288+
        tab = ProjectView(project,
289+
            showTranslated = self.showTranslatedAct.isChecked(),
290+
            showUntranslated = self.showUntranslatedAct.isChecked(),
291+
            showFuzzy = self.showFuzzyAct.isChecked(),
292+
            monospace = self.monospaceAct.isChecked())
293+
        tab.translationModified.connect(self.count)
294+
        self.tabs.addTab(tab, name)
295+
        self.count()
296+
297+
    def count(self, item = -1):
298+
        widget = self.tabs.currentWidget()
299+
        content = widget.content[widget.currentContent]
300+
        total = 0
301+
        translated = 0
302+
        for d in content:
303+
            total += 1
304+
            if d.isTranslated() and not d.isFuzzy():
305+
                translated += 1
306+
        percent = 100 if total == 0 else math.floor(1000 * translated / total)/10
307+
        self.countLabel.setText(self.tr("{} translated on {} total ({}%).").format(translated, total, percent))
308+
309+
    def save(self):
310+
        self.tabs.currentWidget().save()
311+
312+
    def manage(self):
313+
        self.projectManagerWindow.show()
314+
315+
    def new(self):
316+
        w = NewWindow(self.manager)
317+
        w.exec_()
318+
        if not w.wantNew():
319+
            return
320+
        self.manager.createProject(w.getProjectName(), w.getProjectLang(),
321+
                    w.getProjectSystem(), w.getProjectInfo())
322+
        self.open(w.getProjectName())
323+
324+
    def send(self):
325+
        self.tabs.currentWidget().send()
326+
327+
    def update(self):
328+
        self.tabs.currentWidget().update()
329+
        self.manager.update()
330+
        self.manager.writeProjects()
331+
332+
    def closeProject(self):
333+
        self.tabs.removeTab(self.tabs.currentIndex())
334+
335+
    def settings(self):
336+
        w = SettingsWindow(self.manager.getConf())
337+
        w.exec_()
338+
        if w.done:
339+
            self.manager.updateSettings(w.data)
340+
341+
    def filter(self):
342+
        for i in range(0, self.tabs.count()):
343+
            self.tabs.widget(i).filter(
344+
                self.showTranslatedAct.isChecked(),
345+
                self.showUntranslatedAct.isChecked(),
346+
                self.showFuzzyAct.isChecked())
347+
348+
    def setFont(self):
349+
        for i in range(0, self.tabs.count()):
350+
            self.tabs.widget(i).setFont(self.monospaceAct.isChecked())
351+
352+
    def previousItem(self):
353+
        self.tabs.currentWidget().previousItem()
354+
355+
    def nextItem(self):
356+
        self.tabs.currentWidget().nextItem()
357+
358+
    def initUI(self):
359+
        # Build menu
360+
        exitAct = QAction(QIcon('exit.png'), self.tr('Exit'), self)
361+
        exitAct.setShortcut('Ctrl+Q')
362+
        exitAct.setStatusTip(self.tr('Exit application'))
363+
        exitAct.triggered.connect(qApp.quit)
364+
365+
        saveAct = QAction(QIcon('save.png'), self.tr('Save'), self)
366+
        saveAct.setShortcut('Ctrl+S')
367+
        saveAct.setStatusTip(self.tr('Save current project'))
368+
        saveAct.triggered.connect(self.save)
369+
370+
        newAct = QAction(QIcon('new.png'), self.tr('New'), self)
371+
        newAct.setShortcut('Ctrl+N')
372+
        newAct.setStatusTip(self.tr('New project'))
373+
        newAct.triggered.connect(self.new)
374+
375+
        manageAct = QAction(QIcon('settings.png'),
376+
                self.tr('Manage Projects'), self)
377+
        manageAct.setShortcut('Ctrl+M')
378+
        manageAct.setStatusTip(self.tr('Open project manager'))
379+
        manageAct.triggered.connect(self.manage)
380+
381+
        updateAct = QAction(QIcon('download.png'), self.tr('Update'), self)
382+
        updateAct.setShortcut('Ctrl+U')
383+
        updateAct.setStatusTip(self.tr('Get modifications from upstream'))
384+
        updateAct.triggered.connect(self.update)
385+
386+
        sendAct = QAction(QIcon('close.png'), self.tr('Close'), self)
387+
        sendAct.setStatusTip(self.tr('Close current project'))
388+
        sendAct.triggered.connect(self.closeProject)
389+
390+
        closeAct = QAction(QIcon('upload.png'), self.tr('Send'), self)
391+
        closeAct.setShortcut('Ctrl+E')
392+
        closeAct.setStatusTip(self.tr('Send modifications upstream'))
393+
        closeAct.triggered.connect(self.send)
394+
395+
        settingsAct = QAction(QIcon('settings.png'), self.tr('Settings'), self)
396+
        settingsAct.setShortcut('Ctrl+P')
397+
        settingsAct.setStatusTip(self.tr('Set parameters'))
398+
        settingsAct.triggered.connect(self.settings)
399+
400+
        self.showTranslatedAct = QAction(self.tr('Show Translated'), self, checkable=True)
401+
        self.showTranslatedAct.setChecked(True)
402+
        self.showTranslatedAct.triggered.connect(self.filter)
403+
        self.showFuzzyAct = QAction(self.tr('Show Fuzzy'), self, checkable=True)
404+
        self.showFuzzyAct.setChecked(True)
405+
        self.showFuzzyAct.triggered.connect(self.filter)
406+
        self.showUntranslatedAct = QAction(self.tr('Show Empty Translation'), self, checkable=True)
407+
        self.showUntranslatedAct.setChecked(True)
408+
        self.showUntranslatedAct.triggered.connect(self.filter)
409+
        self.monospaceAct = QAction(self.tr('Use a monospace font'), self, checkable=True)
410+
        self.monospaceAct.setChecked(False)
411+
        self.monospaceAct.triggered.connect(self.setFont)
412+
413+
        self.previousShortcut = QShortcut(QKeySequence("Ctrl+Up"), self)
414+
        self.previousShortcut.activated.connect(self.previousItem)
415+
416+
        self.previous2Shortcut = QShortcut(QKeySequence("Ctrl+Shift+Return"), self)
417+
        self.previous2Shortcut.activated.connect(self.previousItem)
418+
419+
        self.nextShortcut = QShortcut(QKeySequence("Ctrl+Down"), self)
420+
        self.nextShortcut.activated.connect(self.nextItem)
421+
422+
        self.next2Shortcut = QShortcut(QKeySequence("Ctrl+Return"), self)
423+
        self.next2Shortcut.activated.connect(self.nextItem)
424+
425+
        self.countLabel = QLabel()
426+
        self.statusBar()
427+
        self.statusBar().addWidget(self.countLabel)
428+
429+
        openMenu = QMenu(self.tr('Open'), self)
430+
        self.initOpenProjects(openMenu)
431+
432+
        menubar = self.menuBar()
433+
        fileMenu = menubar.addMenu(self.tr('&File'))
434+
        fileMenu.addAction(newAct)
435+
        fileMenu.addMenu(openMenu)
436+
        fileMenu.addSeparator()
437+
        fileMenu.addAction(manageAct)
438+
        fileMenu.addSeparator()
439+
        fileMenu.addAction(exitAct)
440+
441+
        projectMenu = menubar.addMenu(self.tr('&Project'))
442+
        projectMenu.addAction(updateAct)
443+
        projectMenu.addAction(saveAct)
444+
        projectMenu.addAction(sendAct)
445+
        projectMenu.addSeparator()
446+
        projectMenu.addAction(closeAct)
447+
448+
        editMenu = menubar.addMenu(self.tr('&Edit'))
449+
        editMenu.addAction(settingsAct)
450+
451+
        viewMenu = menubar.addMenu(self.tr('&View'))
452+
        viewMenu.addAction(self.showTranslatedAct)
453+
        viewMenu.addAction(self.showUntranslatedAct)
454+
        viewMenu.addAction(self.showFuzzyAct)
455+
        viewMenu.addSeparator()
456+
        viewMenu.addAction(self.monospaceAct)
457+
458+
        self.tabs = ProjectTab()
459+
        self.tabs.currentChanged.connect(self.count)
460+
461+
        self.setCentralWidget(self.tabs)
462+
463+
        self.setGeometry(0, 0, 800, 600)
464+
        self.setWindowTitle('Offlate')

offlate/ui/main.py unknown status 1

1+
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
from PyQt5.QtWidgets import *
18+
from PyQt5.QtGui import *
19+
from PyQt5.QtCore import *
20+
21+
from .manager import ProjectManagerWindow
22+
23+
import sys
24+
import os
25+
26+
def main():
27+
    app = QApplication(sys.argv)
28+
    translator = QTranslator()
29+
    if translator.load(QLocale(), "offlate", "_",
30+
            os.path.dirname(os.path.realpath(__file__))+"/locales"):
31+
        app.installTranslator(translator);
32+
33+
    w = ProjectManagerWindow.getInstance()
34+
    w.show()
35+
36+
    sys.exit(app.exec_())

offlate/ui/manager.py unknown status 1

1+
#   Copyright (c) 2019 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
import os
18+
import re
19+
import sys
20+
21+
from PyQt5.QtWidgets import *
22+
from PyQt5.QtGui import *
23+
from PyQt5.QtCore import *
24+
25+
from .about import AboutWindow
26+
from .new import NewWindow
27+
from .settings import SettingsWindow
28+
from .editor import EditorWindow
29+
30+
from ..manager import ProjectManager
31+
32+
class ModalWait(QDialog):
33+
    def __init__(self, parent = None):
34+
        super().__init__(parent)
35+
        box = QVBoxLayout()
36+
        txt = QLabel()
37+
        txt.setText(self.tr("Please wait???"))
38+
        box.addWidget(txt)
39+
        self.setLayout(box)
40+
        self.setModal(True)
41+
42+
class ProjectManagerWindow(QMainWindow):
43+
    _instance = None
44+
45+
    @staticmethod
46+
    def getInstance():
47+
        if ProjectManagerWindow._instance == None:
48+
            ProjectManagerWindow._instance = ProjectManagerWindow()
49+
        return ProjectManagerWindow._instance
50+
51+
    def __init__(self):
52+
        super().__init__()
53+
        self.initUI()
54+
        _instance = self
55+
56+
    def initUI(self):
57+
        center = QDesktopWidget().availableGeometry().center()
58+
        self.setGeometry(center.x()-400, center.y()-300, 800, 600)
59+
        self.setWindowTitle(self.tr('Offlate Project Manager'))
60+
        self.setCentralWidget(ProjectManagerWidget(self))
61+
62+
class ProjectManagerWidget(QWidget):
63+
    def __init__(self, parent=None):
64+
        super().__init__(parent)
65+
        self.manager = ProjectManager()
66+
        self.editor = EditorWindow(parent, self.manager)
67+
        self.threadpool = QThreadPool()
68+
        self.initUI()
69+
70+
    def initUI(self):
71+
        # Manager Window UI: left is project list with options, right
72+
        # is a set of actions
73+
74+
        hbox = QHBoxLayout()
75+
        project_vbox = QVBoxLayout()
76+
        self.searchfield = QLineEdit()
77+
        project_vbox.addWidget(self.searchfield)
78+
        self.projectlist = QListWidget()
79+
        for p in self.manager.listProjects():
80+
            item = QListWidgetItem(p['name'])
81+
            item.setData(Qt.UserRole, p)
82+
            self.projectlist.addItem(item)
83+
        project_vbox.addWidget(self.projectlist)
84+
85+
        buttonbox = QHBoxLayout()
86+
        self.open_button = QPushButton(self.tr("Open"))
87+
        self.edit_button = QPushButton(self.tr("Edit"))
88+
        self.remove_button = QPushButton(self.tr("Remove"))
89+
        buttonbox.addWidget(self.open_button)
90+
        buttonbox.addWidget(self.edit_button)
91+
        buttonbox.addWidget(self.remove_button)
92+
93+
        project_vbox.addLayout(buttonbox)
94+
        hbox.addLayout(project_vbox, 1)
95+
96+
        new_button = QPushButton(self.tr("New Project"))
97+
        settings_button = QPushButton(self.tr("Settings"))
98+
        about_button = QPushButton(self.tr("About Offlate"))
99+
        quit_button = QPushButton(self.tr("Exit"))
100+
101+
        filename = os.path.dirname(__file__) + '/../icon.png'
102+
        icon = QPixmap(filename)
103+
        iconlabel = QLabel(self)
104+
        iconlabel.setPixmap(icon)
105+
        iconlabel.setAlignment(Qt.AlignCenter)
106+
107+
        global_vbox = QVBoxLayout()
108+
        global_vbox.addSpacing(28)
109+
        global_vbox.addWidget(new_button)
110+
        global_vbox.addWidget(settings_button)
111+
        global_vbox.addWidget(about_button)
112+
        global_vbox.addStretch(1)
113+
        global_vbox.addWidget(iconlabel)
114+
        global_vbox.addStretch(1)
115+
        global_vbox.addWidget(quit_button)
116+
117+
        hbox.addLayout(global_vbox, 1)
118+
119+
        self.setLayout(hbox)
120+
121+
        # Actions
122+
        self.searchfield.textChanged.connect(self.filter)
123+
124+
        quit_button.clicked.connect(qApp.quit)
125+
        new_button.clicked.connect(self.new)
126+
        settings_button.clicked.connect(self.settings)
127+
        about_button.clicked.connect(self.about)
128+
        self.projectlist.currentItemChanged.connect(self.activate)
129+
        self.remove_button.clicked.connect(self.remove)
130+
        self.edit_button.clicked.connect(self.edit)
131+
        self.open_button.clicked.connect(self.open)
132+
133+
        # Defaults
134+
        self.edit_button.setEnabled(False)
135+
        self.open_button.setEnabled(False)
136+
        self.remove_button.setEnabled(False)
137+
138+
    def activate(self):
139+
        self.edit_button.setEnabled(True)
140+
        self.open_button.setEnabled(True)
141+
        self.remove_button.setEnabled(True)
142+
143+
    def about(self):
144+
        geometry = self.parent().geometry()
145+
        w = AboutWindow()
146+
        w.setGeometry(geometry.x() + geometry.width()/2 - 300,
147+
                geometry.y() + geometry.height()/2 - 200,
148+
                300, 200)
149+
        w.exec_()
150+
151+
    def new(self):
152+
        w = NewWindow(self.manager)
153+
        w.exec_()
154+
        if not w.wantNew():
155+
            return
156+
        worker = NewRunnable(self, w.getProjectName(), w.getProjectLang(),
157+
                w.getProjectSystem(), w.getProjectInfo())
158+
        self.threadpool.start(worker)
159+
160+
    def settings(self):
161+
        w = SettingsWindow(self.manager.getConf())
162+
        w.exec_()
163+
        if w.done:
164+
            self.manager.updateSettings(w.data)
165+
166+
    def filter(self):
167+
        search = self.searchfield.text()
168+
        self.projectlist.clear()
169+
        regexp = re.compile(".*"+search)
170+
        for p in self.manager.listProjects():
171+
            if regexp.match(p['name']):
172+
                item = QListWidgetItem(p['name'])
173+
                item.setData(Qt.UserRole, p)
174+
                self.projectlist.addItem(item)
175+
176+
    def open(self):
177+
        item = self.projectlist.currentItem()
178+
        data = item.data(Qt.UserRole)
179+
        name = data['name']
180+
        self.openProject(name)
181+
182+
    def openProject(self, name):
183+
        self.editor.show()
184+
        self.parent().hide()
185+
        self.editor.open(name)
186+
187+
    def remove(self):
188+
        item = self.projectlist.currentItem()
189+
        data = item.data(Qt.UserRole)
190+
        name = data['name']
191+
        self.manager.remove(name)
192+
        self.filter()
193+
194+
    def edit(self):
195+
        item = self.projectlist.currentItem()
196+
        data = item.data(Qt.UserRole)
197+
        name = data['name']
198+
199+
        w = NewWindow(self.manager, name=name, lang=data['lang'],
200+
                system=data['system'], info=data['info'])
201+
        w.exec_()
202+
        if not w.wantNew():
203+
            return
204+
        worker = EditRunnable(self, w.getProjectName(), w.getProjectLang(),
205+
                w.getProjectSystem(), w.getProjectInfo())
206+
        self.threadpool.start(worker)
207+
208+
class NewRunnable(QRunnable):
209+
    def __init__(self, parent, name, lang, system, info):
210+
        super().__init__()
211+
        self.name = name
212+
        self.lang = lang
213+
        self.system = system
214+
        self.info = info
215+
        self.parent = parent
216+
217+
    def run(self):
218+
        self.parent.manager.createProject(self.name, self.lang, self.system,
219+
                self.info)
220+
        self.parent.openProject(self.name)
221+
        self.parent.filter()
222+
223+
class EditRunnable(QRunnable):
224+
    def __init__(self, parent, name, lang, system, info):
225+
        super().__init__()
226+
        self.name = name
227+
        self.lang = lang
228+
        self.system = system
229+
        self.info = info
230+
        self.parent = parent
231+
232+
    def run(self):
233+
        self.parent.manager.updateProject(self.name, self.lang, self.system,
234+
                self.info)
235+
        self.parent.filter()

offlate/ui/multiplelineedit.py unknown status 1

1+
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
from PyQt5.QtWidgets import *
18+
from PyQt5.QtGui import *
19+
from PyQt5.QtCore import *
20+
21+
class MultipleLineEdit(QWidget):
22+
    textChanged = pyqtSignal()
23+
24+
    def __init__(self, parent = None):
25+
        super(MultipleLineEdit, self).__init__(parent)
26+
        self.initUI()
27+
28+
    def setText(self, texts):
29+
        self.treeWidget.clear()
30+
        items = []
31+
        for text in texts:
32+
            item = QTreeWidgetItem([text])
33+
            items.append(item)
34+
        self.treeWidget.insertTopLevelItems(0, items)
35+
36+
    def addLine(self, args):
37+
        text = self.newtext.text()
38+
        items = [QTreeWidgetItem([text])]
39+
        self.treeWidget.insertTopLevelItems(0, items)
40+
41+
    def deleteLine(self, args):
42+
        self.treeWidget.takeTopLevelItem(self.treeWidget.currentIndex().row())
43+
44+
    def content(self):
45+
        number = self.treeWidget.topLevelItemCount()
46+
        items = []
47+
        for i in range(0, number):
48+
            items.append(self.treeWidget.topLevelItem(i).text(0))
49+
        return items
50+
51+
    def initUI(self):
52+
        vbox = QVBoxLayout()
53+
        hbox = QHBoxLayout()
54+
        self.setLayout(vbox)
55+
        self.treeWidget = QTreeWidget()
56+
        self.treeWidget.setColumnCount(1)
57+
        vbox.addWidget(self.treeWidget)
58+
        self.newtext = QLineEdit()
59+
        addbutton = QPushButton(self.tr("Add"))
60+
        addbutton.clicked.connect(self.addLine)
61+
        removebutton = QPushButton(self.tr("Remove"))
62+
        removebutton.clicked.connect(self.deleteLine, 0)
63+
        hbox.addWidget(self.newtext)
64+
        hbox.addWidget(addbutton)
65+
        hbox.addWidget(removebutton)
66+
        vbox.addLayout(hbox)

offlate/ui/new.py unknown status 1

1+
#   Copyright (c) 2019 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
import json
18+
import os
19+
import re
20+
import sys
21+
22+
from PyQt5.QtWidgets import *
23+
from PyQt5.QtGui import *
24+
from PyQt5.QtCore import *
25+
26+
from ..systems.list import *
27+
from ..formats.list import *
28+
from .multiplelineedit import MultipleLineEdit
29+
30+
class NewWindow(QDialog):
31+
    def __init__(self, manager, parent = None, name = "", 
32+
            lang = "", system = 0, info = None):
33+
        super().__init__(parent)
34+
        self.name = name
35+
        self.lang = lang
36+
        self.system = system
37+
        self.info = info
38+
        self.manager = manager
39+
        self.askNew = False
40+
        self.initUI()
41+
42+
    def initUI(self):
43+
        hbox = QHBoxLayout()
44+
        predefinedbox = QVBoxLayout()
45+
        self.searchfield = QLineEdit()
46+
        predefinedbox.addWidget(self.searchfield)
47+
        self.predefinedprojects = QListWidget()
48+
        with open(os.path.dirname(__file__) + '/../data.json') as f:
49+
            self.projectdata = json.load(f)
50+
            for d in self.projectdata:
51+
                item = QListWidgetItem(d['name'])
52+
                item.setData(Qt.UserRole, d)
53+
                self.predefinedprojects.addItem(item)
54+
        predefinedbox.addWidget(self.predefinedprojects)
55+
56+
        contentbox = QVBoxLayout()
57+
        formbox = QGroupBox(self.tr("Project information"))
58+
        self.formLayout = QFormLayout()
59+
        formbox.setLayout(self.formLayout)
60+
61+
        self.nameWidget = QLineEdit()
62+
        self.nameWidget.setText(self.name)
63+
        self.langWidget = QLineEdit()
64+
        self.langWidget.setText(self.lang)
65+
        self.formLayout.addRow(QLabel(self.tr("Name:")), self.nameWidget)
66+
        self.formLayout.addRow(QLabel(self.tr("Target Language:")), self.langWidget)
67+
        self.combo = QComboBox()
68+
        self.combo.addItem(self.tr("The Translation Project"))
69+
        self.combo.addItem(self.tr("Transifex"))
70+
        self.combo.addItem(self.tr("Gitlab"))
71+
        self.combo.setCurrentIndex(self.system)
72+
        self.formLayout.addRow(self.combo)
73+
74+
        self.nameWidget.textChanged.connect(self.modify)
75+
        self.langWidget.textChanged.connect(self.modify)
76+
77+
        hhbox = QHBoxLayout()
78+
        cancel = QPushButton(self.tr("Cancel"))
79+
        self.okbutton = QPushButton(self.tr("OK"))
80+
        self.okbutton.setEnabled(False)
81+
        hhbox.addWidget(cancel)
82+
        hhbox.addWidget(self.okbutton)
83+
        contentbox.addWidget(formbox)
84+
        contentbox.addLayout(hhbox)
85+
        hbox.addLayout(predefinedbox)
86+
        hbox.addLayout(contentbox)
87+
88+
        self.additionalFields = []
89+
        self.additionalFields.append([])
90+
        self.additionalFields.append([])
91+
        self.additionalFields.append([])
92+
        
93+
        # Transifex
94+
        self.transifexOrganisation = QLineEdit()
95+
        if self.system == TRANSIFEX:
96+
            self.transifexOrganisation.setText(self.info['organization'])
97+
        self.transifexOrganisation.textChanged.connect(self.modify)
98+
        transifexOrganisationLabel = QLabel(self.tr("Organization"))
99+
        self.additionalFields[TRANSIFEX].append({'label': transifexOrganisationLabel,
100+
            'widget': self.transifexOrganisation})
101+
102+
        # Gitlab
103+
        self.gitlabSite = QLineEdit()
104+
        self.gitlabSite.textChanged.connect(self.modify)
105+
        gitlabSiteLabel = QLabel(self.tr('hosting website'))
106+
        self.additionalFields[GITLAB].append({'label': gitlabSiteLabel,
107+
            'widget': self.gitlabSite})
108+
        self.gitlabProject = QLineEdit()
109+
        self.gitlabProject.textChanged.connect(self.modify)
110+
        gitlabProjectLabel = QLabel(self.tr('project part'))
111+
        self.additionalFields[GITLAB].append({'label': gitlabProjectLabel,
112+
            'widget': self.gitlabProject})
113+
        self.gitlabFileTypeCombo = QComboBox()
114+
        self.gitlabFileTypeCombo.addItem(format_list[0])
115+
        self.gitlabFileTypeCombo.addItem(format_list[1])
116+
        self.gitlabFileTypeCombo.addItem(format_list[2])
117+
        self.gitlabFileTypeCombo.currentIndexChanged.connect(self.modify)
118+
        gitlabFileTypeLabel = QLabel(self.tr('Translation type'))
119+
        self.additionalFields[GITLAB].append({'label': gitlabFileTypeLabel,
120+
            'widget': self.gitlabFileTypeCombo})
121+
        self.gitlabLocations = MultipleLineEdit()
122+
        self.gitlabLocations.textChanged.connect(self.modify)
123+
        gitlabLocationsLabel = QLabel(self.tr('Locations'))
124+
        self.additionalFields[GITLAB].append({'label': gitlabLocationsLabel,
125+
            'widget': self.gitlabLocations})
126+
        if self.system == GITLAB:
127+
            self.gitlabSite.setText(self.info['site'])
128+
            self.gitlabProject.setText(self.info['project'])
129+
            self.gitlabFileTypeCombo.setCurrentIndex(self.info['format'])
130+
            self.gitlabLocations.setText(self.info['locations'])
131+
132+
        self.setLayout(hbox)
133+
134+
        self.predefinedprojects.currentItemChanged.connect(self.fill)
135+
        cancel.clicked.connect(self.close)
136+
        self.okbutton.clicked.connect(self.ok)
137+
        self.searchfield.textChanged.connect(self.filter)
138+
        self.combo.currentIndexChanged.connect(self.othersystem)
139+
        self.modify()
140+
141+
    def ok(self):
142+
        self.askNew = True
143+
        self.close()
144+
145+
    def fill(self):
146+
        item = self.predefinedprojects.currentItem()
147+
        data = item.data(Qt.UserRole)
148+
        self.nameWidget.setText(data['name'])
149+
        self.combo.setCurrentIndex(int(data['system']))
150+
        if data['system'] == TRANSIFEX:
151+
            self.transifexOrganisation.setText(data['organisation'])
152+
        if data['system'] == GITLAB:
153+
            self.gitlabSite.setText(data['site'])
154+
            self.gitlabProject.setText(data['project'])
155+
            self.gitlabFileTypeCombo.setCurrentIndex(data['format'])
156+
            self.gitlabLocations.setText(data['locations'])
157+
158+
    def filter(self):
159+
        search = self.searchfield.text()
160+
        self.predefinedprojects.clear()
161+
        regexp = re.compile(".*"+search)
162+
        for d in self.projectdata:
163+
            if regexp.match(d['name']):
164+
                item = QListWidgetItem(d['name'])
165+
                item.setData(Qt.UserRole, d)
166+
                self.predefinedprojects.addItem(item)
167+
168+
    def modify(self):
169+
        enable = False
170+
        if self.nameWidget.text() != '' and self.langWidget.text() != '':
171+
            enable = True
172+
            for widget in self.additionalFields[self.combo.currentIndex()]:
173+
                if isinstance(widget['widget'], QLineEdit) and widget['widget'].text() == '':
174+
                    enable = False
175+
                    break
176+
        self.okbutton.setEnabled(enable)
177+
178+
    def wantNew(self):
179+
        return self.askNew
180+
181+
    def getProjectName(self):
182+
        return self.nameWidget.text()
183+
184+
    def getProjectLang(self):
185+
        return self.langWidget.text()
186+
187+
    def getProjectSystem(self):
188+
        return self.combo.currentIndex()
189+
190+
    def getProjectInfo(self):
191+
        if self.getProjectSystem() == TRANSLATION_PROJECT:
192+
            return {}
193+
        if self.getProjectSystem() == TRANSIFEX:
194+
            return {'organization': self.additionalFields[TRANSIFEX][0]['widget'].text()}
195+
        if self.getProjectSystem() == GITLAB:
196+
            return {'site': self.additionalFields[GITLAB][0]['widget'].text(),
197+
                    'project': self.additionalFields[GITLAB][1]['widget'].text(),
198+
                    'format': self.additionalFields[GITLAB][2]['widget'].currentIndex(),
199+
                    'locations': self.additionalFields[GITLAB][3]['widget'].content()}
200+
        return {}
201+
202+
    def othersystem(self):
203+
        for system in self.additionalFields:
204+
            for widget in system:
205+
                self.formLayout.takeRow(widget['widget'])
206+
                widget['widget'].hide()
207+
                widget['label'].hide()
208+
        self.formLayout.invalidate()
209+
        for widget in self.additionalFields[self.combo.currentIndex()]:
210+
            self.formLayout.addRow(widget['label'], widget['widget'])
211+
            widget['widget'].show()
212+
            widget['label'].show()
213+
        self.modify()

offlate/ui/settings.py unknown status 1

1+
#   Copyright (c) 2019 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
from PyQt5.QtWidgets import *
18+
from PyQt5.QtGui import *
19+
from PyQt5.QtCore import *
20+
21+
class SettingsWindow(QDialog):
22+
    def __init__(self, preferences, parent = None):
23+
        super().__init__(parent)
24+
        self.data = preferences
25+
        self.done = False
26+
        self.initUI()
27+
28+
    def initUI(self):
29+
        vbox = QVBoxLayout()
30+
31+
        tab = QTabWidget()
32+
        self.addTPTab(tab)
33+
        self.addTransifexTab(tab)
34+
35+
        buttonbox = QHBoxLayout()
36+
        cancel = QPushButton(self.tr("Cancel"))
37+
        ok = QPushButton(self.tr("OK"))
38+
        buttonbox.addWidget(cancel)
39+
        buttonbox.addWidget(ok)
40+
41+
        vbox.addWidget(tab)
42+
        vbox.addLayout(buttonbox)
43+
        self.setLayout(vbox)
44+
        cancel.clicked.connect(self.close)
45+
        ok.clicked.connect(self.ok)
46+
47+
    def addTransifexTab(self, tab):
48+
        formBox = QGroupBox(self.tr("Transifex"))
49+
        formLayout = QFormLayout()
50+
        self.TransifexToken = QLineEdit()
51+
52+
        if not "Transifex" in self.data:
53+
            self.data["Transifex"] = {}
54+
        try:
55+
            self.TransifexToken.setText(self.data["Transifex"]["token"])
56+
        except Exception:
57+
            pass
58+
59+
        self.TransifexToken.textChanged.connect(self.updateTransifex)
60+
        label = QLabel(self.tr("You can get a token from <a href=\"#\">https://www.transifex.com/user/settings/api/</a>"))
61+
        label.linkActivated.connect(self.openTransifex)
62+
63+
        formLayout.addRow(QLabel(self.tr("Token:")), self.TransifexToken)
64+
        formLayout.addRow(label)
65+
66+
        formBox.setLayout(formLayout)
67+
        tab.addTab(formBox, "Transifex")
68+
69+
    def openTransifex(self):
70+
        QDesktopServices().openUrl(QUrl("https://www.transifex.com/user/settings/api/"));
71+
72+
    def updateTransifex(self):
73+
        self.data["Transifex"] = {}
74+
        self.data["Transifex"]["token"] = self.TransifexToken.text()
75+
76+
    def addTPTab(self, tab):
77+
        formBox = QGroupBox(self.tr("Translation Project"))
78+
        formLayout = QFormLayout()
79+
80+
        self.TPemail = QLineEdit()
81+
        self.TPuser = QLineEdit()
82+
        self.TPserver = QLineEdit()
83+
        self.TPfullname = QLineEdit()
84+
85+
        if not "TP" in self.data:
86+
            self.data["TP"] = {}
87+
88+
        if 'email' in self.data['TP']:
89+
            self.TPemail.setText(self.data["TP"]["email"])
90+
        if 'user' in self.data['TP']:
91+
            self.TPuser.setText(self.data["TP"]["user"])
92+
        if 'server' in self.data['TP']:
93+
            self.TPserver.setText(self.data["TP"]["server"])
94+
        if 'fullname' in self.data['TP']:
95+
            self.TPfullname.setText(self.data["TP"]["fullname"])
96+
97+
        self.TPemail.textChanged.connect(self.updateTP)
98+
        self.TPuser.textChanged.connect(self.updateTP)
99+
        self.TPserver.textChanged.connect(self.updateTP)
100+
        self.TPfullname.textChanged.connect(self.updateTP)
101+
102+
        formLayout.addRow(QLabel(self.tr("Email:")), self.TPemail)
103+
        formLayout.addRow(QLabel(self.tr("Server:")), self.TPserver)
104+
        formLayout.addRow(QLabel(self.tr("User Name:")), self.TPuser)
105+
        formLayout.addRow(QLabel(self.tr("Full Name (John Doe <john@doe.me>):")), self.TPfullname)
106+
107+
        formBox.setLayout(formLayout)
108+
        tab.addTab(formBox, "TP")
109+
110+
    def updateTP(self):
111+
        self.data["TP"] = {}
112+
        self.data["TP"]["email"] = self.TPemail.text()
113+
        self.data["TP"]["user"] = self.TPuser.text()
114+
        self.data["TP"]["server"] = self.TPserver.text()
115+
        self.data["TP"]["fullname"] = self.TPfullname.text()
116+
117+
    def ok(self):
118+
        self.done = True
119+
        self.close()

offlate/ui/spellcheckedit.py unknown status 1

1+
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
from PyQt5.QtWidgets import *
18+
from PyQt5.QtGui import *
19+
from PyQt5.QtCore import *
20+
21+
import enchant
22+
import re
23+
import sys
24+
25+
class SpellCheckEdit(QTextEdit):
26+
    def __init__(self, lang, *args):
27+
        QTextEdit.__init__(self, *args)
28+
        self.dict = enchant.Dict(lang)
29+
        self.highlighter = Highlighter(self.document())
30+
        self.highlighter.setDict(self.dict)
31+
32+
    def contextMenuEvent(self, event):
33+
        popup_menu = self.createStandardContextMenu()
34+
35+
        # Select the word under the cursor.
36+
        cursor = self.textCursor()
37+
        cursor.select(QTextCursor.WordUnderCursor)
38+
        self.setTextCursor(cursor)
39+
40+
        if self.textCursor().hasSelection():
41+
            text = self.textCursor().selectedText()
42+
            if not self.dict.check(text):
43+
                spell_menu = QMenu(self.tr('Spelling Suggestions'))
44+
                nospell = QAction(self.tr('No Suggestions'))
45+
                nospell.setEnabled(False)
46+
                for word in self.dict.suggest(text):
47+
                    action = QAction(word)
48+
                    action.triggered.connect((lambda word: (lambda : self.correctWord(word)))(word))
49+
                    spell_menu.addAction(action)
50+
                # If there are suggestions, use the spell_menu. Otherwise, show
51+
                # there is no suggestion.
52+
                popup_menu.insertSeparator(popup_menu.actions()[0])
53+
                if len(spell_menu.actions()) != 0:
54+
                    popup_menu.insertMenu(popup_menu.actions()[0], spell_menu)
55+
                else:
56+
                    popup_menu.insertAction(popup_menu.actions()[0], nospell)
57+
58+
        popup_menu.exec_(event.globalPos())
59+
60+
    def correctWord(self, word):
61+
        cursor = self.textCursor()
62+
        cursor.beginEditBlock()
63+
64+
        cursor.removeSelectedText()
65+
        cursor.insertText(word)
66+
67+
        cursor.endEditBlock()
68+
69+
class Highlighter(QSyntaxHighlighter):
70+
    def __init__(self, *args):
71+
        QSyntaxHighlighter.__init__(self, *args)
72+
73+
    def setDict(self, dico):
74+
        self.dict = dico
75+
76+
    def highlightBlock(self, text):
77+
        if self.dict == None:
78+
            return
79+
80+
        format = QTextCharFormat()
81+
        format.setUnderlineColor(Qt.red)
82+
        format.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
83+
84+
        for word_object in re.finditer(r'\b[^\W\d_]+\b', text):
85+
            if not self.dict.check(word_object.group()):
86+
                self.setFormat(word_object.start(), word_object.end() - word_object.start(), format)
87+
88+
89+
if __name__ == '__main__':
90+
    app = QApplication(sys.argv)
91+
    w = SpellCheckEdit()
92+
    w.show()
93+
    sys.exit(app.exec_())

offlate/ui/tagclickedit.py unknown status 1

1+
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2+
#
3+
#   This program is free software: you can redistribute it and/or modify
4+
#   it under the terms of the GNU Affero General Public License as
5+
#   published by the Free Software Foundation, either version 3 of the
6+
#   License, or (at your option) any later version.
7+
#
8+
#   This program is distributed in the hope that it will be useful,
9+
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11+
#   GNU Affero General Public License for more details.
12+
#
13+
#   You should have received a copy of the GNU Affero General Public License
14+
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15+
####
16+
17+
from PyQt5.QtWidgets import *
18+
from PyQt5.QtGui import *
19+
from PyQt5.QtCore import *
20+
21+
import re
22+
import sys
23+
from urllib.parse import quote
24+
import html
25+
26+
class TagClickEdit(QTextBrowser):
27+
    def __init__(self, *args):
28+
        QTextBrowser.__init__(self, *args)
29+
30+
    def createLinks(self):
31+
        text = self.toHtml()
32+
        for word_object in re.finditer(r'@[a-z]+{[^}]*}', text):
33+
            rep = word_object.string[word_object.span()[0] : word_object.span()[1]]
34+
            text = text.replace(rep, '<a href="#' + quote(html.unescape(rep)) + '">' + rep + '</a>')
35+
        self.setHtml(text)
36+
37+
if __name__ == '__main__':
38+
    app = QApplication(sys.argv)
39+
    w = TagClickEdit()
40+
    w.setText("GNU@tie{}Hello provides the @command{hello} command.")
41+
    w.createLinks()
42+
    w.show()
43+
    sys.exit(app.exec_())

offlate/window.py unknown status 2

1-
#   Copyright (c) 2018 Julien Lepiller <julien@lepiller.eu>
2-
#
3-
#   This program is free software: you can redistribute it and/or modify
4-
#   it under the terms of the GNU Affero General Public License as
5-
#   published by the Free Software Foundation, either version 3 of the
6-
#   License, or (at your option) any later version.
7-
#
8-
#   This program is distributed in the hope that it will be useful,
9-
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
10-
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11-
#   GNU Affero General Public License for more details.
12-
#
13-
#   You should have received a copy of the GNU Affero General Public License
14-
#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
15-
####
16-
17-
import sys
18-
from PyQt5.QtWidgets import *
19-
from PyQt5.QtGui import *
20-
from PyQt5.QtCore import *
21-
22-
import json
23-
import re
24-
import os
25-
import math
26-
from urllib.parse import unquote
27-
28-
from .manager import ProjectManager
29-
from .spellcheckedit import SpellCheckEdit
30-
from .tagclickedit import TagClickEdit
31-
from .multiplelineedit import MultipleLineEdit
32-
33-
from .systems.list import *
34-
from .formats.list import *
35-
36-
class ProjectTab(QTabWidget):
37-
    def __init__(self, parent = None):
38-
        super(ProjectTab, self).__init__(parent)
39-
40-
class Interface:
41-
    def __init__(self):
42-
        self.value = None
43-
44-
    def ok(self):
45-
        self.value = self.qd.textValue()
46-
47-
    def askPassword(self):
48-
        self.qd = QInputDialog()
49-
        self.qd.setLabelText(self.qd.tr("Please enter your password:"))
50-
        self.qd.setTextEchoMode(QLineEdit.Password)
51-
        self.qd.accepted.connect(self.ok)
52-
        self.qd.exec_()
53-
        return self.value
54-
55-
class ProjectView(QWidget):
56-
    translationModified = pyqtSignal()
57-
58-
    def __init__(self, project, showTranslated = True, showUntranslated = True,
59-
                showFuzzy = True, monospace = False, parent = None):
60-
        super(ProjectView, self).__init__(parent)
61-
        self.project = project
62-
        self.content = self.project.content()
63-
        self.currentContent = list(self.content.keys())[0]
64-
        self.showTranslated = showTranslated
65-
        self.showUntranslated = showUntranslated
66-
        self.showFuzzy = showFuzzy
67-
        self.monospace = monospace
68-
        self.fuzzyColor = QBrush(QColor(255, 127, 80))
69-
        self.emptyColor = QBrush(QColor(255, 240, 235))
70-
        self.initUI()
71-
72-
    def updateContent(self):
73-
        self.treeWidget.clear()
74-
        items = []
75-
        for entry in self.content[self.currentContent]:
76-
            if entry.isObsolete():
77-
                continue
78-
            cont = False
79-
            if self.showTranslated and entry.isTranslated():
80-
                cont = True
81-
            if self.showUntranslated and not entry.isTranslated():
82-
                cont = True
83-
            if self.showFuzzy and entry.isFuzzy():
84-
                cont = True
85-
            if not cont:
86-
                continue
87-
            item = QTreeWidgetItem([entry.msgids[0].replace('\n', ' '),
88-
                                    entry.msgstrs[0].replace('\n', ' ')])
89-
            if entry.isFuzzy():
90-
                item.setForeground(1, self.fuzzyColor)
91-
            if not entry.isTranslated():
92-
                item.setBackground(1, self.emptyColor)
93-
            item.setFont(0, QFont("sans-serif", 10))
94-
            item.setFont(1, QFont("sans-serif", 10))
95-
            item.setSizeHint(0, QSize(-1, 22))
96-
            item.setData(0, Qt.UserRole, entry)
97-
            items.append(item)
98-
        self.treeWidget.insertTopLevelItems(0, items)
99-
        self.translationModified.emit()
100-
101-
    def initUI(self):
102-
        vbox = QVBoxLayout()
103-
        self.setLayout(vbox)
104-
        model = QStandardItemModel()
105-
        self.treeWidget = QTreeWidget()
106-
        self.treeWidget.setColumnCount(2)
107-
        self.msgid = QTextEdit()
108-
        self.msgid.setReadOnly(True)
109-
        self.msgstr = SpellCheckEdit(self.project.lang)
110-
        self.filechooser = QComboBox()
111-
        for project in list(self.content.keys()):
112-
            self.filechooser.addItem(project)
113-
        self.filechooser.currentIndexChanged.connect(self.changefile)
114-
115-
        self.buttons = QVBoxLayout()
116-
        self.copyButton = QPushButton(self.tr("Copy"))
117-
        self.copyButton.clicked.connect(self.copy)
118-
        self.buttons.addWidget(self.copyButton)
119-
120-
        if self.filechooser.count() > 1:
121-
            vbox.addWidget(self.filechooser)
122-
123-
        self.updateContent()
124-
        vbox.addWidget(self.treeWidget, 4)
125-
        self.hbox = QHBoxLayout()
126-
        self.hbox.addWidget(self.msgid)
127-
        self.hbox.addLayout(self.buttons)
128-
        self.hbox.addWidget(self.msgstr)
129-
        vbox.addLayout(self.hbox, 1)
130-
        size = self.treeWidget.size()
131-
        self.treeWidget.setColumnWidth(0, size.width()/2)
132-
        self.treeWidget.currentItemChanged.connect(self.selectItem)
133-
134-
    def changefile(self):
135-
        self.currentContent = list(self.content.keys())[self.filechooser.currentIndex()]
136-
        self.updateContent()
137-
138-
    def nextItem(self):
139-
        index = self.treeWidget.currentIndex()
140-
        nextItem = self.treeWidget.itemFromIndex(self.treeWidget.indexBelow(index))
141-
        self.treeWidget.setCurrentItem(nextItem)
142-
143-
    def previousItem(self):
144-
        index = self.treeWidget.currentIndex()
145-
        nextItem = self.treeWidget.itemFromIndex(self.treeWidget.indexAbove(index))
146-
        self.treeWidget.setCurrentItem(nextItem)
147-
148-
    def copy(self):
149-
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
150-
            text = self.msgid.toPlainText()
151-
            self.msgstr.setText(text)
152-
        else:
153-
            text = self.msgid.currentWidget().toPlainText()
154-
            self.msgstr.currentWidget().setText(text)
155-
156-
    def copyTag(self, tag):
157-
        tag = tag.toDisplayString()[1:]
158-
        tag = unquote(tag)
159-
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
160-
            self.msgstr.insertPlainText(tag)
161-
            self.msgstr.setFocus(True)
162-
        else:
163-
            self.msgstr.currentWidget.insertPlainText(tag)
164-
            self.msgstr.currentWidget.setFocus(True)
165-
166-
    def selectItem(self, current, old):
167-
        if current == None:
168-
            return
169-
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
170-
            self.msgstr.clearFocus()
171-
        else:
172-
            self.msgstr.currentWidget.clearFocus()
173-
        data = current.data(0, Qt.UserRole)
174-
        self.hbox.removeWidget(self.msgid)
175-
        self.hbox.removeItem(self.buttons)
176-
        self.hbox.removeWidget(self.msgstr)
177-
        self.msgid.deleteLater()
178-
        self.msgstr.deleteLater()
179-
180-
        font = "monospace" if self.monospace else "sans-serif"
181-
        focuser = None
182-
183-
        if len(data.msgstrs) > 1:
184-
            self.msgid = QTabWidget();
185-
            self.msgstr = QTabWidget();
186-
            singular = TagClickEdit()
187-
            singular.setFont(QFont(font))
188-
            singular.setReadOnly(True)
189-
            singular.setText(data.msgids[0])
190-
            singular.createLinks()
191-
            singular.anchorClicked.connect(self.copyTag)
192-
            plural = TagClickEdit()
193-
            plural.setFont(QFont(font))
194-
            plural.setReadOnly(True)
195-
            plural.setText(data.msgids[1])
196-
            plural.createLinks()
197-
            plural.anchorClicked.connect(self.copyTag)
198-
            self.msgid.addTab(singular, self.tr("Singular"))
199-
            self.msgid.addTab(plural, self.tr("Plural"))
200-
            i = 0
201-
            for msgstr in data.msgstrs:
202-
                form = SpellCheckEdit(self.project.lang)
203-
                form.setFont(QFont(font))
204-
                form.setText(msgstr)
205-
                form.textChanged.connect(self.modify)
206-
                self.msgstr.addTab(form, str(i))
207-
                if i == 0:
208-
                    focuser = form
209-
                i=i+1
210-
        else:
211-
            self.msgid = TagClickEdit()
212-
            self.msgid.setFont(QFont(font))
213-
            self.msgid.setReadOnly(True)
214-
            self.msgid.setText(data.msgids[0])
215-
            self.msgid.createLinks()
216-
            self.msgid.anchorClicked.connect(self.copyTag)
217-
            self.msgstr = SpellCheckEdit(self.project.lang)
218-
            self.msgstr.setFont(QFont(font))
219-
            self.msgstr.setText(data.msgstrs[0])
220-
            self.msgstr.textChanged.connect(self.modify)
221-
            focuser = self.msgstr
222-
        self.hbox.addWidget(self.msgid)
223-
        self.hbox.addLayout(self.buttons)
224-
        self.hbox.addWidget(self.msgstr)
225-
        focuser.setFocus()
226-
227-
    def modify(self):
228-
        item = self.treeWidget.currentItem()
229-
        data = item.data(0, Qt.UserRole)
230-
        if self.msgstr.__class__.__name__ == "SpellCheckEdit":
231-
            msgstr = self.msgstr.toPlainText()
232-
            data.update(0, msgstr)
233-
            item.setText(1, msgstr.replace('\n', ' '))
234-
        else:
235-
            i = 0
236-
            for msgstr in data.msgstrs:
237-
                data.update(i, self.msgstr.widget(i).toPlainText())
238-
                i=i+1
239-
            item.setText(1, data.get(0).replace('\n', ' '))
240-
        item.setForeground(1, QBrush())
241-
        if data.isTranslated():
242-
            item.setBackground(1, QBrush())
243-
        else:
244-
            item.setBackground(1, self.emptyColor)
245-
        self.translationModified.emit()
246-
247-
248-
    def save(self):
249-
        self.project.save()
250-
251-
    def send(self):
252-
        self.project.save()
253-
        self.project.send(Interface())
254-
255-
    def askmerge(self, msgid, oldstr, newstr):
256-
        # TODO: Actually do something more intelligent
257-
        return newstr
258-
259-
    def update(self):
260-
        self.project.save()
261-
        self.project.update(self.askmerge)
262-
        self.content = self.project.content()
263-
        self.updateContent()
264-
265-
    def filter(self, showTranslated, showUntranslated, showFuzzy):
266-
        self.showTranslated = showTranslated
267-
        self.showUntranslated = showUntranslated
268-
        self.showFuzzy = showFuzzy
269-
        self.updateContent()
270-
271-
    def setFont(self, monospace):
272-
        self.monospace = monospace
273-
        current = self.treeWidget.currentItem()
274-
        self.selectItem(current, current)
275-
276-
class NewWindow(QDialog):
277-
    def __init__(self, manager, parent = None):
278-
        super().__init__(parent)
279-
        self.name = ""
280-
        self.lang = ""
281-
        self.system = 0
282-
        self.manager = manager
283-
        self.askNew = False
284-
        self.initUI()
285-
286-
    def initUI(self):
287-
        hbox = QHBoxLayout()
288-
        predefinedbox = QVBoxLayout()
289-
        self.searchfield = QLineEdit()
290-
        predefinedbox.addWidget(self.searchfield)
291-
        self.predefinedprojects = QListWidget()
292-
        with open(os.path.dirname(__file__) + '/data.json') as f:
293-
            self.projectdata = json.load(f)
294-
            for d in self.projectdata:
295-
                item = QListWidgetItem(d['name'])
296-
                item.setData(Qt.UserRole, d)
297-
                self.predefinedprojects.addItem(item)
298-
        predefinedbox.addWidget(self.predefinedprojects)
299-
300-
        contentbox = QVBoxLayout()
301-
        formbox = QGroupBox(self.tr("Project information"))
302-
        self.formLayout = QFormLayout()
303-
        formbox.setLayout(self.formLayout)
304-
305-
        self.nameWidget = QLineEdit()
306-
        self.langWidget = QLineEdit()
307-
        self.formLayout.addRow(QLabel(self.tr("Name:")), self.nameWidget)
308-
        self.formLayout.addRow(QLabel(self.tr("Target Language:")), self.langWidget)
309-
        self.combo = QComboBox()
310-
        self.combo.addItem(self.tr("The Translation Project"))
311-
        self.combo.addItem(self.tr("Transifex"))
312-
        self.combo.addItem(self.tr("Gitlab"))
313-
        self.formLayout.addRow(self.combo)
314-
315-
        self.nameWidget.textChanged.connect(self.modify)
316-
        self.langWidget.textChanged.connect(self.modify)
317-
318-
        hhbox = QHBoxLayout()
319-
        cancel = QPushButton(self.tr("Cancel"))
320-
        self.okbutton = QPushButton(self.tr("OK"))
321-
        self.okbutton.setEnabled(False)
322-
        hhbox.addWidget(cancel)
323-
        hhbox.addWidget(self.okbutton)
324-
        contentbox.addWidget(formbox)
325-
        contentbox.addLayout(hhbox)
326-
        hbox.addLayout(predefinedbox)
327-
        hbox.addLayout(contentbox)
328-
329-
        self.additionalFields = []
330-
        self.additionalFields.append([])
331-
        self.additionalFields.append([])
332-
        self.additionalFields.append([])
333-
        
334-
        # Transifex
335-
        self.transifexOrganisation = QLineEdit()
336-
        self.transifexOrganisation.textChanged.connect(self.modify)
337-
        transifexOrganisationLabel = QLabel(self.tr("Organization"))
338-
        self.additionalFields[TRANSIFEX].append({'label': transifexOrganisationLabel,
339-
            'widget': self.transifexOrganisation})
340-
341-
        # Gitlab
342-
        self.gitlabSite = QLineEdit()
343-
        self.gitlabSite.textChanged.connect(self.modify)
344-
        gitlabSiteLabel = QLabel(self.tr('hosting website'))
345-
        self.additionalFields[GITLAB].append({'label': gitlabSiteLabel,
346-
            'widget': self.gitlabSite})
347-
        self.gitlabProject = QLineEdit()
348-
        self.gitlabProject.textChanged.connect(self.modify)
349-
        gitlabProjectLabel = QLabel(self.tr('project part'))
350-
        self.additionalFields[GITLAB].append({'label': gitlabProjectLabel,
351-
            'widget': self.gitlabProject})
352-
        self.gitlabFileTypeCombo = QComboBox()
353-
        self.gitlabFileTypeCombo.addItem(format_list[0])
354-
        self.gitlabFileTypeCombo.addItem(format_list[1])
355-
        self.gitlabFileTypeCombo.addItem(format_list[2])
356-
        self.gitlabFileTypeCombo.currentIndexChanged.connect(self.modify)
357-
        gitlabFileTypeLabel = QLabel(self.tr('Translation type'))
358-
        self.additionalFields[GITLAB].append({'label': gitlabFileTypeLabel,
359-
            'widget': self.gitlabFileTypeCombo})
360-
        self.gitlabLocations = MultipleLineEdit()
361-
        self.gitlabLocations.textChanged.connect(self.modify)
362-
        gitlabLocationsLabel = QLabel(self.tr('Locations'))
363-
        self.additionalFields[GITLAB].append({'label': gitlabLocationsLabel,
364-
            'widget': self.gitlabLocations})
365-
366-
        self.setLayout(hbox)
367-
368-
        self.predefinedprojects.currentItemChanged.connect(self.fill)
369-
        cancel.clicked.connect(self.close)
370-
        self.okbutton.clicked.connect(self.ok)
371-
        self.searchfield.textChanged.connect(self.filter)
372-
        self.combo.currentIndexChanged.connect(self.othersystem)
373-
374-
    def ok(self):
375-
        self.askNew = True
376-
        self.close()
377-
378-
    def fill(self):
379-
        item = self.predefinedprojects.currentItem()
380-
        data = item.data(Qt.UserRole)
381-
        self.nameWidget.setText(data['name'])
382-
        self.combo.setCurrentIndex(int(data['system']))
383-
        if data['system'] == TRANSIFEX:
384-
            self.transifexOrganisation.setText(data['organisation'])
385-
        if data['system'] == GITLAB:
386-
            self.gitlabSite.setText(data['site'])
387-
            self.gitlabProject.setText(data['project'])
388-
            self.gitlabFileTypeCombo.setCurrentIndex(data['format'])
389-
            self.gitlabLocations.setText(data['locations'])
390-
391-
    def filter(self):
392-
        search = self.searchfield.text()
393-
        self.predefinedprojects.clear()
394-
        regexp = re.compile(".*"+search)
395-
        for d in self.projectdata:
396-
            if regexp.match(d['name']):
397-
                item = QListWidgetItem(d['name'])
398-
                item.setData(Qt.UserRole, d)
399-
                self.predefinedprojects.addItem(item)
400-
401-
    def modify(self):
402-
        enable = False
403-
        if self.nameWidget.text() != '' and self.langWidget.text() != '':
404-
            enable = True
405-
            for widget in self.additionalFields[self.combo.currentIndex()]:
406-
                if isinstance(widget['widget'], QLineEdit) and widget['widget'].text() == '':
407-
                    enable = False
408-
                    break
409-
        self.okbutton.setEnabled(enable)
410-
411-
    def wantNew(self):
412-
        return self.askNew
413-
414-
    def getProjectName(self):
415-
        return self.nameWidget.text()
416-
417-
    def getProjectLang(self):
418-
        return self.langWidget.text()
419-
420-
    def getProjectSystem(self):
421-
        return self.combo.currentIndex()
422-
423-
    def getProjectInfo(self):
424-
        if self.getProjectSystem() == TRANSLATION_PROJECT:
425-
            return {}
426-
        if self.getProjectSystem() == TRANSIFEX:
427-
            return {'organization': self.additionalFields[TRANSIFEX][0]['widget'].text()}
428-
        if self.getProjectSystem() == GITLAB:
429-
            return {'site': self.additionalFields[GITLAB][0]['widget'].text(),
430-
                    'project': self.additionalFields[GITLAB][1]['widget'].text(),
431-
                    'format': self.additionalFields[GITLAB][2]['widget'].currentIndex(),
432-
                    'locations': self.additionalFields[GITLAB][3]['widget'].content()}
433-
        return {}
434-
435-
    def othersystem(self):
436-
        for system in self.additionalFields:
437-
            for widget in system:
438-
                self.formLayout.takeRow(widget['widget'])
439-
                widget['widget'].hide()
440-
                widget['label'].hide()
441-
        self.formLayout.invalidate()
442-
        for widget in self.additionalFields[self.combo.currentIndex()]:
443-
            self.formLayout.addRow(widget['label'], widget['widget'])
444-
            widget['widget'].show()
445-
            widget['label'].show()
446-
        self.modify()
447-
448-
class SettingsWindow(QDialog):
449-
    def __init__(self, preferences, parent = None):
450-
        super().__init__(parent)
451-
        self.data = preferences
452-
        self.done = False
453-
        self.initUI()
454-
455-
    def initUI(self):
456-
        vbox = QVBoxLayout()
457-
458-
        tab = QTabWidget()
459-
        self.addTPTab(tab)
460-
        self.addTransifexTab(tab)
461-
462-
        buttonbox = QHBoxLayout()
463-
        cancel = QPushButton(self.tr("Cancel"))
464-
        ok = QPushButton(self.tr("OK"))
465-
        buttonbox.addWidget(cancel)
466-
        buttonbox.addWidget(ok)
467-
468-
        vbox.addWidget(tab)
469-
        vbox.addLayout(buttonbox)
470-
        self.setLayout(vbox)
471-
        cancel.clicked.connect(self.close)
472-
        ok.clicked.connect(self.ok)
473-
474-
    def addTransifexTab(self, tab):
475-
        formBox = QGroupBox(self.tr("Transifex"))
476-
        formLayout = QFormLayout()
477-
        self.TransifexToken = QLineEdit()
478-
479-
        if not "Transifex" in self.data:
480-
            self.data["Transifex"] = {}
481-
        try:
482-
            self.TransifexToken.setText(self.data["Transifex"]["token"])
483-
        except Exception:
484-
            pass
485-
486-
        self.TransifexToken.textChanged.connect(self.updateTransifex)
487-
        label = QLabel(self.tr("You can get a token from <a href=\"#\">https://www.transifex.com/user/settings/api/</a>"))
488-
        label.linkActivated.connect(self.openTransifex)
489-
490-
        formLayout.addRow(QLabel(self.tr("Token:")), self.TransifexToken)
491-
        formLayout.addRow(label)
492-
493-
        formBox.setLayout(formLayout)
494-
        tab.addTab(formBox, "Transifex")
495-
496-
    def openTransifex(self):
497-
        QDesktopServices().openUrl(QUrl("https://www.transifex.com/user/settings/api/"));
498-
499-
    def updateTransifex(self):
500-
        self.data["Transifex"] = {}
501-
        self.data["Transifex"]["token"] = self.TransifexToken.text()
502-
503-
    def addTPTab(self, tab):
504-
        formBox = QGroupBox(self.tr("Translation Project"))
505-
        formLayout = QFormLayout()
506-
507-
        self.TPemail = QLineEdit()
508-
        self.TPuser = QLineEdit()
509-
        self.TPserver = QLineEdit()
510-
        self.TPfullname = QLineEdit()
511-
512-
        if not "TP" in self.data:
513-
            self.data["TP"] = {}
514-
515-
        if 'email' in self.data['TP']:
516-
            self.TPemail.setText(self.data["TP"]["email"])
517-
        if 'user' in self.data['TP']:
518-
            self.TPuser.setText(self.data["TP"]["user"])
519-
        if 'server' in self.data['TP']:
520-
            self.TPserver.setText(self.data["TP"]["server"])
521-
        if 'fullname' in self.data['TP']:
522-
            self.TPfullname.setText(self.data["TP"]["fullname"])
523-
524-
        self.TPemail.textChanged.connect(self.updateTP)
525-
        self.TPuser.textChanged.connect(self.updateTP)
526-
        self.TPserver.textChanged.connect(self.updateTP)
527-
        self.TPfullname.textChanged.connect(self.updateTP)
528-
529-
        formLayout.addRow(QLabel(self.tr("Email:")), self.TPemail)
530-
        formLayout.addRow(QLabel(self.tr("Server:")), self.TPserver)
531-
        formLayout.addRow(QLabel(self.tr("User Name:")), self.TPuser)
532-
        formLayout.addRow(QLabel(self.tr("Full Name (John Doe <john@doe.me>):")), self.TPfullname)
533-
534-
        formBox.setLayout(formLayout)
535-
        tab.addTab(formBox, "TP")
536-
537-
    def updateTP(self):
538-
        self.data["TP"] = {}
539-
        self.data["TP"]["email"] = self.TPemail.text()
540-
        self.data["TP"]["user"] = self.TPuser.text()
541-
        self.data["TP"]["server"] = self.TPserver.text()
542-
        self.data["TP"]["fullname"] = self.TPfullname.text()
543-
544-
    def ok(self):
545-
        self.done = True
546-
        self.close()
547-
548-
class Window(QMainWindow):
549-
    def __init__(self):
550-
        super().__init__()
551-
        self.manager = ProjectManager()
552-
        self.initUI()
553-
554-
    def initOpenProjects(self, menu):
555-
        l = self.manager.listProjects()
556-
        for p in l:
557-
            name = p['name']
558-
            act = QAction(name, self) 
559-
            act.triggered.connect((lambda name: (lambda : self.open(name)))(name))
560-
            menu.addAction(act)
561-
562-
    def open(self, name):
563-
        try:
564-
            project = self.manager.getProject(name)
565-
        except Exception:
566-
            self.qd = QErrorMessage()
567-
            self.qd.showMessage(self.tr("Unsupported / Unknown project"))
568-
            self.qd.exec_()
569-
            return
570-
        tab = ProjectView(project,
571-
            showTranslated = self.showTranslatedAct.isChecked(),
572-
            showUntranslated = self.showUntranslatedAct.isChecked(),
573-
            showFuzzy = self.showFuzzyAct.isChecked(),
574-
            monospace = self.monospaceAct.isChecked())
575-
        tab.translationModified.connect(self.count)
576-
        self.tabs.addTab(tab, name)
577-
        self.count()
578-
579-
    def count(self, item = -1):
580-
        widget = self.tabs.currentWidget()
581-
        content = widget.content[widget.currentContent]
582-
        total = 0
583-
        translated = 0
584-
        for d in content:
585-
            total += 1
586-
            if d.isTranslated() and not d.isFuzzy():
587-
                translated += 1
588-
        percent = 100 if total == 0 else math.floor(1000 * translated / total)/10
589-
        self.countLabel.setText(self.tr("{} translated on {} total ({}%).").format(translated, total, percent))
590-
591-
    def save(self):
592-
        self.tabs.currentWidget().save()
593-
594-
    def new(self):
595-
        w = NewWindow(self.manager)
596-
        w.exec_()
597-
        if not w.wantNew():
598-
            return
599-
        self.manager.createProject(w.getProjectName(), w.getProjectLang(),
600-
                    w.getProjectSystem(), w.getProjectInfo())
601-
        self.open(w.getProjectName())
602-
603-
    def send(self):
604-
        self.tabs.currentWidget().send()
605-
606-
    def update(self):
607-
        self.tabs.currentWidget().update()
608-
        self.manager.update()
609-
        self.manager.writeProjects()
610-
611-
    def closeProject(self):
612-
        self.tabs.removeTab(self.tabs.currentIndex())
613-
614-
    def settings(self):
615-
        w = SettingsWindow(self.manager.getConf())
616-
        w.exec_()
617-
        if w.done:
618-
            self.manager.updateSettings(w.data)
619-
620-
    def filter(self):
621-
        for i in range(0, self.tabs.count()):
622-
            self.tabs.widget(i).filter(
623-
                self.showTranslatedAct.isChecked(),
624-
                self.showUntranslatedAct.isChecked(),
625-
                self.showFuzzyAct.isChecked())
626-
627-
    def setFont(self):
628-
        for i in range(0, self.tabs.count()):
629-
            self.tabs.widget(i).setFont(self.monospaceAct.isChecked())
630-
631-
    def previousItem(self):
632-
        self.tabs.currentWidget().previousItem()
633-
634-
    def nextItem(self):
635-
        self.tabs.currentWidget().nextItem()
636-
637-
    def initUI(self):
638-
        # Build menu
639-
        exitAct = QAction(QIcon('exit.png'), self.tr('Exit'), self)
640-
        exitAct.setShortcut('Ctrl+Q')
641-
        exitAct.setStatusTip(self.tr('Exit application'))
642-
        exitAct.triggered.connect(qApp.quit)
643-
644-
        saveAct = QAction(QIcon('save.png'), self.tr('Save'), self)
645-
        saveAct.setShortcut('Ctrl+S')
646-
        saveAct.setStatusTip(self.tr('Save current project'))
647-
        saveAct.triggered.connect(self.save)
648-
649-
        newAct = QAction(QIcon('new.png'), self.tr('New'), self)
650-
        newAct.setShortcut('Ctrl+N')
651-
        newAct.setStatusTip(self.tr('New project'))
652-
        newAct.triggered.connect(self.new)
653-
654-
        updateAct = QAction(QIcon('download.png'), self.tr('Update'), self)
655-
        updateAct.setShortcut('Ctrl+U')
656-
        updateAct.setStatusTip(self.tr('Get modifications from upstream'))
657-
        updateAct.triggered.connect(self.update)
658-
659-
        sendAct = QAction(QIcon('close.png'), self.tr('Close'), self)
660-
        sendAct.setStatusTip(self.tr('Close current project'))
661-
        sendAct.triggered.connect(self.closeProject)
662-
663-
        closeAct = QAction(QIcon('upload.png'), self.tr('Send'), self)
664-
        closeAct.setShortcut('Ctrl+E')
665-
        closeAct.setStatusTip(self.tr('Send modifications upstream'))
666-
        closeAct.triggered.connect(self.send)
667-
668-
        settingsAct = QAction(QIcon('settings.png'), self.tr('Settings'), self)
669-
        settingsAct.setShortcut('Ctrl+P')
670-
        settingsAct.setStatusTip(self.tr('Set parameters'))
671-
        settingsAct.triggered.connect(self.settings)
672-
673-
        self.showTranslatedAct = QAction(self.tr('Show Translated'), self, checkable=True)
674-
        self.showTranslatedAct.setChecked(True)
675-
        self.showTranslatedAct.triggered.connect(self.filter)
676-
        self.showFuzzyAct = QAction(self.tr('Show Fuzzy'), self, checkable=True)
677-
        self.showFuzzyAct.setChecked(True)
678-
        self.showFuzzyAct.triggered.connect(self.filter)
679-
        self.showUntranslatedAct = QAction(self.tr('Show Empty Translation'), self, checkable=True)
680-
        self.showUntranslatedAct.setChecked(True)
681-
        self.showUntranslatedAct.triggered.connect(self.filter)
682-
        self.monospaceAct = QAction(self.tr('Use a monospace font'), self, checkable=True)
683-
        self.monospaceAct.setChecked(False)
684-
        self.monospaceAct.triggered.connect(self.setFont)
685-
686-
        self.previousShortcut = QShortcut(QKeySequence("Ctrl+Up"), self)
687-
        self.previousShortcut.activated.connect(self.previousItem)
688-
689-
        self.previous2Shortcut = QShortcut(QKeySequence("Ctrl+Shift+Return"), self)
690-
        self.previous2Shortcut.activated.connect(self.previousItem)
691-
692-
        self.nextShortcut = QShortcut(QKeySequence("Ctrl+Down"), self)
693-
        self.nextShortcut.activated.connect(self.nextItem)
694-
695-
        self.next2Shortcut = QShortcut(QKeySequence("Ctrl+Return"), self)
696-
        self.next2Shortcut.activated.connect(self.nextItem)
697-
698-
        self.countLabel = QLabel()
699-
        self.statusBar()
700-
        self.statusBar().addWidget(self.countLabel)
701-
702-
        openMenu = QMenu(self.tr('Open'), self)
703-
        self.initOpenProjects(openMenu)
704-
705-
        menubar = self.menuBar()
706-
        fileMenu = menubar.addMenu(self.tr('&File'))
707-
        fileMenu.addAction(newAct)
708-
        fileMenu.addMenu(openMenu)
709-
        fileMenu.addSeparator()
710-
        fileMenu.addAction(exitAct)
711-
712-
        projectMenu = menubar.addMenu(self.tr('&Project'))
713-
        projectMenu.addAction(updateAct)
714-
        projectMenu.addAction(saveAct)
715-
        projectMenu.addAction(sendAct)
716-
        projectMenu.addSeparator()
717-
        projectMenu.addAction(closeAct)
718-
719-
        editMenu = menubar.addMenu(self.tr('&Edit'))
720-
        editMenu.addAction(settingsAct)
721-
722-
        viewMenu = menubar.addMenu(self.tr('&View'))
723-
        viewMenu.addAction(self.showTranslatedAct)
724-
        viewMenu.addAction(self.showUntranslatedAct)
725-
        viewMenu.addAction(self.showFuzzyAct)
726-
        viewMenu.addSeparator()
727-
        viewMenu.addAction(self.monospaceAct)
728-
729-
        self.tabs = ProjectTab()
730-
        self.tabs.currentChanged.connect(self.count)
731-
732-
        self.setCentralWidget(self.tabs)
733-
734-
        self.setGeometry(0, 0, 800, 600)
735-
        self.setWindowTitle('Offlate')
736-
        self.show()
737-
738-
def main():
739-
    app = QApplication(sys.argv)
740-
    translator = QTranslator()
741-
    if translator.load(QLocale(), "offlate", "_",
742-
            os.path.dirname(os.path.realpath(__file__))+"/locales"):
743-
        app.installTranslator(translator);
744-
745-
    w = Window()
746-
747-
    sys.exit(app.exec_())
748-

run.py

1-
from offlate import window
1+
import offlate.ui.main
22
3-
window.main()
3+
offlate.ui.main.main()