Add enrolling wizard for first-time users.

Julien LepillerMon Dec 06 00:36:49+0100 2021

4c99b39

Add enrolling wizard for first-time users. This replaces the initial setup screen that used the same window as the settings manager, which is a bit confusing. The rest of the app is unchanged.

CHANGELOG.md

55
66
### Improvements and features ###
77
8+
* First-time users now get a specific window to help them with initial configuration.
89
* Fixed TS format not setting the obsolete flag when saving.
910
* Whitespace characters are shown with a small symbol in the editor. Spaces get
1011
  a small middle dot, no-break spaces get a line underneath. Tabs are also represented.

offlate/ui/config/settings.py

4747
        self.welcomeWidget = SettingsWidget(self, self.manager)
4848
        self.setCentralWidget(self.welcomeWidget)
4949
50-
class SystemSettingsWindow(QDialog):
51-
    def __init__(self, manager, system = -1, parent = None):
50+
class SystemSettingsWidget(QWidget):
51+
    def __init__(self, manager, system = -1, parent=None):
5252
        super().__init__(parent)
5353
        self.system = systems[system]
5454
        self.manager = manager

9292
9393
        formBox.setLayout(formLayout)
9494
        vbox.addWidget(formBox)
95-
96-
        buttons = QHBoxLayout()
97-
        buttons.addStretch(1)
98-
        okbutton = QPushButton(self.tr("OK"))
99-
        buttons.addWidget(okbutton)
100-
        okbutton.clicked.connect(self.close)
101-
        vbox.addLayout(buttons)
10295
        self.setLayout(vbox)
10396
10497
    def update(self):

109102
            widget = self.widgets[key][s.key]
110103
            self.data[key][s.key] = widget.content()
111104
105+
class SystemSettingsWindow(QDialog):
106+
    def __init__(self, manager, system = -1, parent = None):
107+
        super().__init__(parent)
108+
        self.widget = SystemSettingsWidget(manager, system, self)
109+
110+
        buttons = QHBoxLayout()
111+
        buttons.addStretch(1)
112+
        okbutton = QPushButton(self.tr("OK"))
113+
        buttons.addWidget(okbutton)
114+
        okbutton.clicked.connect(self.close)
115+
116+
        vbox = QVBoxLayout()
117+
        vbox.addWidget(self.widget)
118+
        vbox.addLayout(buttons)
119+
        self.setLayout(vbox)
120+
112121
class SettingsWidget(QWidget):
113-
    def __init__(self, parent=None, manager=None, welcome=False):
122+
    def __init__(self, parent=None, manager=None):
114123
        super().__init__(parent)
115124
        self.parent = parent
116125
        self.manager = manager
117-
        self.welcome = welcome
118126
        self.initUI()
119127
120128
    def initUI(self):
121129
        vbox = QVBoxLayout()
122-
        if self.welcome:
123-
            filename = os.path.dirname(__file__) + '/../data/icon.png'
124-
            icon = QPixmap(filename)
125-
            label = QLabel(self)
126-
            label.setPixmap(icon)
127-
            label.setAlignment(Qt.AlignCenter)
128-
            name = QLabel(self)
129-
            name.setText("Offlate")
130-
            name.setAlignment(Qt.AlignCenter)
131-
            font = name.font()
132-
            font.setPointSize(64)
133-
            font.setBold(True)
134-
            name.setFont(font)
135-
            titlebox = QHBoxLayout()
136-
            titlebox.addWidget(label)
137-
            titlebox.addWidget(name)
138-
            vbox.addLayout(titlebox)
139-
            line = QFrame(self)
140-
            line.setFrameShape(QFrame.HLine)
141-
            line.setFrameShadow(QFrame.Sunken)
142-
            vbox.addWidget(line)
143-
144-
            explain = QLabel(self)
