excel2moodle 0.6.1__py3-none-any.whl → 0.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- excel2moodle/__init__.py +2 -0
- excel2moodle/__main__.py +18 -3
- excel2moodle/core/bullets.py +2 -2
- excel2moodle/core/category.py +27 -8
- excel2moodle/core/dataStructure.py +122 -32
- excel2moodle/core/etHelpers.py +0 -20
- excel2moodle/core/globals.py +0 -9
- excel2moodle/core/parser.py +21 -13
- excel2moodle/core/question.py +43 -23
- excel2moodle/core/settings.py +16 -5
- excel2moodle/extra/equationVerification.py +0 -2
- excel2moodle/extra/updateQuery.py +48 -0
- excel2moodle/extra/variableGenerator.py +73 -49
- excel2moodle/question_types/cloze.py +119 -87
- excel2moodle/question_types/mc.py +15 -10
- excel2moodle/question_types/nf.py +7 -1
- excel2moodle/question_types/nfm.py +14 -10
- excel2moodle/ui/UI_exportSettingsDialog.py +60 -14
- excel2moodle/ui/UI_updateDlg.py +106 -0
- excel2moodle/ui/appUi.py +66 -24
- excel2moodle/ui/dialogs.py +30 -1
- excel2moodle/ui/treewidget.py +30 -10
- {excel2moodle-0.6.1.dist-info → excel2moodle-0.6.3.dist-info}/METADATA +66 -19
- excel2moodle-0.6.3.dist-info/RECORD +41 -0
- excel2moodle-0.6.1.dist-info/RECORD +0 -39
- {excel2moodle-0.6.1.dist-info → excel2moodle-0.6.3.dist-info}/WHEEL +0 -0
- {excel2moodle-0.6.1.dist-info → excel2moodle-0.6.3.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.6.1.dist-info → excel2moodle-0.6.3.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.6.1.dist-info → excel2moodle-0.6.3.dist-info}/top_level.txt +0 -0
@@ -22,6 +22,7 @@ class NFMQuestion(ParametricQuestion):
|
|
22
22
|
def __init__(self, *args, **kwargs) -> None:
|
23
23
|
super().__init__(*args, **kwargs)
|
24
24
|
self.answerElement: ET.Element
|
25
|
+
self.answerElementWrongSign: ET.Element
|
25
26
|
|
26
27
|
def getUpdatedElement(self, variant: int = 1) -> ET.Element:
|
27
28
|
"""Update and get the Question Elements to reflect the version.
|
@@ -33,14 +34,16 @@ class NFMQuestion(ParametricQuestion):
|
|
33
34
|
result = self.parametrics.getResult(variant)
|
34
35
|
tolerance = round(self.rawData.get(Tags.TOLERANCE) * result, 3)
|
35
36
|
self.answerElement.find(XMLTags.TEXT).text = str(result)
|
37
|
+
self.answerElementWrongSign.find(XMLTags.TEXT).text = str(result * (-1))
|
36
38
|
self.answerElement.find(XMLTags.TOLERANCE).text = str(tolerance)
|
39
|
+
self.answerElementWrongSign.find(XMLTags.TOLERANCE).text = str(tolerance)
|
37
40
|
return super().getUpdatedElement(variant)
|
38
41
|
|
39
42
|
|
40
43
|
class NFMQuestionParser(QuestionParser):
|
41
44
|
def __init__(self) -> None:
|
42
45
|
super().__init__()
|
43
|
-
self.
|
46
|
+
self.feedBackList = {XMLTags.GENFEEDB: Tags.GENERALFB}
|
44
47
|
self.question: NFMQuestion
|
45
48
|
|
46
49
|
def setup(self, question: NFMQuestion) -> None:
|
@@ -61,13 +64,14 @@ class NFMQuestionParser(QuestionParser):
|
|
61
64
|
)
|
62
65
|
self.question.parametrics.variables = variables
|
63
66
|
self.question.answerElement = self.getNumericAnsElement()
|
64
|
-
|
67
|
+
self.question.answerElementWrongSign = self.getNumericAnsElement(
|
68
|
+
fraction=self.rawInput.get(Tags.WRONGSIGNPERCENT),
|
69
|
+
feedback=self.rawInput.get(Tags.WRONGSIGNFB),
|
70
|
+
)
|
71
|
+
return [self.question.answerElement, self.question.answerElementWrongSign]
|
65
72
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
# self.question.category.maxVariants = number
|
72
|
-
# else:
|
73
|
-
# self.question.category.maxVariants = min(number, mvar)
|
73
|
+
def _finalizeParsing(self) -> None:
|
74
|
+
self.tmpEle.append(
|
75
|
+
self.getFeedBEle(XMLTags.GENFEEDB, text=self.rawInput.get(Tags.GENERALFB))
|
76
|
+
)
|
77
|
+
return super()._finalizeParsing()
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
|
3
3
|
################################################################################
|
4
|
-
## Form generated from reading UI file '
|
4
|
+
## Form generated from reading UI file 'UI_exportSettingsDialog.ui'
|
5
5
|
##
|
6
6
|
## Created by: Qt User Interface Compiler version 6.9.1
|
7
7
|
##
|
@@ -43,38 +43,42 @@ class Ui_ExportDialog(object):
|
|
43
43
|
self.label_9 = QLabel(ExportDialog)
|
44
44
|
self.label_9.setObjectName(u"label_9")
|
45
45
|
|
46
|
-
self.formLayout_2.setWidget(
|
46
|
+
self.formLayout_2.setWidget(2, QFormLayout.ItemRole.LabelRole, self.label_9)
|
47
47
|
|
48
48
|
self.checkBoxIncludeCategories = QCheckBox(ExportDialog)
|
49
49
|
self.checkBoxIncludeCategories.setObjectName(u"checkBoxIncludeCategories")
|
50
50
|
|
51
|
-
self.formLayout_2.setWidget(
|
51
|
+
self.formLayout_2.setWidget(2, QFormLayout.ItemRole.FieldRole, self.checkBoxIncludeCategories)
|
52
52
|
|
53
53
|
self.label = QLabel(ExportDialog)
|
54
54
|
self.label.setObjectName(u"label")
|
55
|
+
font = QFont()
|
56
|
+
font.setPointSize(12)
|
57
|
+
font.setBold(True)
|
58
|
+
self.label.setFont(font)
|
55
59
|
|
56
|
-
self.formLayout_2.setWidget(
|
60
|
+
self.formLayout_2.setWidget(4, QFormLayout.ItemRole.LabelRole, self.label)
|
57
61
|
|
58
62
|
self.btnExportFile = QPushButton(ExportDialog)
|
59
63
|
self.btnExportFile.setObjectName(u"btnExportFile")
|
60
64
|
self.btnExportFile.setMinimumSize(QSize(200, 0))
|
61
65
|
|
62
|
-
self.formLayout_2.setWidget(
|
66
|
+
self.formLayout_2.setWidget(4, QFormLayout.ItemRole.FieldRole, self.btnExportFile)
|
63
67
|
|
64
68
|
self.line_2 = QFrame(ExportDialog)
|
65
69
|
self.line_2.setObjectName(u"line_2")
|
66
70
|
self.line_2.setFrameShape(QFrame.Shape.HLine)
|
67
71
|
self.line_2.setFrameShadow(QFrame.Shadow.Sunken)
|
68
72
|
|
69
|
-
self.formLayout_2.setWidget(
|
73
|
+
self.formLayout_2.setWidget(5, QFormLayout.ItemRole.LabelRole, self.line_2)
|
70
74
|
|
71
75
|
self.label_2 = QLabel(ExportDialog)
|
72
76
|
self.label_2.setObjectName(u"label_2")
|
73
|
-
|
74
|
-
|
75
|
-
self.label_2.setFont(
|
77
|
+
font1 = QFont()
|
78
|
+
font1.setPointSize(12)
|
79
|
+
self.label_2.setFont(font1)
|
76
80
|
|
77
|
-
self.formLayout_2.setWidget(
|
81
|
+
self.formLayout_2.setWidget(6, QFormLayout.ItemRole.LabelRole, self.label_2)
|
78
82
|
|
79
83
|
self.questionCount = QSpinBox(ExportDialog)
|
80
84
|
self.questionCount.setObjectName(u"questionCount")
|
@@ -82,7 +86,7 @@ class Ui_ExportDialog(object):
|
|
82
86
|
self.questionCount.setButtonSymbols(QAbstractSpinBox.ButtonSymbols.NoButtons)
|
83
87
|
self.questionCount.setMaximum(999)
|
84
88
|
|
85
|
-
self.formLayout_2.setWidget(
|
89
|
+
self.formLayout_2.setWidget(7, QFormLayout.ItemRole.LabelRole, self.questionCount)
|
86
90
|
|
87
91
|
self.pointCount = QDoubleSpinBox(ExportDialog)
|
88
92
|
self.pointCount.setObjectName(u"pointCount")
|
@@ -93,7 +97,27 @@ class Ui_ExportDialog(object):
|
|
93
97
|
self.pointCount.setDecimals(1)
|
94
98
|
self.pointCount.setMaximum(1000.000000000000000)
|
95
99
|
|
96
|
-
self.formLayout_2.setWidget(
|
100
|
+
self.formLayout_2.setWidget(7, QFormLayout.ItemRole.FieldRole, self.pointCount)
|
101
|
+
|
102
|
+
self.label_3 = QLabel(ExportDialog)
|
103
|
+
self.label_3.setObjectName(u"label_3")
|
104
|
+
|
105
|
+
self.formLayout_2.setWidget(1, QFormLayout.ItemRole.LabelRole, self.label_3)
|
106
|
+
|
107
|
+
self.checkBoxExportAll = QCheckBox(ExportDialog)
|
108
|
+
self.checkBoxExportAll.setObjectName(u"checkBoxExportAll")
|
109
|
+
|
110
|
+
self.formLayout_2.setWidget(1, QFormLayout.ItemRole.FieldRole, self.checkBoxExportAll)
|
111
|
+
|
112
|
+
self.label_4 = QLabel(ExportDialog)
|
113
|
+
self.label_4.setObjectName(u"label_4")
|
114
|
+
|
115
|
+
self.formLayout_2.setWidget(3, QFormLayout.ItemRole.LabelRole, self.label_4)
|
116
|
+
|
117
|
+
self.checkBoxGenerateReport = QCheckBox(ExportDialog)
|
118
|
+
self.checkBoxGenerateReport.setObjectName(u"checkBoxGenerateReport")
|
119
|
+
|
120
|
+
self.formLayout_2.setWidget(3, QFormLayout.ItemRole.FieldRole, self.checkBoxGenerateReport)
|
97
121
|
|
98
122
|
|
99
123
|
self.horizontalLayout.addLayout(self.formLayout_2)
|
@@ -117,14 +141,36 @@ class Ui_ExportDialog(object):
|
|
117
141
|
ExportDialog.setWindowTitle(QCoreApplication.translate("ExportDialog", u"Dialog", None))
|
118
142
|
self.label_10.setText(QCoreApplication.translate("ExportDialog", u"Default Question Variant", None))
|
119
143
|
#if QT_CONFIG(tooltip)
|
120
|
-
self.label_9.setToolTip(
|
144
|
+
self.label_9.setToolTip("")
|
121
145
|
#endif // QT_CONFIG(tooltip)
|
122
146
|
self.label_9.setText(QCoreApplication.translate("ExportDialog", u"Include Categories", None))
|
147
|
+
#if QT_CONFIG(tooltip)
|
148
|
+
self.checkBoxIncludeCategories.setToolTip(QCoreApplication.translate("ExportDialog", u"If enabled, all questions will be categorized, when importing into moodle. Otherwise they will all be imported into one category", None))
|
149
|
+
#endif // QT_CONFIG(tooltip)
|
123
150
|
self.checkBoxIncludeCategories.setText("")
|
124
|
-
self.label.setText(QCoreApplication.translate("ExportDialog", u"Export File:", None))
|
151
|
+
self.label.setText(QCoreApplication.translate("ExportDialog", u"Select Export File:", None))
|
152
|
+
#if QT_CONFIG(tooltip)
|
153
|
+
self.btnExportFile.setToolTip(QCoreApplication.translate("ExportDialog", u"Select the file to write the questions into.", None))
|
154
|
+
#endif // QT_CONFIG(tooltip)
|
125
155
|
self.btnExportFile.setText("")
|
126
156
|
self.label_2.setText(QCoreApplication.translate("ExportDialog", u"Export Information:", None))
|
127
157
|
self.questionCount.setSuffix(QCoreApplication.translate("ExportDialog", u" Qst.", None))
|
128
158
|
self.pointCount.setSuffix(QCoreApplication.translate("ExportDialog", u" Pts.", None))
|
159
|
+
#if QT_CONFIG(tooltip)
|
160
|
+
self.label_3.setToolTip("")
|
161
|
+
#endif // QT_CONFIG(tooltip)
|
162
|
+
self.label_3.setText(QCoreApplication.translate("ExportDialog", u"Export all Variants", None))
|
163
|
+
#if QT_CONFIG(tooltip)
|
164
|
+
self.checkBoxExportAll.setToolTip(QCoreApplication.translate("ExportDialog", u"If enable all variants for each question are exported into a seperate category for each question", None))
|
165
|
+
#endif // QT_CONFIG(tooltip)
|
166
|
+
self.checkBoxExportAll.setText("")
|
167
|
+
#if QT_CONFIG(tooltip)
|
168
|
+
self.label_4.setToolTip("")
|
169
|
+
#endif // QT_CONFIG(tooltip)
|
170
|
+
self.label_4.setText(QCoreApplication.translate("ExportDialog", u"Generate export report", None))
|
171
|
+
#if QT_CONFIG(tooltip)
|
172
|
+
self.checkBoxGenerateReport.setToolTip(QCoreApplication.translate("ExportDialog", u"If enabled a yaml report file will be saved with a list of all exported questions and variants.", None))
|
173
|
+
#endif // QT_CONFIG(tooltip)
|
174
|
+
self.checkBoxGenerateReport.setText("")
|
129
175
|
# retranslateUi
|
130
176
|
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
################################################################################
|
4
|
+
## Form generated from reading UI file 'UI_updateDlg.ui'
|
5
|
+
##
|
6
|
+
## Created by: Qt User Interface Compiler version 6.9.1
|
7
|
+
##
|
8
|
+
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
9
|
+
################################################################################
|
10
|
+
|
11
|
+
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
12
|
+
QMetaObject, QObject, QPoint, QRect,
|
13
|
+
QSize, QTime, QUrl, Qt)
|
14
|
+
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
15
|
+
QFont, QFontDatabase, QGradient, QIcon,
|
16
|
+
QImage, QKeySequence, QLinearGradient, QPainter,
|
17
|
+
QPalette, QPixmap, QRadialGradient, QTransform)
|
18
|
+
from PySide6.QtWidgets import (QAbstractButton, QAbstractScrollArea, QApplication, QDialog,
|
19
|
+
QDialogButtonBox, QFrame, QLabel, QSizePolicy,
|
20
|
+
QTextBrowser, QVBoxLayout, QWidget)
|
21
|
+
|
22
|
+
class Ui_UpdateDialog(object):
|
23
|
+
def setupUi(self, UpdateDialog):
|
24
|
+
if not UpdateDialog.objectName():
|
25
|
+
UpdateDialog.setObjectName(u"UpdateDialog")
|
26
|
+
UpdateDialog.setWindowModality(Qt.WindowModality.NonModal)
|
27
|
+
UpdateDialog.resize(534, 512)
|
28
|
+
UpdateDialog.setModal(True)
|
29
|
+
self.verticalLayout = QVBoxLayout(UpdateDialog)
|
30
|
+
self.verticalLayout.setObjectName(u"verticalLayout")
|
31
|
+
self.titleLabel = QLabel(UpdateDialog)
|
32
|
+
self.titleLabel.setObjectName(u"titleLabel")
|
33
|
+
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
|
34
|
+
sizePolicy.setHorizontalStretch(0)
|
35
|
+
sizePolicy.setVerticalStretch(0)
|
36
|
+
sizePolicy.setHeightForWidth(self.titleLabel.sizePolicy().hasHeightForWidth())
|
37
|
+
self.titleLabel.setSizePolicy(sizePolicy)
|
38
|
+
|
39
|
+
self.verticalLayout.addWidget(self.titleLabel)
|
40
|
+
|
41
|
+
self.fundingLabel = QLabel(UpdateDialog)
|
42
|
+
self.fundingLabel.setObjectName(u"fundingLabel")
|
43
|
+
sizePolicy.setHeightForWidth(self.fundingLabel.sizePolicy().hasHeightForWidth())
|
44
|
+
self.fundingLabel.setSizePolicy(sizePolicy)
|
45
|
+
self.fundingLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
46
|
+
self.fundingLabel.setOpenExternalLinks(True)
|
47
|
+
|
48
|
+
self.verticalLayout.addWidget(self.fundingLabel)
|
49
|
+
|
50
|
+
self.line = QFrame(UpdateDialog)
|
51
|
+
self.line.setObjectName(u"line")
|
52
|
+
self.line.setFrameShape(QFrame.Shape.HLine)
|
53
|
+
self.line.setFrameShadow(QFrame.Shadow.Sunken)
|
54
|
+
|
55
|
+
self.verticalLayout.addWidget(self.line)
|
56
|
+
|
57
|
+
self.changelogLabel = QLabel(UpdateDialog)
|
58
|
+
self.changelogLabel.setObjectName(u"changelogLabel")
|
59
|
+
sizePolicy.setHeightForWidth(self.changelogLabel.sizePolicy().hasHeightForWidth())
|
60
|
+
self.changelogLabel.setSizePolicy(sizePolicy)
|
61
|
+
|
62
|
+
self.verticalLayout.addWidget(self.changelogLabel)
|
63
|
+
|
64
|
+
self.changelogBrowser = QTextBrowser(UpdateDialog)
|
65
|
+
self.changelogBrowser.setObjectName(u"changelogBrowser")
|
66
|
+
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
67
|
+
sizePolicy1.setHorizontalStretch(0)
|
68
|
+
sizePolicy1.setVerticalStretch(0)
|
69
|
+
sizePolicy1.setHeightForWidth(self.changelogBrowser.sizePolicy().hasHeightForWidth())
|
70
|
+
self.changelogBrowser.setSizePolicy(sizePolicy1)
|
71
|
+
self.changelogBrowser.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
72
|
+
self.changelogBrowser.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
|
73
|
+
self.changelogBrowser.setSizeAdjustPolicy(QAbstractScrollArea.SizeAdjustPolicy.AdjustToContents)
|
74
|
+
|
75
|
+
self.verticalLayout.addWidget(self.changelogBrowser)
|
76
|
+
|
77
|
+
self.label = QLabel(UpdateDialog)
|
78
|
+
self.label.setObjectName(u"label")
|
79
|
+
sizePolicy.setHeightForWidth(self.label.sizePolicy().hasHeightForWidth())
|
80
|
+
self.label.setSizePolicy(sizePolicy)
|
81
|
+
|
82
|
+
self.verticalLayout.addWidget(self.label)
|
83
|
+
|
84
|
+
self.buttonBox = QDialogButtonBox(UpdateDialog)
|
85
|
+
self.buttonBox.setObjectName(u"buttonBox")
|
86
|
+
self.buttonBox.setOrientation(Qt.Orientation.Horizontal)
|
87
|
+
self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Ok)
|
88
|
+
|
89
|
+
self.verticalLayout.addWidget(self.buttonBox)
|
90
|
+
|
91
|
+
|
92
|
+
self.retranslateUi(UpdateDialog)
|
93
|
+
self.buttonBox.accepted.connect(UpdateDialog.accept)
|
94
|
+
self.buttonBox.rejected.connect(UpdateDialog.reject)
|
95
|
+
|
96
|
+
QMetaObject.connectSlotsByName(UpdateDialog)
|
97
|
+
# setupUi
|
98
|
+
|
99
|
+
def retranslateUi(self, UpdateDialog):
|
100
|
+
UpdateDialog.setWindowTitle(QCoreApplication.translate("UpdateDialog", u"Dialog", None))
|
101
|
+
self.titleLabel.setText(QCoreApplication.translate("UpdateDialog", u"<h2>A new <i>excel2moodle</i> version is available!</h2>", None))
|
102
|
+
self.fundingLabel.setText(QCoreApplication.translate("UpdateDialog", u"If you find this project useful, please consider supporting its development.", None))
|
103
|
+
self.changelogLabel.setText(QCoreApplication.translate("UpdateDialog", u"<h3>Changelog:</h3>", None))
|
104
|
+
self.label.setText(QCoreApplication.translate("UpdateDialog", u"To install the update run: 'uv tool upgrade excel2moodle'", None))
|
105
|
+
# retranslateUi
|
106
|
+
|
excel2moodle/ui/appUi.py
CHANGED
@@ -21,6 +21,7 @@ from PySide6.QtWidgets import (
|
|
21
21
|
from excel2moodle import e2mMetadata, mainLogger
|
22
22
|
from excel2moodle.core.category import Category
|
23
23
|
from excel2moodle.core.dataStructure import QuestionDB
|
24
|
+
from excel2moodle.core.exceptions import InvalidFieldException
|
24
25
|
from excel2moodle.core.question import ParametricQuestion
|
25
26
|
from excel2moodle.core.settings import Settings, Tags
|
26
27
|
from excel2moodle.extra.variableGenerator import VariableGeneratorDialog
|
@@ -34,15 +35,14 @@ from excel2moodle.ui.UI_mainWindow import Ui_MoodleTestGenerator
|
|
34
35
|
|
35
36
|
logger = logging.getLogger(__name__)
|
36
37
|
|
37
|
-
loggerSignal = LogWindowHandler()
|
38
|
-
mainLogger.addHandler(loggerSignal)
|
39
|
-
|
40
38
|
|
41
39
|
class MainWindow(QMainWindow):
|
42
40
|
def __init__(self, settings: Settings, testDB: QuestionDB) -> None:
|
43
41
|
super().__init__()
|
44
42
|
self.settings = settings
|
45
43
|
self.qSettings = QSettings("jbosse3", "excel2moodle")
|
44
|
+
self.logHandler = LogWindowHandler()
|
45
|
+
mainLogger.addHandler(self.logHandler)
|
46
46
|
logger.info("Settings are stored under: %s", self.qSettings.fileName())
|
47
47
|
|
48
48
|
self.excelPath: Path | None = None
|
@@ -73,9 +73,12 @@ class MainWindow(QMainWindow):
|
|
73
73
|
self.exportDialog.ui.checkBoxIncludeCategories.setChecked(
|
74
74
|
self.qSettings.value(Tags.INCLUDEINCATS, defaultValue=True, type=bool)
|
75
75
|
)
|
76
|
-
self.
|
77
|
-
|
78
|
-
|
76
|
+
variant = self.qSettings.value(Tags.QUESTIONVARIANT, defaultValue=1, type=int)
|
77
|
+
if variant == -1:
|
78
|
+
self.exportDialog.ui.checkBoxExportAll.setChecked(True)
|
79
|
+
self.exportDialog.ui.spinBoxDefaultQVariant.setEnabled(False)
|
80
|
+
else:
|
81
|
+
self.exportDialog.ui.spinBoxDefaultQVariant.setValue(variant)
|
79
82
|
try:
|
80
83
|
self.resize(self.qSettings.value("windowSize"))
|
81
84
|
self.move(self.qSettings.value("windowPosition"))
|
@@ -90,7 +93,7 @@ class MainWindow(QMainWindow):
|
|
90
93
|
self.ui.checkBoxQuestionListSelectAll.checkStateChanged.connect(
|
91
94
|
self.toggleQuestionSelectionState,
|
92
95
|
)
|
93
|
-
|
96
|
+
self.logHandler.emitter.signal.connect(self.updateLog)
|
94
97
|
self.ui.actionEquationChecker.triggered.connect(self.openEqCheckerDlg)
|
95
98
|
self.ui.actionParseAll.triggered.connect(self.parseSpreadsheetAll)
|
96
99
|
self.testDB.signals.categoryQuestionsReady.connect(self.treeRefreshCategory)
|
@@ -107,6 +110,10 @@ class MainWindow(QMainWindow):
|
|
107
110
|
self.openSpreadsheetExternally
|
108
111
|
)
|
109
112
|
|
113
|
+
def showUpdateDialog(self, changelog, version) -> None:
|
114
|
+
dialog = dialogs.UpdateDialog(self, changelog=changelog, version=version)
|
115
|
+
dialog.exec()
|
116
|
+
|
110
117
|
@Slot()
|
111
118
|
def parseSpreadsheetAll(self) -> None:
|
112
119
|
"""Event triggered by the *Tools/Parse all Questions* Event.
|
@@ -156,7 +163,7 @@ class MainWindow(QMainWindow):
|
|
156
163
|
selection = self.ui.treeWidget.selectedItems()
|
157
164
|
for q in selection:
|
158
165
|
questions += 1
|
159
|
-
count += q.
|
166
|
+
count += q.question.points
|
160
167
|
self.ui.pointCounter.setValue(count)
|
161
168
|
self.ui.questionCounter.setValue(questions)
|
162
169
|
if self.eqChecker.isVisible():
|
@@ -177,8 +184,10 @@ class MainWindow(QMainWindow):
|
|
177
184
|
"""Open a file Dialog so the export file may be choosen."""
|
178
185
|
selection: list[QuestionItem] = self.ui.treeWidget.selectedItems()
|
179
186
|
self.exportDialog.exportFile = Path(self.mainPath / "TestFile.xml")
|
180
|
-
self.
|
181
|
-
self.
|
187
|
+
qCount = self.ui.questionCounter.value()
|
188
|
+
pCount = self.ui.pointCounter.value()
|
189
|
+
self.exportDialog.ui.questionCount.setValue(qCount)
|
190
|
+
self.exportDialog.ui.pointCount.setValue(pCount)
|
182
191
|
if self.exportDialog.exec():
|
183
192
|
self.exportFile = self.exportDialog.exportFile
|
184
193
|
self.settings.set(
|
@@ -187,10 +196,18 @@ class MainWindow(QMainWindow):
|
|
187
196
|
)
|
188
197
|
self.settings.set(
|
189
198
|
Tags.QUESTIONVARIANT,
|
190
|
-
|
199
|
+
-1
|
200
|
+
if self.exportDialog.ui.checkBoxExportAll.isChecked()
|
201
|
+
else self.exportDialog.ui.spinBoxDefaultQVariant.value(),
|
202
|
+
)
|
203
|
+
self.settings.set(
|
204
|
+
Tags.GENEXPORTREPORT,
|
205
|
+
self.exportDialog.ui.checkBoxGenerateReport.isChecked(),
|
191
206
|
)
|
192
207
|
logger.info("New Export File is set %s", self.exportFile)
|
193
|
-
self.testDB.appendQuestions(
|
208
|
+
self.testDB.appendQuestions(
|
209
|
+
selection, self.exportFile, pCount=pCount, qCount=qCount
|
210
|
+
)
|
194
211
|
else:
|
195
212
|
logger.info("Aborting Export")
|
196
213
|
|
@@ -208,18 +225,33 @@ class MainWindow(QMainWindow):
|
|
208
225
|
|
209
226
|
@Slot(Category)
|
210
227
|
def treeRefreshCategory(self, cat: Category) -> None:
|
211
|
-
"""Append Category with its Questions to the treewidget.
|
228
|
+
"""Append Category with its Questions to the treewidget.
|
229
|
+
|
230
|
+
If the category already has an item, refresh it.
|
231
|
+
"""
|
232
|
+
# Find existing item
|
233
|
+
for i in range(self.ui.treeWidget.topLevelItemCount()):
|
234
|
+
item = self.ui.treeWidget.topLevelItem(i)
|
235
|
+
# The top level items are categories
|
236
|
+
if isinstance(item, CategoryItem) and item.category.NAME == cat.NAME:
|
237
|
+
item.refresh()
|
238
|
+
return
|
239
|
+
|
212
240
|
catItem = CategoryItem(self.ui.treeWidget, cat)
|
213
241
|
catItem.setFlags(catItem.flags() & ~Qt.ItemIsSelectable)
|
214
|
-
|
215
|
-
|
242
|
+
try:
|
243
|
+
for q in cat.questions.values():
|
244
|
+
QuestionItem(catItem, q)
|
245
|
+
except ValueError:
|
246
|
+
logger.exception("No Questions to update.")
|
247
|
+
catItem.updateVariantCount()
|
216
248
|
self.ui.treeWidget.sortItems(0, Qt.SortOrder.AscendingOrder)
|
217
249
|
|
218
250
|
@Slot()
|
219
251
|
def updateQuestionPreview(self) -> None:
|
220
252
|
item = self.ui.treeWidget.currentItem()
|
221
253
|
if isinstance(item, QuestionItem):
|
222
|
-
self.questionPreview.setupQuestion(item.
|
254
|
+
self.questionPreview.setupQuestion(item.question)
|
223
255
|
else:
|
224
256
|
logger.info("current Item is not a Question, can't preview")
|
225
257
|
|
@@ -231,12 +263,12 @@ class MainWindow(QMainWindow):
|
|
231
263
|
def openEqCheckerDlg(self) -> None:
|
232
264
|
item = self.ui.treeWidget.currentItem()
|
233
265
|
if isinstance(item, QuestionItem):
|
234
|
-
question = item.
|
266
|
+
question = item.question
|
235
267
|
if isinstance(question, (NFQuestion, MCQuestion)):
|
236
268
|
logger.debug("Can't check an MC or NF Question")
|
237
269
|
else:
|
238
270
|
logger.debug("opening wEquationChecker \n")
|
239
|
-
self.eqChecker.setup(item.
|
271
|
+
self.eqChecker.setup(item.question)
|
240
272
|
self.eqChecker.show()
|
241
273
|
else:
|
242
274
|
logger.debug("No Question Item selected: %s", type(item))
|
@@ -249,17 +281,19 @@ class MainWindow(QMainWindow):
|
|
249
281
|
@Slot()
|
250
282
|
def openDocumentation(self) -> None:
|
251
283
|
url = QUrl(e2mMetadata["documentation"])
|
284
|
+
logger.info("Opening documentation in your Webbrowser...%s", url)
|
252
285
|
QDesktopServices.openUrl(url)
|
253
286
|
|
254
287
|
@Slot()
|
255
288
|
def openVariableGeneratorDlg(self) -> None:
|
256
289
|
item = self.ui.treeWidget.currentItem()
|
257
290
|
if isinstance(item, QuestionItem):
|
258
|
-
question = item.
|
291
|
+
question = item.question
|
259
292
|
if isinstance(question, ParametricQuestion):
|
260
293
|
dialog = VariableGeneratorDialog(self, parametrics=question.parametrics)
|
261
294
|
if dialog.exec():
|
262
295
|
self.questionPreview.setupQuestion(question)
|
296
|
+
self.treeRefreshCategory(question.category)
|
263
297
|
logger.info("Updated QuestionItem display for %s", question.id)
|
264
298
|
self.copyVariablesToClipboard(
|
265
299
|
variables=question.parametrics.variables
|
@@ -280,7 +314,7 @@ class MainWindow(QMainWindow):
|
|
280
314
|
if not variables:
|
281
315
|
item = self.ui.treeWidget.currentItem()
|
282
316
|
if isinstance(item, QuestionItem):
|
283
|
-
question = item.
|
317
|
+
question = item.question
|
284
318
|
if isinstance(question, ParametricQuestion):
|
285
319
|
variables = question.parametrics.variables
|
286
320
|
varsList = [
|
@@ -321,7 +355,15 @@ class ParseAllThread(QRunnable):
|
|
321
355
|
|
322
356
|
@Slot()
|
323
357
|
def run(self) -> None:
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
358
|
+
try:
|
359
|
+
self.testDB.readCategoriesMetadata()
|
360
|
+
except InvalidFieldException:
|
361
|
+
logger.exception("Youre spreadsheet questionbank isn't correctly setup.")
|
362
|
+
except ValueError:
|
363
|
+
logger.exception(
|
364
|
+
"Did you forget to specify a 'settings' sheet in the file?"
|
365
|
+
)
|
366
|
+
else:
|
367
|
+
self.testDB.asyncInitAllCategories(self.mainApp.excelPath)
|
368
|
+
self.mainApp.setStatus("[OK] Tabellen wurde eingelesen")
|
369
|
+
self.testDB.parseAllQuestions()
|
excel2moodle/ui/dialogs.py
CHANGED
@@ -5,7 +5,8 @@ from pathlib import Path
|
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
7
|
import lxml.etree as ET
|
8
|
-
from PySide6.
|
8
|
+
from PySide6.QtCore import Slot
|
9
|
+
from PySide6.QtWidgets import QDialog, QFileDialog, QMainWindow, QMessageBox, QWidget
|
9
10
|
|
10
11
|
from excel2moodle import e2mMetadata
|
11
12
|
from excel2moodle.core.globals import XMLTags
|
@@ -13,6 +14,7 @@ from excel2moodle.core.question import ParametricQuestion, Question
|
|
13
14
|
from excel2moodle.core.settings import Tags
|
14
15
|
from excel2moodle.extra import variableGenerator
|
15
16
|
from excel2moodle.ui.UI_exportSettingsDialog import Ui_ExportDialog
|
17
|
+
from excel2moodle.ui.UI_updateDlg import Ui_UpdateDialog
|
16
18
|
from excel2moodle.ui.UI_variantDialog import Ui_Dialog
|
17
19
|
|
18
20
|
if TYPE_CHECKING:
|
@@ -21,6 +23,22 @@ if TYPE_CHECKING:
|
|
21
23
|
logger = logging.getLogger(__name__)
|
22
24
|
|
23
25
|
|
26
|
+
class UpdateDialog(QDialog):
|
27
|
+
def __init__(
|
28
|
+
self, parent: QMainWindow, changelog: str = "", version: str = ""
|
29
|
+
) -> None:
|
30
|
+
super().__init__(parent)
|
31
|
+
self.ui = Ui_UpdateDialog()
|
32
|
+
self.ui.setupUi(self)
|
33
|
+
self.ui.changelogBrowser.setMarkdown(changelog)
|
34
|
+
self.ui.titleLabel.setText(
|
35
|
+
f"<h2>New Version {version} of <i>exel2moodle</i> just dropped!!</h2>"
|
36
|
+
)
|
37
|
+
self.ui.fundingLabel.setText(
|
38
|
+
f'If you find this project useful, please consider supporting its development. <br> <a href="{e2mMetadata["funding"]}">Buy jbosse3 a coffee</a>, so he stays caffeinated during coding.',
|
39
|
+
)
|
40
|
+
|
41
|
+
|
24
42
|
class QuestionVariantDialog(QDialog):
|
25
43
|
def __init__(self, parent, question: ParametricQuestion) -> None:
|
26
44
|
super().__init__(parent)
|
@@ -50,6 +68,7 @@ class ExportDialog(QDialog):
|
|
50
68
|
self.ui = Ui_ExportDialog()
|
51
69
|
self.ui.setupUi(self)
|
52
70
|
self.ui.btnExportFile.clicked.connect(self.getExportFile)
|
71
|
+
self.ui.checkBoxExportAll.clicked.connect(self.toggleExportAll)
|
53
72
|
|
54
73
|
@property
|
55
74
|
def exportFile(self) -> Path:
|
@@ -62,6 +81,16 @@ class ExportDialog(QDialog):
|
|
62
81
|
f"../{(self.exportFile.parent.name)}/{self.exportFile.name}"
|
63
82
|
)
|
64
83
|
|
84
|
+
@Slot()
|
85
|
+
def toggleExportAll(self) -> None:
|
86
|
+
self.ui.spinBoxDefaultQVariant.setEnabled(
|
87
|
+
not self.ui.checkBoxExportAll.isChecked()
|
88
|
+
)
|
89
|
+
self.ui.checkBoxIncludeCategories.setChecked(
|
90
|
+
self.ui.checkBoxExportAll.isChecked()
|
91
|
+
)
|
92
|
+
|
93
|
+
@Slot()
|
65
94
|
def getExportFile(self) -> None:
|
66
95
|
path = QFileDialog.getSaveFileName(
|
67
96
|
self,
|
excel2moodle/ui/treewidget.py
CHANGED
@@ -4,24 +4,33 @@ Those two are subclasses of `QTreeWidgetItem`, to provide an easy interface
|
|
4
4
|
of accessing the corresponding questions from the items.
|
5
5
|
"""
|
6
6
|
|
7
|
+
import logging
|
8
|
+
|
7
9
|
from PySide6.QtCore import Qt
|
8
10
|
from PySide6.QtWidgets import QTreeWidgetItem
|
9
11
|
|
10
12
|
from excel2moodle.core.dataStructure import Category
|
11
13
|
from excel2moodle.core.question import ParametricQuestion, Question
|
12
14
|
|
15
|
+
logger = logging.getLogger(__name__)
|
16
|
+
|
13
17
|
|
14
18
|
class QuestionItem(QTreeWidgetItem):
|
15
19
|
def __init__(self, parent, question: Question | ParametricQuestion) -> None:
|
16
20
|
super().__init__(parent)
|
17
21
|
self.setData(2, Qt.UserRole, question)
|
22
|
+
self.refresh()
|
23
|
+
|
24
|
+
def refresh(self) -> None:
|
25
|
+
question = self.question
|
18
26
|
self.setText(0, question.id)
|
19
27
|
self.setText(1, question.name)
|
20
28
|
self.setText(2, str(question.points))
|
21
|
-
if
|
22
|
-
self.setText(3, str(question.variants))
|
29
|
+
if isinstance(question, ParametricQuestion):
|
30
|
+
self.setText(3, str(question.parametrics.variants))
|
23
31
|
|
24
|
-
|
32
|
+
@property
|
33
|
+
def question(self) -> Question | ParametricQuestion:
|
25
34
|
"""Return the question Object the QTreeWidgetItem represents."""
|
26
35
|
return self.data(2, Qt.UserRole)
|
27
36
|
|
@@ -30,9 +39,9 @@ class CategoryItem(QTreeWidgetItem):
|
|
30
39
|
def __init__(self, parent, category: Category) -> None:
|
31
40
|
super().__init__(parent)
|
32
41
|
self.setData(2, Qt.UserRole, category)
|
33
|
-
self.
|
34
|
-
|
35
|
-
|
42
|
+
self.refresh()
|
43
|
+
|
44
|
+
def updateVariantCount(self) -> None:
|
36
45
|
var = self.getMaxVariants()
|
37
46
|
if var != 0:
|
38
47
|
self.setText(3, str(var))
|
@@ -44,10 +53,21 @@ class CategoryItem(QTreeWidgetItem):
|
|
44
53
|
def getMaxVariants(self) -> int:
|
45
54
|
count: int = 0
|
46
55
|
for child in self.iterateChildren():
|
47
|
-
q = child.
|
48
|
-
if
|
49
|
-
count = max(q.variants, count)
|
56
|
+
q = child.question
|
57
|
+
if isinstance(q, ParametricQuestion):
|
58
|
+
count = max(q.parametrics.variants, count)
|
50
59
|
return count
|
51
60
|
|
52
|
-
|
61
|
+
@property
|
62
|
+
def category(self) -> Category:
|
53
63
|
return self.data(2, Qt.UserRole)
|
64
|
+
|
65
|
+
def refresh(self) -> None:
|
66
|
+
for child in self.iterateChildren():
|
67
|
+
child.refresh()
|
68
|
+
# Update category data, as it might have changed
|
69
|
+
cat = self.category
|
70
|
+
self.setText(0, cat.NAME)
|
71
|
+
self.setText(1, cat.desc)
|
72
|
+
self.setText(2, str(cat.points))
|
73
|
+
self.updateVariantCount()
|