excel2moodle 0.5.1__py3-none-any.whl → 0.6.0__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/core/bullets.py +98 -0
- excel2moodle/core/dataStructure.py +6 -7
- excel2moodle/core/globals.py +3 -8
- excel2moodle/core/parser.py +37 -65
- excel2moodle/core/question.py +144 -76
- excel2moodle/extra/variableGenerator.py +250 -0
- excel2moodle/question_types/cloze.py +269 -103
- excel2moodle/question_types/nfm.py +41 -102
- excel2moodle/ui/UI_mainWindow.py +63 -36
- excel2moodle/ui/UI_variableGenerator.py +197 -0
- excel2moodle/ui/appUi.py +107 -44
- excel2moodle/ui/dialogs.py +44 -77
- excel2moodle/ui/equationChecker.py +2 -2
- excel2moodle/ui/treewidget.py +9 -24
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.6.0.dist-info}/METADATA +67 -2
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.6.0.dist-info}/RECORD +20 -19
- excel2moodle/core/numericMultiQ.py +0 -80
- excel2moodle/ui/windowDoc.py +0 -27
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.6.0.dist-info}/WHEEL +0 -0
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.6.0.dist-info}/entry_points.txt +0 -0
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {excel2moodle-0.5.1.dist-info → excel2moodle-0.6.0.dist-info}/top_level.txt +0 -0
excel2moodle/ui/appUi.py
CHANGED
@@ -7,13 +7,23 @@ It needs to be seperated from ``windowMain.py`` because that file will be change
|
|
7
7
|
import logging
|
8
8
|
from pathlib import Path
|
9
9
|
|
10
|
-
from PySide6 import
|
11
|
-
from PySide6.
|
10
|
+
from PySide6.QtCore import QRunnable, QSettings, Qt, QThreadPool, QUrl, Slot
|
11
|
+
from PySide6.QtGui import QDesktopServices
|
12
|
+
from PySide6.QtWidgets import (
|
13
|
+
QAbstractItemView,
|
14
|
+
QApplication,
|
15
|
+
QFileDialog,
|
16
|
+
QHeaderView,
|
17
|
+
QMainWindow,
|
18
|
+
QMessageBox,
|
19
|
+
)
|
12
20
|
|
13
|
-
from excel2moodle import mainLogger
|
21
|
+
from excel2moodle import e2mMetadata, mainLogger
|
14
22
|
from excel2moodle.core.category import Category
|
15
23
|
from excel2moodle.core.dataStructure import QuestionDB
|
24
|
+
from excel2moodle.core.question import ParametricQuestion
|
16
25
|
from excel2moodle.core.settings import Settings, Tags
|
26
|
+
from excel2moodle.extra.variableGenerator import VariableGeneratorDialog
|
17
27
|
from excel2moodle.logger import LogWindowHandler
|
18
28
|
from excel2moodle.question_types.mc import MCQuestion
|
19
29
|
from excel2moodle.question_types.nf import NFQuestion
|
@@ -21,7 +31,6 @@ from excel2moodle.ui import dialogs
|
|
21
31
|
from excel2moodle.ui.equationChecker import EqCheckerWindow
|
22
32
|
from excel2moodle.ui.treewidget import CategoryItem, QuestionItem
|
23
33
|
from excel2moodle.ui.UI_mainWindow import Ui_MoodleTestGenerator
|
24
|
-
from excel2moodle.ui.windowDoc import DocumentationWindow
|
25
34
|
|
26
35
|
logger = logging.getLogger(__name__)
|
27
36
|
|
@@ -29,7 +38,7 @@ loggerSignal = LogWindowHandler()
|
|
29
38
|
mainLogger.addHandler(loggerSignal)
|
30
39
|
|
31
40
|
|
32
|
-
class MainWindow(
|
41
|
+
class MainWindow(QMainWindow):
|
33
42
|
def __init__(self, settings: Settings, testDB: QuestionDB) -> None:
|
34
43
|
super().__init__()
|
35
44
|
self.settings = settings
|
@@ -47,27 +56,31 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
47
56
|
self.eqChecker = EqCheckerWindow()
|
48
57
|
self.connectEvents()
|
49
58
|
logger.info("Settings are stored under: %s", self.qSettings.fileName())
|
50
|
-
self.ui.treeWidget.setSelectionMode(
|
59
|
+
self.ui.treeWidget.setSelectionMode(QAbstractItemView.MultiSelection)
|
51
60
|
self.ui.treeWidget.header().setSectionResizeMode(
|
52
|
-
|
61
|
+
QHeaderView.ResizeToContents,
|
53
62
|
)
|
63
|
+
self.ui.pointCounter.setReadOnly(True)
|
64
|
+
self.ui.questionCounter.setReadOnly(True)
|
65
|
+
self.setStatus(
|
66
|
+
"Wählen Sie eine Excel Tabelle mit den Fragen aus",
|
67
|
+
)
|
68
|
+
self.threadPool = QThreadPool()
|
69
|
+
self._restoreSettings()
|
70
|
+
|
71
|
+
def _restoreSettings(self) -> None:
|
72
|
+
"""Restore the settings from the last session, if they exist."""
|
54
73
|
self.exportDialog.ui.checkBoxIncludeCategories.setChecked(
|
55
74
|
self.qSettings.value(Tags.INCLUDEINCATS, defaultValue=True, type=bool)
|
56
75
|
)
|
57
76
|
self.exportDialog.ui.spinBoxDefaultQVariant.setValue(
|
58
77
|
self.qSettings.value(Tags.QUESTIONVARIANT, defaultValue=1, type=int)
|
59
78
|
)
|
60
|
-
self.ui.pointCounter.setReadOnly(True)
|
61
|
-
self.ui.questionCounter.setReadOnly(True)
|
62
|
-
self.setStatus(
|
63
|
-
"Wählen Sie eine Excel Tabelle mit den Fragen aus",
|
64
|
-
)
|
65
79
|
try:
|
66
80
|
self.resize(self.qSettings.value("windowSize"))
|
67
81
|
self.move(self.qSettings.value("windowPosition"))
|
68
82
|
except Exception:
|
69
83
|
pass
|
70
|
-
self.threadPool = QThreadPool()
|
71
84
|
if self.qSettings.contains(Tags.SPREADSHEETPATH.full):
|
72
85
|
sheet = self.qSettings.value(Tags.SPREADSHEETPATH.full)
|
73
86
|
self.setSheetPath(sheet)
|
@@ -79,9 +92,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
79
92
|
)
|
80
93
|
loggerSignal.emitter.signal.connect(self.updateLog)
|
81
94
|
self.ui.actionEquationChecker.triggered.connect(self.openEqCheckerDlg)
|
82
|
-
self.exportDialog.ui.checkBoxIncludeCategories.checkStateChanged.connect(
|
83
|
-
self.setIncludeCategoriesSetting,
|
84
|
-
)
|
85
95
|
self.ui.actionParseAll.triggered.connect(self.parseSpreadsheetAll)
|
86
96
|
self.testDB.signals.categoryQuestionsReady.connect(self.treeRefreshCategory)
|
87
97
|
self.ui.actionSpreadsheet.triggered.connect(self.actionSpreadsheet)
|
@@ -91,15 +101,13 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
91
101
|
self.ui.treeWidget.itemClicked.connect(self.updateQuestionPreview)
|
92
102
|
self.ui.actionAbout.triggered.connect(self.openAboutDlg)
|
93
103
|
self.ui.actionDocumentation.triggered.connect(self.openDocumentation)
|
94
|
-
self.
|
95
|
-
|
104
|
+
self.ui.actionGenerateVariables.triggered.connect(self.openVariableGeneratorDlg)
|
105
|
+
self.ui.actionCopyVariables.triggered.connect(self.copyVariablesToClipboard)
|
106
|
+
self.ui.actionOpenSpreadsheetExternal.triggered.connect(
|
107
|
+
self.openSpreadsheetExternally
|
96
108
|
)
|
97
109
|
|
98
|
-
@
|
99
|
-
def setQVariantDefault(self, value: int) -> None:
|
100
|
-
self.settings.set(Tags.QUESTIONVARIANT, value)
|
101
|
-
|
102
|
-
@QtCore.Slot()
|
110
|
+
@Slot()
|
103
111
|
def parseSpreadsheetAll(self) -> None:
|
104
112
|
"""Event triggered by the *Tools/Parse all Questions* Event.
|
105
113
|
|
@@ -128,12 +136,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
128
136
|
def updateLog(self, log) -> None:
|
129
137
|
self.ui.loggerWindow.append(log)
|
130
138
|
|
131
|
-
def setIncludeCategoriesSetting(self) -> None:
|
132
|
-
if self.exportDialog.ui.checkBoxIncludeCategories.isChecked():
|
133
|
-
self.settings.set(Tags.INCLUDEINCATS, True)
|
134
|
-
else:
|
135
|
-
self.settings.set(Tags.INCLUDEINCATS, False)
|
136
|
-
|
137
139
|
def closeEvent(self, event) -> None:
|
138
140
|
logger.info("Closing. Saving window stats.")
|
139
141
|
self.qSettings.setValue("windowSize", self.size())
|
@@ -146,7 +148,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
146
148
|
self.settings.get(Tags.QUESTIONVARIANT),
|
147
149
|
)
|
148
150
|
|
149
|
-
@
|
151
|
+
@Slot()
|
150
152
|
def onSelectionChanged(self, **args) -> None:
|
151
153
|
"""Whenever the selection changes the total of selected points needs to be recalculated."""
|
152
154
|
count: int = 0
|
@@ -160,7 +162,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
160
162
|
if self.eqChecker.isVisible():
|
161
163
|
self.openEqCheckerDlg()
|
162
164
|
|
163
|
-
@
|
165
|
+
@Slot()
|
164
166
|
def toggleQuestionSelectionState(self, state) -> None:
|
165
167
|
setter = state == Qt.Checked
|
166
168
|
root = self.ui.treeWidget.invisibleRootItem()
|
@@ -170,7 +172,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
170
172
|
for q in range(qs):
|
171
173
|
root.child(i).child(q).setSelected(setter)
|
172
174
|
|
173
|
-
@
|
175
|
+
@Slot()
|
174
176
|
def onButGenTest(self) -> None:
|
175
177
|
"""Open a file Dialog so the export file may be choosen."""
|
176
178
|
selection: list[QuestionItem] = self.ui.treeWidget.selectedItems()
|
@@ -179,14 +181,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
179
181
|
self.exportDialog.ui.pointCount.setValue(self.ui.pointCounter.value())
|
180
182
|
if self.exportDialog.exec():
|
181
183
|
self.exportFile = self.exportDialog.exportFile
|
184
|
+
self.settings.set(
|
185
|
+
Tags.INCLUDEINCATS,
|
186
|
+
self.exportDialog.ui.checkBoxIncludeCategories.isChecked(),
|
187
|
+
)
|
188
|
+
self.settings.set(
|
189
|
+
Tags.QUESTIONVARIANT,
|
190
|
+
self.exportDialog.ui.spinBoxDefaultQVariant.value(),
|
191
|
+
)
|
182
192
|
logger.info("New Export File is set %s", self.exportFile)
|
183
193
|
self.testDB.appendQuestions(selection, self.exportFile)
|
184
194
|
else:
|
185
195
|
logger.info("Aborting Export")
|
186
196
|
|
187
|
-
@
|
197
|
+
@Slot()
|
188
198
|
def actionSpreadsheet(self) -> None:
|
189
|
-
file =
|
199
|
+
file = QFileDialog.getOpenFileName(
|
190
200
|
self,
|
191
201
|
self.tr("Open Spreadsheet"),
|
192
202
|
dir=str(self.mainPath),
|
@@ -196,7 +206,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
196
206
|
path = Path(file[0]).resolve(strict=True)
|
197
207
|
self.setSheetPath(path)
|
198
208
|
|
199
|
-
@
|
209
|
+
@Slot(Category)
|
200
210
|
def treeRefreshCategory(self, cat: Category) -> None:
|
201
211
|
"""Append Category with its Questions to the treewidget."""
|
202
212
|
catItem = CategoryItem(self.ui.treeWidget, cat)
|
@@ -205,7 +215,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
205
215
|
QuestionItem(catItem, q)
|
206
216
|
self.ui.treeWidget.sortItems(0, Qt.SortOrder.AscendingOrder)
|
207
217
|
|
208
|
-
@
|
218
|
+
@Slot()
|
209
219
|
def updateQuestionPreview(self) -> None:
|
210
220
|
item = self.ui.treeWidget.currentItem()
|
211
221
|
if isinstance(item, QuestionItem):
|
@@ -217,7 +227,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
217
227
|
self.ui.statusbar.clearMessage()
|
218
228
|
self.ui.statusbar.showMessage(self.tr(status))
|
219
229
|
|
220
|
-
@
|
230
|
+
@Slot()
|
221
231
|
def openEqCheckerDlg(self) -> None:
|
222
232
|
item = self.ui.treeWidget.currentItem()
|
223
233
|
if isinstance(item, QuestionItem):
|
@@ -231,18 +241,71 @@ class MainWindow(QtWidgets.QMainWindow):
|
|
231
241
|
else:
|
232
242
|
logger.debug("No Question Item selected: %s", type(item))
|
233
243
|
|
234
|
-
@
|
244
|
+
@Slot()
|
235
245
|
def openAboutDlg(self) -> None:
|
236
246
|
about = dialogs.AboutDialog(self)
|
237
247
|
about.exec()
|
238
248
|
|
239
|
-
@
|
249
|
+
@Slot()
|
240
250
|
def openDocumentation(self) -> None:
|
241
|
-
|
242
|
-
|
251
|
+
url = QUrl(e2mMetadata["documentation"])
|
252
|
+
QDesktopServices.openUrl(url)
|
253
|
+
|
254
|
+
@Slot()
|
255
|
+
def openVariableGeneratorDlg(self) -> None:
|
256
|
+
item = self.ui.treeWidget.currentItem()
|
257
|
+
if isinstance(item, QuestionItem):
|
258
|
+
question = item.getQuestion()
|
259
|
+
if isinstance(question, ParametricQuestion):
|
260
|
+
dialog = VariableGeneratorDialog(self, parametrics=question.parametrics)
|
261
|
+
if dialog.exec():
|
262
|
+
self.questionPreview.setupQuestion(question)
|
263
|
+
logger.info("Updated QuestionItem display for %s", question.id)
|
264
|
+
self.copyVariablesToClipboard(
|
265
|
+
variables=question.parametrics.variables
|
266
|
+
)
|
267
|
+
else:
|
268
|
+
logger.warning("No variable sets were generated.")
|
269
|
+
else:
|
270
|
+
logger.info("Selected item is not a ParametricQuestion.")
|
243
271
|
else:
|
244
|
-
|
245
|
-
|
272
|
+
logger.info("No Question Item selected.")
|
273
|
+
|
274
|
+
@Slot()
|
275
|
+
def copyVariablesToClipboard(
|
276
|
+
self, variables: dict[str, list[float | int]] | None = None
|
277
|
+
) -> None:
|
278
|
+
if variables is None:
|
279
|
+
variables = {}
|
280
|
+
if not variables:
|
281
|
+
item = self.ui.treeWidget.currentItem()
|
282
|
+
if isinstance(item, QuestionItem):
|
283
|
+
question = item.getQuestion()
|
284
|
+
if isinstance(question, ParametricQuestion):
|
285
|
+
variables = question.parametrics.variables
|
286
|
+
varsList = [
|
287
|
+
f"{name}\t{'; '.join(map(str, vals))}"
|
288
|
+
for name, vals in variables.items()
|
289
|
+
]
|
290
|
+
clipb = QApplication.clipboard()
|
291
|
+
variablesStr = "\n".join(varsList)
|
292
|
+
clipb.setText(variablesStr)
|
293
|
+
logger.info("Copied all variables to the clipboard")
|
294
|
+
QMessageBox.information(
|
295
|
+
self,
|
296
|
+
"Variables Copied.",
|
297
|
+
"""All variables from the parametric Question are saved to the system clipboard.\n
|
298
|
+
You can paste them into the spreadsheet.
|
299
|
+
Make sure to import them with 'Tab' as the seperator.""",
|
300
|
+
)
|
301
|
+
|
302
|
+
@Slot()
|
303
|
+
def openSpreadsheetExternally(self) -> None:
|
304
|
+
if self.excelPath is None:
|
305
|
+
return
|
306
|
+
spreadsheetPath = QUrl(f"file://{self.excelPath.absolute()}")
|
307
|
+
logger.info("Opening: %s", spreadsheetPath)
|
308
|
+
QDesktopServices.openUrl(spreadsheetPath)
|
246
309
|
|
247
310
|
|
248
311
|
class ParseAllThread(QRunnable):
|
@@ -256,7 +319,7 @@ class ParseAllThread(QRunnable):
|
|
256
319
|
self.testDB = questionDB
|
257
320
|
self.mainApp = mainApp
|
258
321
|
|
259
|
-
@
|
322
|
+
@Slot()
|
260
323
|
def run(self) -> None:
|
261
324
|
self.testDB.readCategoriesMetadata()
|
262
325
|
self.testDB.asyncInitAllCategories(self.mainApp.excelPath)
|
excel2moodle/ui/dialogs.py
CHANGED
@@ -5,12 +5,13 @@ from pathlib import Path
|
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
7
|
import lxml.etree as ET
|
8
|
-
from PySide6 import
|
9
|
-
from PySide6.QtSvgWidgets import QGraphicsSvgItem
|
8
|
+
from PySide6.QtWidgets import QDialog, QFileDialog, QMessageBox, QWidget
|
10
9
|
|
11
10
|
from excel2moodle import e2mMetadata
|
12
11
|
from excel2moodle.core.globals import XMLTags
|
13
|
-
from excel2moodle.core.question import Question
|
12
|
+
from excel2moodle.core.question import ParametricQuestion, Question
|
13
|
+
from excel2moodle.core.settings import Tags
|
14
|
+
from excel2moodle.extra import variableGenerator
|
14
15
|
from excel2moodle.ui.UI_exportSettingsDialog import Ui_ExportDialog
|
15
16
|
from excel2moodle.ui.UI_variantDialog import Ui_Dialog
|
16
17
|
|
@@ -20,11 +21,11 @@ if TYPE_CHECKING:
|
|
20
21
|
logger = logging.getLogger(__name__)
|
21
22
|
|
22
23
|
|
23
|
-
class QuestionVariantDialog(
|
24
|
-
def __init__(self, parent, question:
|
24
|
+
class QuestionVariantDialog(QDialog):
|
25
|
+
def __init__(self, parent, question: ParametricQuestion) -> None:
|
25
26
|
super().__init__(parent)
|
26
27
|
self.setWindowTitle("Question Variant Dialog")
|
27
|
-
self.maxVal = question.variants
|
28
|
+
self.maxVal = question.parametrics.variants
|
28
29
|
self.ui = Ui_Dialog()
|
29
30
|
self.ui.setupUi(self)
|
30
31
|
self.ui.spinBox.setRange(1, self.maxVal)
|
@@ -41,7 +42,7 @@ class QuestionVariantDialog(QtWidgets.QDialog):
|
|
41
42
|
return self.ui.checkBox.isChecked()
|
42
43
|
|
43
44
|
|
44
|
-
class ExportDialog(
|
45
|
+
class ExportDialog(QDialog):
|
45
46
|
def __init__(self, parent) -> None:
|
46
47
|
super().__init__(parent)
|
47
48
|
self.setWindowTitle("Export question Selection")
|
@@ -62,7 +63,7 @@ class ExportDialog(QtWidgets.QDialog):
|
|
62
63
|
)
|
63
64
|
|
64
65
|
def getExportFile(self) -> None:
|
65
|
-
path =
|
66
|
+
path = QFileDialog.getSaveFileName(
|
66
67
|
self,
|
67
68
|
"Select Output File",
|
68
69
|
dir=str(self.exportFile),
|
@@ -83,85 +84,51 @@ class QuestionPreview:
|
|
83
84
|
self.ui = parent.ui
|
84
85
|
self.parent = parent
|
85
86
|
|
87
|
+
def _replaceImgPlaceholder(self, elementStr: str) -> str:
|
88
|
+
"""Replaces '@@PLUGINFILE@@' with the questions Img Folder path."""
|
89
|
+
return elementStr.replace(
|
90
|
+
"@@PLUGINFILE@@",
|
91
|
+
f"{self.pictureFolder}/{self.question.category.NAME}",
|
92
|
+
)
|
93
|
+
|
86
94
|
def setupQuestion(self, question: Question) -> None:
|
95
|
+
self.pictureFolder = self.parent.settings.get(Tags.PICTUREFOLDER)
|
96
|
+
self.ui.previewTextEdit.clear()
|
87
97
|
self.question: Question = question
|
88
98
|
self.ui.qNameLine.setText(f"{self.question.qtype} - {self.question.name}")
|
89
|
-
self.
|
90
|
-
|
91
|
-
|
99
|
+
self.ui.previewTextEdit.append(
|
100
|
+
self._replaceImgPlaceholder(
|
101
|
+
ET.tostring(self.question.htmlRoot, encoding="unicode")
|
102
|
+
)
|
103
|
+
)
|
104
|
+
self.parent.ui.tableVariables.hide()
|
92
105
|
self.setAnswers()
|
93
|
-
if hasattr(self, "picItem") and self.picItem.scene() == self.picScene:
|
94
|
-
logger.debug("removing Previous picture")
|
95
|
-
self.picScene.removeItem(self.picItem)
|
96
|
-
del self.picItem
|
97
|
-
self.setPicture()
|
98
|
-
|
99
|
-
def setPicture(self) -> None:
|
100
|
-
if hasattr(self.question, "picture") and self.question.picture.ready:
|
101
|
-
path = self.question.picture.path
|
102
|
-
if path.suffix == ".svg":
|
103
|
-
self.picItem = QGraphicsSvgItem(str(path))
|
104
|
-
else:
|
105
|
-
pic = QtGui.QPixmap(str(path))
|
106
|
-
self.picItem = QtWidgets.QGraphicsPixmapItem(pic)
|
107
|
-
if pic.isNull():
|
108
|
-
logger.warning("Picture null")
|
109
|
-
scale = self._getImgFittingScale()
|
110
|
-
self.picItem.setScale(scale)
|
111
|
-
self.picScene.addItem(self.picItem)
|
112
|
-
|
113
|
-
def _getImgFittingScale(self) -> float:
|
114
|
-
view_size = self.ui.graphicsView.viewport().size()
|
115
|
-
view_width = view_size.width()
|
116
|
-
view_height = view_size.height()
|
117
|
-
if isinstance(self.picItem, QtWidgets.QGraphicsPixmapItem):
|
118
|
-
original_size = self.picItem.pixmap().size()
|
119
|
-
elif isinstance(self.picItem, QGraphicsSvgItem):
|
120
|
-
original_size = self.picItem.renderer().defaultSize()
|
121
|
-
else:
|
122
|
-
return 1 # Unknown item type
|
123
|
-
scale_x = view_width / original_size.width()
|
124
|
-
scale_y = view_height / original_size.height()
|
125
|
-
return min(scale_x, scale_y)
|
126
|
-
|
127
|
-
def setText(self) -> None:
|
128
|
-
t = []
|
129
|
-
for text in self.question.qtextParagraphs:
|
130
|
-
t.append(ET.tostring(text, encoding="unicode"))
|
131
|
-
if self.question.bulletList is not None:
|
132
|
-
t.append(ET.tostring(self.question.bulletList, encoding="unicode"))
|
133
|
-
self.ui.questionText.setText("\n".join(t))
|
134
106
|
|
135
107
|
def setAnswers(self) -> None:
|
136
|
-
if self.question
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
list.append(textEle)
|
142
|
-
self.ui.answersLabel.setText(ET.tostring(list, encoding="unicode"))
|
108
|
+
if isinstance(self.question, ParametricQuestion):
|
109
|
+
variableGenerator.populateDataSetTable(
|
110
|
+
self.parent.ui.tableVariables, parametrics=self.question.parametrics
|
111
|
+
)
|
112
|
+
self.parent.ui.tableVariables.show()
|
143
113
|
elif self.question.qtype == "NF":
|
144
|
-
ans = self.question.
|
145
|
-
self.ui.
|
114
|
+
ans = self.question._element.find(XMLTags.ANSWER)
|
115
|
+
self.ui.previewTextEdit.append(f" Result: {ans.find('text').text}")
|
146
116
|
elif self.question.qtype == "MC":
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
class AboutDialog(QtWidgets.QMessageBox):
|
160
|
-
def __init__(self, parent: QtWidgets.QWidget) -> None:
|
117
|
+
for n, ans in enumerate(self.question._element.findall(XMLTags.ANSWER)):
|
118
|
+
self.ui.previewTextEdit.append(
|
119
|
+
f"<b>Answer {n + 1}, Fraction {ans.get('fraction')}:</b>"
|
120
|
+
)
|
121
|
+
self.ui.previewTextEdit.append(
|
122
|
+
self._replaceImgPlaceholder(ans.find("text").text)
|
123
|
+
)
|
124
|
+
|
125
|
+
|
126
|
+
class AboutDialog(QMessageBox):
|
127
|
+
def __init__(self, parent: QWidget) -> None:
|
161
128
|
super().__init__(parent)
|
162
129
|
self.setWindowTitle(f"About {e2mMetadata['name']}")
|
163
|
-
self.setIcon(
|
164
|
-
self.setStandardButtons(
|
130
|
+
self.setIcon(QMessageBox.Information)
|
131
|
+
self.setStandardButtons(QMessageBox.StandardButton.Close)
|
165
132
|
|
166
133
|
self.aboutMessage: str = f"""
|
167
134
|
<h1> About {e2mMetadata["name"]} v{e2mMetadata["version"]}</h1><br>
|
@@ -2,7 +2,7 @@ import logging
|
|
2
2
|
import math
|
3
3
|
from pathlib import Path
|
4
4
|
|
5
|
-
from PySide6 import
|
5
|
+
from PySide6.QtWidgets import QWidget
|
6
6
|
|
7
7
|
from excel2moodle import mainLogger
|
8
8
|
from excel2moodle.core.question import ParametricQuestion
|
@@ -18,7 +18,7 @@ loggerSignal = LogWindowHandler()
|
|
18
18
|
mainLogger.addHandler(loggerSignal)
|
19
19
|
|
20
20
|
|
21
|
-
class EqCheckerWindow(
|
21
|
+
class EqCheckerWindow(QWidget):
|
22
22
|
def __init__(self) -> None:
|
23
23
|
super().__init__()
|
24
24
|
self.excelFile = Path()
|
excel2moodle/ui/treewidget.py
CHANGED
@@ -1,11 +1,17 @@
|
|
1
|
-
|
1
|
+
"""The `treewidget` Module provides the `QuestionItem` and the `CategoryItem` item.
|
2
|
+
|
3
|
+
Those two are subclasses of `QTreeWidgetItem`, to provide an easy interface
|
4
|
+
of accessing the corresponding questions from the items.
|
5
|
+
"""
|
6
|
+
|
2
7
|
from PySide6.QtCore import Qt
|
8
|
+
from PySide6.QtWidgets import QTreeWidgetItem
|
3
9
|
|
4
10
|
from excel2moodle.core.dataStructure import Category
|
5
11
|
from excel2moodle.core.question import ParametricQuestion, Question
|
6
12
|
|
7
13
|
|
8
|
-
class QuestionItem(
|
14
|
+
class QuestionItem(QTreeWidgetItem):
|
9
15
|
def __init__(self, parent, question: Question | ParametricQuestion) -> None:
|
10
16
|
super().__init__(parent)
|
11
17
|
self.setData(2, Qt.UserRole, question)
|
@@ -20,7 +26,7 @@ class QuestionItem(QtWidgets.QTreeWidgetItem):
|
|
20
26
|
return self.data(2, Qt.UserRole)
|
21
27
|
|
22
28
|
|
23
|
-
class CategoryItem(
|
29
|
+
class CategoryItem(QTreeWidgetItem):
|
24
30
|
def __init__(self, parent, category: Category) -> None:
|
25
31
|
super().__init__(parent)
|
26
32
|
self.setData(2, Qt.UserRole, category)
|
@@ -45,24 +51,3 @@ class CategoryItem(QtWidgets.QTreeWidgetItem):
|
|
45
51
|
|
46
52
|
def getCategory(self) -> Category:
|
47
53
|
return self.data(2, Qt.UserRole)
|
48
|
-
|
49
|
-
|
50
|
-
# class SpinBoxDelegate(QtWidgets.QStyledItemDelegate):
|
51
|
-
# def __init__(self, parent=None):
|
52
|
-
# super().__init__(parent)
|
53
|
-
#
|
54
|
-
# def createEditor(self, parent, option, index):
|
55
|
-
# # Create a QSpinBox when the item is being edited
|
56
|
-
# spinbox = QtWidgets.QSpinBox(parent)
|
57
|
-
# spinbox.setMinimum(0)
|
58
|
-
# spinbox.setMaximum(100)
|
59
|
-
# return spinbox
|
60
|
-
#
|
61
|
-
# def setEditorData(self, editor, index):
|
62
|
-
# # Set the current value of the QSpinBox based on the item's data
|
63
|
-
# value = index.model().data(index, Qt.EditRole)
|
64
|
-
# editor.setValue(value)
|
65
|
-
#
|
66
|
-
# def setModelData(self, editor, model, index):
|
67
|
-
# # When editing is done, update the model data with the QSpinBox value
|
68
|
-
# model.setData(index, editor.value(), Qt.EditRole)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: excel2moodle
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Summary: A package for converting questions from a spreadsheet, to valid moodle-xml
|
5
5
|
Author: Jakob Bosse
|
6
6
|
License-Expression: GPL-3.0-or-later
|
@@ -12,11 +12,11 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Requires-Python: >=3.10
|
13
13
|
Description-Content-Type: text/markdown
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: pyside6>=6.8.0
|
16
15
|
Requires-Dist: pandas>=2.1.3
|
17
16
|
Requires-Dist: lxml>=5.4.0
|
18
17
|
Requires-Dist: asteval>=1.0.6
|
19
18
|
Requires-Dist: python-calamine>=0.3.2
|
19
|
+
Requires-Dist: pyside6-essentials>=6.8.0
|
20
20
|
Dynamic: license-file
|
21
21
|
|
22
22
|
# excel 2 Moodle
|
@@ -81,6 +81,71 @@ If You want to support my work as well, you can by me a [coffee](https://ko-fi.c
|
|
81
81
|
|
82
82
|
# Changelogs
|
83
83
|
|
84
|
+
## 0.6.0 (2025-07-12)
|
85
|
+
Added variable generator and other architechtural improvements
|
86
|
+
|
87
|
+
### documentation (1 change)
|
88
|
+
|
89
|
+
- [Documenting variable generator usage](https://gitlab.com/jbosse3/excel2moodle/-/commit/3e4d3019b29872b5cfddf5539d5ebe7638bca049)
|
90
|
+
|
91
|
+
### feature (5 changes)
|
92
|
+
|
93
|
+
- [Opening spreadsheet file works from within excel2moodle](https://gitlab.com/jbosse3/excel2moodle/-/commit/9470f12ea5f098745a3210b281a5144a938ae8b5)
|
94
|
+
- [Variables are copied to clipboard](https://gitlab.com/jbosse3/excel2moodle/-/commit/87a7e5ec75f899b293e89ad3c1742567e3ec1c29)
|
95
|
+
- [Removed dependence on pyside6-addons](https://gitlab.com/jbosse3/excel2moodle/-/commit/2b3a7cf48581c14bd9cb570cd61d1d41aa410e11)
|
96
|
+
- [Var Generator ready](https://gitlab.com/jbosse3/excel2moodle/-/commit/ea97f0639dc35a4c99a64ae3976ccc8a0ac5d109)
|
97
|
+
- [Merge development of BulletsObj, Parametrization and VarGenerator](https://gitlab.com/jbosse3/excel2moodle/-/commit/40b46f3c143e082f1bb985d6c8c4e68bb6b6a7a8)
|
98
|
+
|
99
|
+
### improvement (7 changes)
|
100
|
+
|
101
|
+
- [Adapted Param. Parser to use bullet Obj](https://gitlab.com/jbosse3/excel2moodle/-/commit/194cab7cc6aecb2d25d1cb9c1538ed7d607dd9e1)
|
102
|
+
- [Added bulleList Object](https://gitlab.com/jbosse3/excel2moodle/-/commit/4ea982b8d8dc270675d2cb059c59fa980ce38894)
|
103
|
+
- [Parametrics in beta stage](https://gitlab.com/jbosse3/excel2moodle/-/commit/7d04d8ef2fc603c1b12b6934c827ce079df5d540)
|
104
|
+
- [Refactor parse() method, to construct complete xml-Tree](https://gitlab.com/jbosse3/excel2moodle/-/commit/8dc4bea9aa0673d39357115254dd55b02c04114e)
|
105
|
+
- [Refactored question assembly to only update fields.](https://gitlab.com/jbosse3/excel2moodle/-/commit/d7accb69be3b4a1e65f59eeecfb463f2663fabd4)
|
106
|
+
- [Adapted NFM Question to parametricResult](https://gitlab.com/jbosse3/excel2moodle/-/commit/fe552cd2b538ca8886415c200e4a2a3ecc1fbb2f) ([merge request](https://gitlab.com/jbosse3/excel2moodle/-/merge_requests/5))
|
107
|
+
- [Implemented ParametricResult Object](https://gitlab.com/jbosse3/excel2moodle/-/commit/e36d025955f1cab8e0542d66263ab70e3d8980df) ([merge request](https://gitlab.com/jbosse3/excel2moodle/-/merge_requests/5))
|
108
|
+
|
109
|
+
## 0.5.2 (2025-06-30)
|
110
|
+
Extended Documentation and bugfix for import Module
|
111
|
+
|
112
|
+
### bugfix (2 changes)
|
113
|
+
|
114
|
+
- [Default question variant saved and reused.](https://gitlab.com/jbosse3/excel2moodle/-/commit/097705ba83727463a9b27cd76e99814a7ecf28df)
|
115
|
+
- [bugfix: Import module working again](https://gitlab.com/jbosse3/excel2moodle/-/commit/5f293970bcdac3858911cdcc102b72714af057bd)
|
116
|
+
|
117
|
+
### documentation (1 change)
|
118
|
+
|
119
|
+
- [documentation: Added how to build question database](https://gitlab.com/jbosse3/excel2moodle/-/commit/71ceb122aa37e8bf2735b659359ae37d81017599)
|
120
|
+
|
121
|
+
### feature (1 change)
|
122
|
+
|
123
|
+
- [Implemented MC question string method](https://gitlab.com/jbosse3/excel2moodle/-/commit/c4f2081d0000ee60322fe8eec8468fa3317ce7be)
|
124
|
+
|
125
|
+
### improvement (1 change)
|
126
|
+
|
127
|
+
- [Implemented ClozePart object](https://gitlab.com/jbosse3/excel2moodle/-/commit/878f90f45e37421384c4f8f602115e7596b4ceb9)
|
128
|
+
|
129
|
+
## 0.5.2 (2025-06-30)
|
130
|
+
Extended Documentation and bugfix for import Module
|
131
|
+
|
132
|
+
### bugfix (2 changes)
|
133
|
+
|
134
|
+
- [Default question variant saved and reused.](https://gitlab.com/jbosse3/excel2moodle/-/commit/097705ba83727463a9b27cd76e99814a7ecf28df)
|
135
|
+
- [bugfix: Import module working again](https://gitlab.com/jbosse3/excel2moodle/-/commit/5f293970bcdac3858911cdcc102b72714af057bd)
|
136
|
+
|
137
|
+
### documentation (1 change)
|
138
|
+
|
139
|
+
- [documentation: Added how to build question database](https://gitlab.com/jbosse3/excel2moodle/-/commit/71ceb122aa37e8bf2735b659359ae37d81017599)
|
140
|
+
|
141
|
+
### feature (1 change)
|
142
|
+
|
143
|
+
- [Implemented MC question string method](https://gitlab.com/jbosse3/excel2moodle/-/commit/c4f2081d0000ee60322fe8eec8468fa3317ce7be)
|
144
|
+
|
145
|
+
### improvement (1 change)
|
146
|
+
|
147
|
+
- [Implemented ClozePart object](https://gitlab.com/jbosse3/excel2moodle/-/commit/878f90f45e37421384c4f8f602115e7596b4ceb9)
|
148
|
+
|
84
149
|
## 0.5.1 (2025-06-24)
|
85
150
|
Minor docs improvement and question variant bugfix
|
86
151
|
|