145-
            explain.setText(self.tr("Welcome to Offlate! Offlate is a tool for \
146-
software translators who don't want to use dozens of different interfaces to \
147-
get their job done. This first screen will help you configure Offlate for your \
148-
first use!"))
149-
            explain.setWordWrap(True)
150-
            vbox.addWidget(explain)
151130
152131
        formExplain = QLabel(self)
153132
        formExplain.setText(self.tr("First, we need to know your name and email \

168147
        form.addRow(QLabel(self.tr("Your email address:")), self.emailWidget)
169148
        vbox.addLayout(form)
170149
171-
        if self.welcome:
172-
            systemExplain = QLabel(self)
173-
            systemExplain.setText(self.tr("Offlate is able to connect to multiple \
174-
translation service interfaces. Each service requires its own configuration \
175-
settings that you can configure later. For now, we're done with the basics! \
176-
When you select a new project, you will be able to configure the system if it \
177-
requires any additional information. You can configure each system you need now \
178-
+or come back later by clicking on the setting button in offlate."))
179-
            systemExplain.setWordWrap(True)
180-
            vbox.addWidget(systemExplain)
181-
182150
        systemsbox = QGridLayout()
183151
        x = 0
184152
        y = 0

218186
        vbox.addStretch(1)
219187
        buttonbox = QHBoxLayout()
220188
        buttonbox.addStretch(1)
221-
        if self.welcome:
222-
            closeButton = QPushButton(self.tr("Cancel"))
223-
            closeButton.clicked.connect(self.quit)
224-
            buttonbox.addWidget(closeButton)
225189
        doneButton = QPushButton(self.tr("Done!"))
226190
        doneButton.clicked.connect(self.done)
227191
        buttonbox.addWidget(doneButton)

241205
        self.quit()
242206
243207
    def quit(self):
244-
        if self.welcome:
245-
            qApp.quit()
246-
        else:
247-
            self.parent.hide()
208+
        self.parent.hide()
248209
249210
    def openSettings(self, system):
250211
        w = SystemSettingsWindow(self.manager, system, self)

offlate/ui/data/illustration.png unknown status 1

Binary data

offlate/ui/data/illustration.xcf unknown status 1

Binary data

offlate/ui/main.py

1919
from PyQt5.QtCore import *
2020
2121
from .manager import ProjectManagerWindow
22-
from .config.welcome import WelcomeWindow
22+
from .welcome import WelcomeWindow
2323
2424
from ..data.common import REPO, EMAIL
2525

4444
        return
4545
4646
    w.projectManagerWidget.manager.setNotNew()
47+
    w.projectManagerWidget.filter()
4748
    try:
4849
        w.show()
4950
        exit = app.exec_()

offlate/ui/manager.py

220220
                self.tr('Fetching project {}...').format(name, progress))
221221
222222
    def finishReport(self, name):
223+
        self.filter()
223224
        self.actionProgress.setValue(0)
224225
        self.actionProgress.setEnabled(False)
225226
        self.actionLabel.setText("")

301302
        else:
302303
            self.signals.restart_required.emit(self.name, self.lang, self.system,
303304
                    self.info, self.error)
304-
        self.parent.filter()
305305
306306
class EditRunnable(RunnableCallback):
307307
    def __init__(self, parent, name, lang, system, info):

offlate/ui/new.py

2727
from ..systems.list import *
2828
from .multiplelineedit import MultipleLineEdit
2929
30-
class NewWindow(QDialog):
31-
    def __init__(self, manager, parent = None, name = "", 
32-
            lang = "", system = 0, info = None):
30+
class PredefinedProjectWidget(QWidget):
31+
    currentItemChanged = pyqtSignal()
32+
33+
    def __init__(self, parent = None):
3334
        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
35+
        self.setProperty("data", {})
4036
        self.initUI()
4137
4238
    def initUI(self):
43-
        hbox = QHBoxLayout()
4439
        predefinedbox = QVBoxLayout()
4540
        self.searchfield = QLineEdit()
41+
        self.searchfield.addAction(QIcon.fromTheme("search"), QLineEdit.LeadingPosition)
4642
        predefinedbox.addWidget(self.searchfield)
4743
        self.predefinedprojects = QListWidget()
4844
        with open(os.path.dirname(__file__) + '/../data/data.json') as f:

5349
                self.predefinedprojects.addItem(item)
5450
        predefinedbox.addWidget(self.predefinedprojects)
5551
52+
        self.predefinedprojects.currentItemChanged.connect(self.itemChanged)
53+
54+
        self.searchfield.textChanged.connect(self.filter)
55+
        self.setLayout(predefinedbox)
56+
57+
    def itemChanged(self):
58+
        item = self.currentItem()
59+
        if item is None:
60+
            self.setProperty("data", {})
61+
        self.setProperty("data", item.data(Qt.UserRole))
62+
        self.currentItemChanged.emit()
63+
64+
    def currentItem(self):
65+
        return self.predefinedprojects.currentItem()
66+
67+
    def filter(self):
68+
        search = self.searchfield.text()
69+
        self.predefinedprojects.clear()
70+
        regexp = re.compile(".*"+search)
71+
        for d in self.projectdata:
72+
            if regexp.match(d['name']):
73+
                item = QListWidgetItem(d['name'])
74+
                item.setData(Qt.UserRole, d)
75+
                self.predefinedprojects.addItem(item)
76+
77+
    def currentData(self):
78+
        return self.property("data")
79+
80+
class AdvancedProjectWidget(QWidget):
81+
    modified = pyqtSignal()
82+
83+
    def __init__(self, parent = None, name = "", lang = "", system = 0, info = None):
84+
        super().__init__(parent)
85+
        self.name = name
86+
        self.lang = lang
87+
        self.system = system
88+
        self.info = info
89+
        self._registerAdditionalFields()
90+
        self.initUI()
91+
92+
    def initUI(self):
5693
        contentbox = QVBoxLayout()
5794
        formbox = QGroupBox(self.tr("Project information"))
5895
        self.formLayout = QFormLayout()

73110
        self.nameWidget.textChanged.connect(self.modify)
74111
        self.langWidget.textChanged.connect(self.modify)
75112
76-
        hhbox = QHBoxLayout()
77-
        self.cancelbutton = QPushButton(self.tr("Cancel"))
78-
        self.okbutton = QPushButton(self.tr("OK"))
79-
        self.okbutton.setEnabled(False)
80-
        hhbox.addWidget(self.cancelbutton)
81-
        hhbox.addWidget(self.okbutton)
82113
        contentbox.addWidget(formbox)
83-
        contentbox.addLayout(hhbox)
84-
        hbox.addLayout(predefinedbox)
85-
        hbox.addLayout(contentbox)
86-
87-
        self.additionalFields = []
88-
89-
        for system in systems:
90-
            fields = []
91-
            for spec in system['system'].getProjectConfigSpec():
92-
                if isinstance(spec, StringConfigSpec):
93-
                    widget = QLineEdit()
94-
                    widget.textChanged.connect(self.modify)
95-
                    if spec.placeholder is not None and spec.placeholder != '':
96-
                        widget.setPlaceholderText(spec.placeholder)
97-
                    label = QLabel(spec.name)
98-
                    fields.append({'label': label, 'widget': widget})
99-
                else:
100-
                    raise Exception("Unknown spec type: " + spec)
101-
            self.additionalFields.append(fields)
102-
103-
        self.setLayout(hbox)
104-
105-
        self.predefinedprojects.currentItemChanged.connect(self.fill)
106-
        self.cancelbutton.clicked.connect(self.close)
107-
        self.okbutton.clicked.connect(self.ok)
108-
        self.searchfield.textChanged.connect(self.filter)
109114
        self.combo.currentIndexChanged.connect(self.othersystem)
110-
        self.modify()
111-
        self.othersystem()
112-
113-
    def ok(self):
114-
        self.askNew = True
115-
        self.close()
116115
117-
    def fill(self):
118-
        item = self.predefinedprojects.currentItem()
116+
        self.setLayout(contentbox)
119117
120-
        if item is None:
121-
            return
118+
        self.othersystem()
122119
123-
        data = item.data(Qt.UserRole)
120+
    def fill(self, data):
124121
        self.nameWidget.setText(data['name'])
125122
        self.combo.setCurrentIndex(int(data['system']))
126123

132129
            if spec.key in data:
133130
                widget.setText(data[spec.key])
134131
            i = i + 1
132+
        self.modified.emit()
135133
136-
    def filter(self):
137-
        search = self.searchfield.text()
138-
        self.predefinedprojects.clear()
139-
        regexp = re.compile(".*"+search)
140-
        for d in self.projectdata:
141-
            if regexp.match(d['name']):
142-
                item = QListWidgetItem(d['name'])
143-
                item.setData(Qt.UserRole, d)
144-
                self.predefinedprojects.addItem(item)
145-
146-
    def modify(self):
147-
        enable = False
134+
    def isComplete(self):
135+
        complete = False
148136
        if self.nameWidget.text() != '' and self.langWidget.text() != '':
149-
            enable = True
137+
            complete = True
150138
            system = systems[self.combo.currentIndex()]
151139
            fields = self.additionalFields[self.combo.currentIndex()]
152140
            i = 0

154142
                if not spec.optional:
155143
                    widget = fields[i]['widget']
156144
                    if isinstance(widget, QLineEdit) and widget.text() == '':
157-
                        enable = False
145+
                        complete = False
158146
                        break
159147
                i = i + 1
160-
        self.okbutton.setEnabled(enable)
148+
        return complete
161149
162-
    def wantNew(self):
163-
        return self.askNew
150+
    def _registerAdditionalFields(self):
151+
        self.additionalFields = []
152+
153+
        for system in systems:
154+
            fields = []
155+
            for spec in system['system'].getProjectConfigSpec():
156+
                if isinstance(spec, StringConfigSpec):
157+
                    widget = QLineEdit()
158+
                    widget.textChanged.connect(self.modify)
159+
                    if spec.placeholder is not None and spec.placeholder != '':
160+
                        widget.setPlaceholderText(spec.placeholder)
161+
                    label = QLabel(spec.name)
162+
                    fields.append({'label': label, 'widget': widget})
163+
                else:
164+
                    raise Exception("Unknown spec type: " + spec)
165+
            self.additionalFields.append(fields)
164166
165167
    def getProjectName(self):
166168
        return self.nameWidget.text()

195197
            self.formLayout.addRow(widget['label'], widget['widget'])
196198
            widget['widget'].show()
197199
            widget['label'].show()
198-
        self.setTabOrder(oldwidget, self.cancelbutton)
199-
        self.setTabOrder(self.cancelbutton, self.okbutton)
200200
        self.modify()
201+
202+
    def modify(self):
203+
        data = {'name': self.getProjectName(), 'system': self.getProjectSystem(),
204+
                'system': self.getProjectSystem(), 'info': self.getProjectInfo()}
205+
        self.setProperty("data", data)
206+
        self.modified.emit()
207+
208+
class NewWindow(QDialog):
209+
    def __init__(self, manager, parent = None, name = "", 
210+
            lang = "", system = 0, info = None):
211+
        super().__init__(parent)
212+
        self.name = name
213+
        self.lang = lang
214+
        self.system = system
215+
        self.info = info
216+
        self.manager = manager
217+
        self.askNew = False
218+
        self.initUI()
219+
220+
    def initUI(self):
221+
        vbox = QVBoxLayout()
222+
        hbox = QHBoxLayout()
223+
        self.predefinedprojects = PredefinedProjectWidget(self)
224+
        self.advancedproject = AdvancedProjectWidget(self, self.name, self.lang,
225+
                self.system, self.info)
226+
227+
        hbox.addWidget(self.predefinedprojects)
228+
        hbox.addWidget(self.advancedproject)
229+
230+
        self.cancelbutton = QPushButton(self.tr("Cancel"))
231+
        self.okbutton = QPushButton(self.tr("OK"))
232+
        self.okbutton.setEnabled(False)
233+
        self.cancelbutton.clicked.connect(self.close)
234+
        self.okbutton.clicked.connect(self.ok)
235+
        self.predefinedprojects.currentItemChanged.connect(self.fill)
236+
        self.advancedproject.modified.connect(self.modify)
237+
238+
        hhbox = QHBoxLayout()
239+
        hhbox.addWidget(self.cancelbutton)
240+
        hhbox.addWidget(self.okbutton)
241+
        vbox.addLayout(hbox)
242+
        vbox.addLayout(hhbox)
243+
244+
        self.setLayout(vbox)
245+
246+
        self.modify()
247+
248+
    def ok(self):
249+
        self.askNew = True
250+
        self.close()
251+
252+
    def fill(self):
253+
        item = self.predefinedprojects.currentItem()
254+
255+
        if item is None:
256+
            return
257+
258+
        data = item.data(Qt.UserRole)
259+
        self.advancedproject.fill(data)
260+
261+
    def modify(self):
262+
        self.okbutton.setEnabled(self.advancedproject.isComplete())
263+
264+
    def wantNew(self):
265+
        return self.askNew
266+
267+
    def getProjectName(self):
268+
        return self.advancedproject.getProjectName()
269+
270+
    def getProjectLang(self):
271+
        return self.advancedproject.getProjectLang()
272+
273+
    def getProjectSystem(self):
274+
        return self.advancedproject.getProjectSystem()
275+
276+
    def getProjectInfo(self):
277+
        return self.advancedproject.getProjectInfo()

offlate/ui/welcome.py unknown status 1

1+
#   Copyright (c) 2021 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 datetime
22+
import sys
23+
import os
24+
25+
from translate.lang.data import languages, tr_lang
26+
from .new import PredefinedProjectWidget, AdvancedProjectWidget
27+
from .manager import NewRunnable
28+
from .config.settings import SystemSettingsWidget
29+
30+
WelcomePage = 1
31+
CopyrightPage = 2
32+
LangPage = 3
33+
ProjectPage = 4
34+
DetailPage = 5
35+
SettingsPage = 6
36+
DownloadPage = 7
37+
SuccessPage = 8
38+
HintsPage = 9
39+
40+
class WelcomeWindow(QWizard):
41+
    """
42+
    This it the class that contains the first-time welcome screen, implemented
43+
    as a wizard, to benefit from the page logic of the class. It is mostly
44+
    linera, but we add one optional page to add the details of a project, if
45+
    the first project is not available in the list.
46+
    """
47+
    def __init__(self, manager):
48+
        super().__init__()
49+
        self.manager = manager
50+
        self.initUI()
51+
52+
    def initUI(self):
53+
        center = QDesktopWidget().availableGeometry().center()
54+
        self.addPage(PageWelcome(self))
55+
        self.addPage(PageCopyright(self))
56+
        self.addPage(PageLang(self))
57+
        self.addPage(PageProjectSelection(self))
58+
        self.addPage(PageSystemSettings(self))
59+
        self.addPage(PageDownload(self))
60+
        self.addPage(PageSuccess(self))
61+
        self.addPage(PageHints(self))
62+
        self.setGeometry(center.x()-300, center.y()-225, 600, 450)
63+
        self.setWindowTitle(self.tr('Welcome to Offlate'))
64+
        self.setWizardStyle(QWizard.ModernStyle);
65+
        self.setPixmap(QWizard.LogoPixmap, QPixmap(os.path.dirname(__file__) + '/data/icon.png'));
66+
67+
68+
class PageWelcome(QWizardPage):
69+
    def __init__(self, parent=None):
70+
        super().__init__(parent)
71+
        self.initUI()
72+
73+
    def initUI(self):
74+
        layout = QVBoxLayout()
75+
76+
        self.setTitle("Offlate")
77+
        self.setPixmap(QWizard.WatermarkPixmap, QPixmap(os.path.dirname(__file__) + '/data/illustration.png'))
78+
79+
        #self.setSubTitle(self.tr("Welcome to Offlate!"))
80+
81+
        explainText = QLabel(self)
82+
        explainText.setText(self.tr("Offlate is a tool to help you localise \
83+
free and open source software. Before you start contributing translations to a \
84+
project though, there are a few things we need to set up and talk about. Let's \
85+
get started!"))
86+
        explainText.setWordWrap(True)
87+
        layout.addWidget(explainText)
88+
        
89+
        self.setLayout(layout)
90+
91+
class PageCopyright(QWizardPage):
92+
    def __init__(self, parent):
93+
        super().__init__(parent)
94+
        self.initUI()
95+
96+
    def initUI(self):
97+
        layout = QVBoxLayout()
98+
99+
        self.setTitle(self.tr("Copyright Settings"))
100+
        self.setSubTitle(self.tr("Configuring how your copyright is added to files"))
101+
102+
        explainText = QLabel(self)
103+
        explainText.setText(self.tr("In some cases, we need to add a copyright \
104+
line for you in the translation files we send upstream. Here, you can configure \
105+
how you want the copyright line to look like. This information will most likely \
106+
become public after your first contribution."))
107+
        explainText.setWordWrap(True)
108+
        layout.addWidget(explainText)
109+
110+
        form = QFormLayout()
111+
        self.nameEdit = QLineEdit()
112+
        self.emailEdit = QLineEdit()
113+
        form.addRow(QLabel("Name:"), self.nameEdit)
114+
        form.addRow(QLabel("Email:"), self.emailEdit)
115+
        layout.addLayout(form)
116+
117+
        layout.addSpacing(15)
118+
119+
        previewLabel = QLabel()
120+
        previewLabel.setText(self.tr("Here is how your copyright line will look like:"))
121+
        layout.addWidget(previewLabel)
122+
123+
        layout.addSpacing(9)
124+
125+
        self.previewLine = QLabel()
126+
        self.previewLine.setTextFormat(Qt.RichText)
127+
        self.previewLine.setWordWrap(True)
128+
        self.previewLine.setAlignment(Qt.AlignCenter)
129+
        pal = QPalette()
130+
        pal.setColor(QPalette.Window, Qt.white)
131+
        self.previewLine.setAutoFillBackground(True)
132+
        self.previewLine.setMargin(9)
133+
        self.previewLine.setPalette(pal)
134+
        layout.addWidget(self.previewLine)
135+
        self.updatePreview()
136+
        self.nameEdit.textChanged.connect(self.updatePreview)
137+
        self.emailEdit.textChanged.connect(self.updatePreview)
138+
139+
        self.setLayout(layout)
140+
141+
        self.registerField("name*", self.nameEdit)
142+
        self.registerField("email*", self.emailEdit)
143+
144+
    def updatePreview(self, _ignore=None):
145+
        currentDateTime = datetime.datetime.now()
146+
        date = currentDateTime.date()
147+
        year = date.strftime("%Y")
148+
        name = self.nameEdit.text()
149+
        nameHolder = "<i>{}</i>".format(self.tr("Your Name"))
150+
        email = self.emailEdit.text()
151+
        emailHolder = "<i>{}</i>".format(self.tr("your@email"))
152+
        self.previewLine.setText(self.tr("Copyright &copy; {} {} &lt;{}&gt;").format(year,
153+
            name if name != "" else nameHolder, email if email != "" else emailHolder))
154+
155+
class PageLang(QWizardPage):
156+
    def __init__(self, parent):
157+
        super().__init__(parent)
158+
        self.initUI()
159+
160+
    def initUI(self):
161+
        layout = QVBoxLayout()
162+
163+
        self.setTitle(self.tr("Language Settings"))
164+
        self.setSubTitle(self.tr("Configuring the default language for new projects"))
165+
166+
        explainText = QLabel(self)
167+
        explainText.setText(self.tr("Which language will you most frequently \
168+
translate projects into? This setting can be changed later. It can also be \
169+
overriden by individual projects."))
170+
        explainText.setWordWrap(True)
171+
        layout.addWidget(explainText)
172+
173+
        self.languageCombo = QComboBox()
174+
        self.languageCombo.addItem(self.tr("None"), userData="")
175+
        for lang in languages:
176+
            langtag = lang
177+
            lang = languages[lang]
178+
            langname = lang[0]
179+
            self.languageCombo.addItem(tr_lang()(langname), userData=langtag)
180+
        self.languageCombo.currentIndexChanged.connect(self.comboChanged)
181+
        layout.addWidget(self.languageCombo)
182+
183+
        layout.addSpacing(15)
184+
185+
        warnText = QLabel(self)
186+
        warnText.setText(self.tr("Note that you should only translate into \
187+
native language or to a language you are extremely familiar with, to avoid \
188+
weird or nonsensical translations."))
189+
        warnText.setWordWrap(True)
190+
        layout.addWidget(warnText)
191+
192+
        # TODO: Add information about the selected language, to redirect users
193+
        # to translation teams, resources, etc
194+
195+
        # TODO: Allow users to select an unlisted language, by providing
196+
        # language codes.
197+
198+
        self.setLayout(layout)
199+
200+
        self.langEdit = QLineEdit()
201+
        self.registerField("lang*", self.langEdit)
202+
203+
    def comboChanged(self, index):
204+
        self.langEdit.setText(self.languageCombo.itemData(index))
205+
206+
class PageProjectSelection(QWizardPage):
207+
    def __init__(self, parent):
208+
        super().__init__(parent)
209+
        self.initUI()
210+
211+
    def initUI(self):
212+
        layout = QVBoxLayout()
213+
214+
        self.setTitle(self.tr("First Project"))
215+
        self.setSubTitle(self.tr("Choosing a first project"))
216+
217+
        self.predefinedprojects = PredefinedProjectWidget()
218+
        self.advancedproject = AdvancedProjectWidget()
219+
        self.predefinedprojects.currentItemChanged.connect(self.selected)
220+
        layout.addWidget(self.predefinedprojects)
221+
222+
        self.setLayout(layout)
223+
224+
        self.registerField("project*", self.advancedproject, "data",
225+
                self.advancedproject.modified)
226+
227+
    def selected(self):
228+
        self.advancedproject.fill(self.predefinedprojects.currentData())
229+
230+
class PageSystemSettings(QWizardPage):
231+
    def __init__(self, parent):
232+
        super().__init__(parent)
233+
        self.parent = parent
234+
        self.initUI()
235+
236+
    def initUI(self):
237+
        self.setTitle(self.tr("System Configuration"))
238+
        self.setSubTitle(self.tr("Getting an account on the project's platform"))
239+
240+
    def initializePage(self):
241+
        layout = QVBoxLayout()
242+
        system = self.field("project")["system"]
243+
        lang = self.field("lang")
244+
        email = self.field("email")
245+
        name = self.field("name")
246+
        self.parent.manager.updateSettings({'Generic': {'name': name, 'email': email, 'lang': lang}})
247+
        widget = SystemSettingsWidget(self.parent.manager, system, self)
248+
        layout.addWidget(widget)
249+
        self.setLayout(layout)
250+
251+
252+
class PageDownload(QWizardPage):
253+
    def __init__(self, parent):
254+
        super().__init__(parent)
255+
        self.parent = parent
256+
        self.threadpool = QThreadPool()
257+
        self.initUI()
258+
259+
    def initUI(self):
260+
        layout = QVBoxLayout()
261+
        self.setTitle(self.tr("Project Fetch"))
262+
        self.setSubTitle(self.tr("Downloading translation files"))
263+
264+
        explainText = QLabel(self)
265+
        explainText.setText(self.tr("We are now attempting to fetch the \
266+
translation files of your project. This step should be pretty fast."))
267+
        explainText.setWordWrap(True)
268+
        layout.addWidget(explainText)
269+
270+
        self.bar = QProgressBar()
271+
        self.bar.setEnabled(True)
272+
        self.bar.setRange(0, 100)
273+
        layout.addWidget(self.bar)
274+
275+
        layout.addSpacing(15)
276+
277+
        pal = QPalette()
278+
        pal.setColor(QPalette.WindowText, Qt.red)
279+
280+
        self.notConfigured = QLabel(self)
281+
        self.notConfigured.setText("The platform is not properly configured, \
282+
please go back.")
283+
        self.notConfigured.setWordWrap(True)
284+
        self.notConfigured.hide()
285+
        self.notConfigured.setPalette(pal)
286+
        layout.addWidget(self.notConfigured)
287+
288+
        self.errorLabel = QLabel(self)
289+
        self.errorLabel.setWordWrap(True)
290+
        self.errorLabel.hide()
291+
        self.errorLabel.setPalette(pal)
292+
        layout.addWidget(self.errorLabel)
293+
294+
        self.finishedLabel = QLabel(self)
295+
        self.finishedLabel.setText(self.tr("Finished!"))
296+
        self.finishedLabel.setWordWrap(True)
297+
        self.finishedLabel.hide()
298+
        layout.addWidget(self.finishedLabel)
299+
300+
        self.setLayout(layout)
301+
302+
        self.check = QCheckBox()
303+
        self.check.setCheckState(Qt.Unchecked)
304+
        self.registerField("downloaded*", self.check)
305+
306+
    def initializePage(self):
307+
        self.check.setCheckState(Qt.Unchecked)
308+
        project = self.field("project")
309+
        lang = self.field("lang")
310+
        email = self.field("email")
311+
        name = self.field("name")
312+
        self.bar.setValue(0)
313+
        self.notConfigured.hide()
314+
        self.errorLabel.hide()
315+
        self.finishedLabel.hide()
316+
        if not self.parent.manager.isConfigured(project['system'], project['info']):
317+
            self.notConfigured.show()
318+
            return
319+
320+
        self.manager = self.parent.manager
321+
        worker = NewRunnable(self, project['name'], lang, project['system'],
322+
                project['info'])
323+
        worker.signals.finished.connect(self.reportFinish)
324+
        worker.signals.progress.connect(self.reportProgress)
325+
        worker.signals.restart_required.connect(self.reportError)
326+
        self.threadpool.start(worker)
327+
328+
    def reportProgress(self, name, progress):
329+
        self.bar.setValue(progress)
330+
331+
    def reportError(self, name, lang, system, info, error):
332+
        self.errorLabel.setText(error)
333+
        self.errorLabel.show()
334+
335+
    def reportFinish(self, name):
336+
        self.bar.setValue(100)
337+
        self.finishedLabel.show()
338+
        self.check.setCheckState(Qt.Checked)
339+
340+
class PageSuccess(QWizardPage):
341+
    def __init__(self, parent):
342+
        super().__init__(parent)
343+
        self.parent = parent
344+
        self.initUI()
345+
346+
    def initUI(self):
347+
        layout = QVBoxLayout()
348+
        self.setTitle("Success")
349+
        self.setPixmap(QWizard.WatermarkPixmap, QPixmap(os.path.dirname(__file__) + '/data/illustration.png'))
350+
351+
        explainText = QLabel(self)
352+
        explainText.setText(self.tr("Congratulations! We've just set up your \
353+
first project! Initial setup is now complete, and you can always change the \
354+
settings you've set here from the Offlate welcome screen. After a few \
355+
explanations you will be able to work on your project right away!"))
356+
        explainText.setWordWrap(True)
357+
        layout.addWidget(explainText)
358+
        
359+
        self.setLayout(layout)
360+
361+
    def initializePage(self):
362+
        self.parent.manager.updateSettings({'Generic': {'new': 'False'}})
363+
364+
class PageHints(QWizardPage):
365+
    def __init__(self, parent):
366+
        super().__init__(parent)
367+
        self.initUI()
368+
369+
    def initUI(self):
370+
        layout = QVBoxLayout()
371+
        self.setTitle("Offlate Hints")
372+
        self.setPixmap(QWizard.WatermarkPixmap, QPixmap(os.path.dirname(__file__) + '/data/illustration.png'))
373+
374+
        explainManager = QLabel(self)
375+
        explainManager.setText(self.tr("You can always go back to the initial \
376+
screen (called the project manager) from the editor, by using \
377+
<i>file > project manager</i>"))
378+
        explainManager.setWordWrap(True)
379+
        layout.addWidget(explainManager)
380+
381+
        layout.addSpacing(15)
382+
383+
        explainManager = QLabel(self)
384+
        explainManager.setText(self.tr("Quickly switch to the next string with \
385+
<i>Ctrl+Enter</i>. Use <i>Ctrl+Shift+Enter</i> to go back to the previous string \
386+
instead."))
387+
        explainManager.setWordWrap(True)
388+
        layout.addWidget(explainManager)
389+
390+
        layout.addSpacing(15)
391+
392+
        explainManager = QLabel(self)
393+
        explainManager.setText(self.tr("With these hints, you are now ready \
394+
to start working on your project! We will now open the project manager, from \
395+
which you can add more projects, change your settings, etc. Have fun!"))
396+
        explainManager.setWordWrap(True)
397+
        layout.addWidget(explainManager)
398+
399+
        self.setLayout(layout)