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.
@@ -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.genFeedbacks = [XMLTags.GENFEEDB]
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
- return [self.question.answerElement]
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
- # TODO: @jbosse3: Implement a new _setVariants() method, to show in treewidget
67
- # def _setVariants(self, number: int) -> None:
68
- # self.question.variants = number
69
- # mvar = self.question.category.maxVariants
70
- # if mvar is None:
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 'exportSettingsDialog.ui'
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(1, QFormLayout.ItemRole.LabelRole, self.label_9)
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(1, QFormLayout.ItemRole.FieldRole, self.checkBoxIncludeCategories)
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(2, QFormLayout.ItemRole.LabelRole, self.label)
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(2, QFormLayout.ItemRole.FieldRole, self.btnExportFile)
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(3, QFormLayout.ItemRole.LabelRole, self.line_2)
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
- font = QFont()
74
- font.setPointSize(12)
75
- self.label_2.setFont(font)
77
+ font1 = QFont()
78
+ font1.setPointSize(12)
79
+ self.label_2.setFont(font1)
76
80
 
77
- self.formLayout_2.setWidget(4, QFormLayout.ItemRole.LabelRole, self.label_2)
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(5, QFormLayout.ItemRole.LabelRole, self.questionCount)
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(5, QFormLayout.ItemRole.FieldRole, self.pointCount)
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(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))
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.exportDialog.ui.spinBoxDefaultQVariant.setValue(
77
- self.qSettings.value(Tags.QUESTIONVARIANT, defaultValue=1, type=int)
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
- loggerSignal.emitter.signal.connect(self.updateLog)
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.getQuestion().points
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.exportDialog.ui.questionCount.setValue(self.ui.questionCounter.value())
181
- self.exportDialog.ui.pointCount.setValue(self.ui.pointCounter.value())
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
- self.exportDialog.ui.spinBoxDefaultQVariant.value(),
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(selection, self.exportFile)
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
- for q in cat.questions.values():
215
- QuestionItem(catItem, q)
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.getQuestion())
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.getQuestion()
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.getQuestion())
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.getQuestion()
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.getQuestion()
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
- self.testDB.readCategoriesMetadata()
325
- self.testDB.asyncInitAllCategories(self.mainApp.excelPath)
326
- self.mainApp.setStatus("[OK] Tabellen wurde eingelesen")
327
- self.testDB.parseAllQuestions()
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()
@@ -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.QtWidgets import QDialog, QFileDialog, QMessageBox, QWidget
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,
@@ -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 hasattr(question, "variants") and question.variants is not None:
22
- self.setText(3, str(question.variants))
29
+ if isinstance(question, ParametricQuestion):
30
+ self.setText(3, str(question.parametrics.variants))
23
31
 
24
- def getQuestion(self) -> Question | ParametricQuestion:
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.setText(0, category.NAME)
34
- self.setText(1, category.desc)
35
- self.setText(2, str(category.points))
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.getQuestion()
48
- if hasattr(q, "variants") and q.variants is not None:
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
- def getCategory(self) -> Category:
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()