excel2moodle 0.3.1__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.
@@ -0,0 +1,174 @@
1
+ """This Module holds the related Functions for writing the Questions to an xml-File
2
+
3
+ It is planned to rework those Functions, because they're not quite elegant.
4
+ """
5
+
6
+ from .stringHelpers import get_bullet_string
7
+
8
+ def write_question_MC(save_dir, ID, name, s_1, s_2, s_3, points_avail, ans_type, true_ans, false_ans, pic):
9
+ """Funktion schreibt MC-Frage auf Grundlage der übergebenen strings nach Pfad f_path"""
10
+ perc = ['100', '50', '33.33333', '25', '20', '16.66667', '14.28571', '12.5', '11.11111', '10']
11
+ num_true = int(len(true_ans))
12
+ perc_true = perc[num_true-1]
13
+ num_false = int(len(false_ans))
14
+ perc_false = '-'+perc_true
15
+ q_name = ID + '_' + name
16
+ f_path = ( save_dir / q_name ).with_suffix('.xml')
17
+
18
+ with open(f_path,'w', encoding='utf-8') as f:
19
+ # Text schreiben
20
+ f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
21
+ f.write('<quiz>\n')
22
+ f.write('<question type="multichoice">\n')
23
+ f.write('<name>\n')
24
+ f.write('<text>' + q_name + '</text>\n')
25
+ f.write('</name>\n')
26
+ f.write('<questiontext format="html">\n')
27
+ if pic != 0:
28
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID ' +str(ID)+ '</b> <br></p>'\
29
+ '<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
30
+ '<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
31
+ '<p dir="ltr" style="text-align: left;">' + s_3 + '<br><br></p>'\
32
+ '<br><img src="@@PLUGINFILE@@/'+ str(ID) + '.svg" alt="Bild" width="500"><br>'\
33
+ '<br></p>]]></text>\n')
34
+ f.write(str(pic))
35
+ else:
36
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID '+q_name+' </b> <br></p>'\
37
+ '<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
38
+ '<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
39
+ '<p dir="ltr" style="text-align: left;">' + s_3 + '<br></p>'\
40
+ '<br></p>]]></text>\n')
41
+
42
+ f.write('</questiontext>\n')
43
+ f.write('<generalfeedback format="html">\n')
44
+ f.write('<text></text>\n')
45
+ f.write('</generalfeedback>\n')
46
+ f.write('<defaultgrade>' +str(float(points_avail)) +'</defaultgrade>\n')
47
+ f.write('<penalty>0.3333333</penalty>\n')
48
+ f.write('<hidden>0</hidden>\n')
49
+ f.write('<idnumber>' + ID +'</idnumber>\n')
50
+ f.write('<single>false</single>\n')
51
+ f.write('<shuffleanswers>true</shuffleanswers>\n')
52
+ f.write('<answernumbering>abc</answernumbering>\n')
53
+ f.write('<showstandardinstruction>0</showstandardinstruction>\n')
54
+ f.write('<correctfeedback format="html">\n')
55
+ f.write('<text>Die Frage wurde richtig beantwortet.</text>\n')
56
+ f.write('</correctfeedback>\n')
57
+ f.write('<partiallycorrectfeedback format="html">\n')
58
+ f.write('<text>Die Frage wurde teilweise richtig beantwortet.</text>\n')
59
+ f.write('</partiallycorrectfeedback>\n')
60
+ f.write('<incorrectfeedback format="html">\n')
61
+ f.write('<text>Die Frage wurde falsch beantwortet.</text>\n')
62
+ f.write('</incorrectfeedback>\n')
63
+ f.write('<shownumcorrect/>\n')
64
+
65
+ # Alle richtigen Antworten
66
+ for i in range (0, num_true):
67
+
68
+ if ans_type=='unit':
69
+ f.write('<answer fraction="' + perc_true +'" format="html">\n')
70
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(\\mathrm{' + true_ans[i] + '}\\)<br></p>]]></text>\n')
71
+ f.write('<feedback format="html">\n')
72
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">richtig</span><br></p>]]></text>\n')
73
+ f.write('</feedback>\n')
74
+ f.write('</answer>\n')
75
+
76
+ elif ans_type=='math':
77
+ f.write('<answer fraction="' + perc_true +'" format="html">\n')
78
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(' + true_ans[i] + '\\)<br></p>]]></text>\n')
79
+ f.write('<feedback format="html">\n')
80
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">richtig</span><br></p>]]></text>\n')
81
+ f.write('</feedback>\n')
82
+ f.write('</answer>\n')
83
+
84
+
85
+ elif ans_type=='text':
86
+ f.write('<answer fraction="' + perc_true +'" format="html">\n')
87
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">' + true_ans[i] + '<br></p>]]></text>\n')
88
+ f.write('<feedback format="html">\n')
89
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">richtig</span><br></p>]]></text>\n')
90
+ f.write('</feedback>\n')
91
+ f.write('</answer>\n')
92
+
93
+ # Alle falschen Antworten
94
+ for i in range (0, num_false):
95
+ if ans_type=='unit':
96
+ f.write('<answer fraction="' + perc_false +'" format="html">\n')
97
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(\\mathrm{' + false_ans[i] + '}\\)<br></p>]]></text>\n')
98
+ f.write('<feedback format="html">\n')
99
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(239, 69, 64);">falsch</span><br></p>]]></text>\n')
100
+ f.write('</feedback>\n')
101
+ f.write('</answer>\n')
102
+
103
+ elif ans_type=='math':
104
+ f.write('<answer fraction="' + perc_false +'" format="html">\n')
105
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">\\(' + false_ans[i] + '\\)<br></p>]]></text>\n')
106
+ f.write('<feedback format="html">\n')
107
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(239, 69, 64);">falsch</span><br></p>]]></text>\n')
108
+ f.write('</feedback>\n')
109
+ f.write('</answer>\n')
110
+
111
+ elif ans_type=='text':
112
+ f.write('<answer fraction="' + perc_false +'" format="html">\n')
113
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;">' + false_ans[i] + '<br></p>]]></text>\n')
114
+ f.write('<feedback format="html">\n')
115
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(239, 69, 64);">falsch</span><br></p>]]></text>\n')
116
+ f.write('</feedback>\n')
117
+ f.write('</answer>\n')
118
+
119
+ f.write('</question>\n')
120
+ f.write('</quiz>\n')
121
+ return None
122
+
123
+ def write_question_NF(save_dir, ID, name, s_1, s_2, s_3, b_str, points_avail, result, pic, tol_abs, picID = None):
124
+ """Funktion schreibt NF-Frage auf Grundlage der übergebenen strings nach Pfad f_path"""
125
+ if picID == None:
126
+ picID = ID
127
+ q_name = ID + '_' + name
128
+ f_path = (save_dir / q_name).with_suffix('.xml')
129
+
130
+ with open(f_path,'w', encoding='utf-8') as f:
131
+ # Text schreiben
132
+ f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
133
+ f.write('<quiz>\n')
134
+ f.write('<question type="numerical">\n')
135
+ f.write('<name>\n')
136
+ f.write('<text>' + q_name + '</text>\n')
137
+ f.write('</name>\n')
138
+ f.write('<questiontext format="html">\n')
139
+ if pic != 0:
140
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID '+ str(ID) +' </b> <br></p>'\
141
+ '<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
142
+ '<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
143
+ '<p dir="ltr" style="text-align: left;">' + s_3 + b_str +'<br><br></p>'\
144
+ '<br><img src="@@PLUGINFILE@@/'+ str(picID) + '.svg" alt="Bild" width="500"><br>'\
145
+ '<br></p>]]></text>\n')
146
+ f.write(pic)
147
+ else:
148
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"> <b>ID '+ID+' </b> <br></p>'\
149
+ '<p dir="ltr" style="text-align: left;">' + s_1 + '<br></p>'\
150
+ '<p dir="ltr" style="text-align: left;">' + s_2 + '<br></p>'\
151
+ '<p dir="ltr" style="text-align: left;">' + s_3 + b_str+ ']]></text>\n')
152
+
153
+ f.write('</questiontext>\n')
154
+ f.write('<generalfeedback format="html">\n')
155
+ f.write('<text></text>\n')
156
+ f.write('</generalfeedback>\n')
157
+ f.write('<defaultgrade>' +str(float(points_avail)) +'</defaultgrade>\n')
158
+ f.write('<penalty>0.3333333</penalty>\n')
159
+ f.write('<hidden>0</hidden>\n')
160
+ f.write('<idnumber>' + ID +'</idnumber>\n')
161
+ f.write('<answer fraction="100" format="moodle_auto_format">\n')
162
+ f.write('<text>'+ str(result) +'</text>\n')
163
+ f.write('<feedback format="html">\n')
164
+ f.write('<text><![CDATA[<p dir="ltr" style="text-align: left;"><span class="" style="color: rgb(152, 202, 62);">Das Ergebnis ist im Rahmen der 1%-Toleranz korrekt.</span><br></p>]]></text>\n')
165
+ f.write('</feedback>\n')
166
+ f.write('<tolerance>'+ str(tol_abs)+'</tolerance>\n')
167
+ f.write('</answer>\n')
168
+ f.write('<unitgradingtype>0</unitgradingtype>\n')
169
+ f.write('<unitpenalty>0.1000000</unitpenalty>\n')
170
+ f.write('<showunits>3</showunits>\n')
171
+ f.write('<unitsleft>0</unitsleft>\n')
172
+ f.write('</question>\n')
173
+ f.write('</quiz>\n')
174
+ return None
@@ -0,0 +1,94 @@
1
+ """This Module holds small Helperfunctions related to string manipulation
2
+ """
3
+
4
+ from pathlib import Path
5
+ import lxml.etree as ET
6
+ import base64 as base64
7
+
8
+ def stripWhitespace(stringList):
9
+ stripped = []
10
+ for i in stringList:
11
+ stripped.append(i.strip())
12
+ return stripped
13
+
14
+ def get_bullet_string(s):
15
+ """Formatiert die Angaben zum Statischen System hübsch"""
16
+ split = s.split(';')
17
+ s_spl = stripWhitespace(split)
18
+ name = []
19
+ var = []
20
+ quant = []
21
+ unit = []
22
+ for sc in s_spl:
23
+ sc_split = sc.split()
24
+ name.append(sc_split[0])
25
+ var.append(sc_split[1])
26
+ quant.append(sc_split[3])
27
+ unit.append(sc_split[4])
28
+ bulletString = ['</p><ul dir="ltr">']
29
+ for i in range(0, len(s_spl)):
30
+ num = quant[i].split(',')
31
+ if len(num)==2:
32
+ num_s = f"{str(num[0])},\\!{str(num[1])}~"
33
+ else: num_s = f"{str(num[0])},\\!0~"
34
+ bulletString.append('<li style="text-align: left;">')
35
+ bulletString.append(f"{ name[i] }: \\( {var[i]} = {num_s} \\mathrm{{ {unit[i]} }}\\) </li>\n")
36
+ bulletString.append('<br></ul>')
37
+ return "\n".join(bulletString)
38
+
39
+ def getBase64Img(imgPath):
40
+ with open(imgPath, 'rb') as img:
41
+ img64 = base64.b64encode(img.read()).decode('utf-8')
42
+ return img64
43
+
44
+ def getUnitsElementAsString(unit):
45
+
46
+ def __getUnitEle__(name, multipl):
47
+ unit = ET.Element("unit")
48
+ ET.SubElement(unit, "multiplier").text = multipl
49
+ ET.SubElement(unit, "unit_name").text = name
50
+ return unit
51
+
52
+ unitsEle = ET.Element("units")
53
+
54
+ def printDom(xmlElement:ET.Element, file:Path|None=None )->None:
55
+ """Prints the document tree of ``xmlTree`` to the ``file``, if specified, else dumps to stdout"""
56
+ documentTree = ET.ElementTree(xmlElement)
57
+ if file is not None:
58
+ if file.parent.exists():
59
+ documentTree.write(file, xml_declaration=True, encoding="utf-8", pretty_print=True)
60
+ else:
61
+ msg =f" No output File specified, here is the Element:"
62
+ print(f'\n{ msg :=^80}')
63
+ print( ET.tostring(xmlElement, encoding="utf-8", pretty_print=True))
64
+ print(f'{" End of Element ":=^80}')
65
+
66
+
67
+ def texWrapper(text:str|list[str], style:str)->list[str]:
68
+ """Puts the strings inside ``text`` into a LaTex environment
69
+
70
+ if ``style == unit``: inside ``\\mathrm{}``
71
+ if ``style == math``: inside ``\\( \\)``
72
+ """
73
+
74
+ answers:list[str]=[]
75
+ begin =""
76
+ end = ""
77
+ if style == "math":
78
+ begin ="\\("
79
+ end = "\\)"
80
+ elif style == "unit":
81
+ begin ="\\(\\mathrm{"
82
+ end = "}\\)"
83
+ if isinstance(text, str):
84
+ li = [begin]
85
+ li.append(text)
86
+ li.append(end)
87
+ answers.append("".join(li))
88
+ elif isinstance(text, list):
89
+ for i in text:
90
+ li = [begin]
91
+ li.append(i)
92
+ li.append(end)
93
+ answers.append("".join(li))
94
+ return answers
@@ -0,0 +1,9 @@
1
+ """This *extra* subpackage conains standalone scripts
2
+
3
+ The modules inside *extra* can be run standalone, but are planned to be available from the main Window as well
4
+
5
+ To run a script execute the following: ``python -m excel2moodle.extra.SCRIPT``
6
+ Note that there is no ``.py`` at the end!!
7
+ """
8
+
9
+ from excel2moodle.core import numericMultiQ
@@ -0,0 +1,124 @@
1
+ """Script for verifying the equations written into the ``result`` field of NFM-type Question
2
+
3
+ This script does two things.
4
+
5
+ #. It calculates all the answers obtained from the series of variables.
6
+ #. It compares the calculation of the first answer to the ``firstResult`` field.
7
+
8
+ Usage
9
+ =====
10
+
11
+ From the main UI
12
+ ----------------
13
+
14
+ #. Start this tool from the top bar in the main Window under the *Tools* section
15
+ #. A new window will open inside you:
16
+ #. Enter the Number of the Category
17
+ #. Enter the Number of the Question
18
+ #. Click on ``Run check now`` and Inspect the results
19
+ #. Rinse and repeat
20
+
21
+ As Script
22
+ ---------
23
+
24
+ #. Start this script with ``py -m excel2moodle.extra.equationVerification`` inside the top-level Directory
25
+ #. Enter the Number of the Category
26
+ #. Enter the Number of the Question
27
+ #. Inspect the results
28
+ #. Rinse and repeat
29
+ """
30
+
31
+ import re as re
32
+ import pandas as pd
33
+ from pathlib import Path
34
+ from excel2moodle.core import numericMultiQ as nmq
35
+
36
+ # Hier Bitte die Frage angeben, die getestet Werden soll:
37
+
38
+ #===========================================================
39
+ def checkResult(checkerValue:float, calculation:float, tolerance = 0.01)-> bool:
40
+ """Checks if the two Arguments are within the tolerance the same value
41
+
42
+ :param checkerValue: the value the calculation is compared against
43
+ :type checkerValue: fleat
44
+ :param calculation: the value to be compared against checker Value
45
+ :type calculation: float
46
+ :param tolerance: the standart tolerance is 0.01 -> 1%
47
+ :type tolerance: float, optional
48
+
49
+ :returns:
50
+ True if checkerValue == calculation
51
+ False if checkerValue != calculation
52
+ :rtype: bool
53
+ """
54
+
55
+ upper = abs(checkerValue + checkerValue*tolerance)
56
+ lower = abs(checkerValue - checkerValue*tolerance)
57
+ if abs(calculation) > lower and abs(calculation) < upper:
58
+ return True
59
+ else :
60
+ return False
61
+
62
+ def equationChecker(categoryName: str, qNumber:int, spreadsheetFile)-> tuple[list[str], list[float], float]:
63
+ """This Function calculates all Results an invokes the checkResult function
64
+
65
+ Parameters
66
+ ----------
67
+ categoryName : str
68
+ The category in which the question to be tested is
69
+ This must match a sheet name of the spreadsheet
70
+ qNumber : int
71
+ The number of the question which results are tested
72
+ spreadsheetFile : Path
73
+ The Filepath to the spreadsheet
74
+
75
+ Returns
76
+ -------
77
+ bulletPointsString : list[str]
78
+ The string list with the bullet points, with numeric values instead of variables
79
+ results : list[str]
80
+ The list with the calculated results
81
+ checkerValue : float
82
+ The value taken from the ``firstResult`` field
83
+ """
84
+
85
+ spreadsheetFile.resolve()
86
+ df = pd.read_excel(spreadsheetFile, sheet_name=categoryName, index_col=0)
87
+ eq = df.loc["result"][qNumber]
88
+ bps = df.loc["bulletPoints"][qNumber]
89
+ try:
90
+ res = float(df.loc["firstResult"][qNumber])
91
+ except Exception:
92
+ print(f"Es ist kein 'firstResult' gegeben, kann nichts überprüfen")
93
+ res = 0
94
+ bps, calcs = nmq.parseNumericMultiQuestion(df,bps,eq, qNumber)
95
+ return bps, calcs, res
96
+
97
+
98
+ def main(spreadsheetFile= Path("../Fragensammlung/Main_question_all.xlsx"), catN = None, qNumber = None)-> None:
99
+ """Takes the Spreadsheet, and asks for a category and a question number
100
+
101
+ """
102
+ if catN == None:
103
+ catN = input("Geben Sie die Kategorie an: KAT_")
104
+ categoryName = f"KAT_{catN}"
105
+ if qNumber == None:
106
+ qNumber = int(input("Geben Sie die Fragennummer an: "))
107
+ bullets, results, firstResult = equationChecker(categoryName, qNumber, spreadsheetFile=spreadsheetFile)
108
+ check = False
109
+
110
+ for i, calculation in enumerate(results):
111
+ if i == 0 and firstResult !=0:
112
+ check = checkResult(firstResult, calculation)
113
+ print(f"Ergebnis {i+1}: \t{calculation}\n\tMit den Werten: \n{bullets[i]}\n")
114
+
115
+ if check == True:
116
+ print(f"Das erste berechnete Ergebnis stimmt mit dem Wert in 'firstResult' überein\n")
117
+ else:
118
+ print(f"WARNUNG: Das erste berechnete Ergebnis weicht von dem Wert {firstResult = } ab.\n")
119
+
120
+
121
+ if __name__ =="__main__":
122
+ spreadsheet =input(f"Geben Sie den Pfad zu dem spreadsheet an:")
123
+ while True:
124
+ main(Path(spreadsheet))
@@ -0,0 +1 @@
1
+ """Here is the relevant stuff for the UI"""
@@ -0,0 +1,243 @@
1
+ """This Module holds the extended class mainWindow() and any other main Windows
2
+
3
+ It needs to be seperated from ``windowMain.py`` because that file will be changed by tho ``pyside6-uic`` command,
4
+ which generates the python code from the ``.ui`` file
5
+ """
6
+
7
+ from PySide6 import QtCore
8
+ from PySide6.QtCore import Qt
9
+ from PySide6 import QtWidgets
10
+ from pathlib import Path
11
+ from excel2moodle.ui.windowMain import Ui_MoodleTestGenerator
12
+ from .windowEquationChecker import Ui_EquationChecker
13
+ from excel2moodle.ui import windowEquationChecker, dialogs
14
+ from excel2moodle.core.dataStructure import QuestionDB
15
+ from excel2moodle.ui.treewidget import QuestionItem, CategoryItem
16
+ from excel2moodle.extra import equationVerification as eqVerif
17
+ from excel2moodle import qSignalLogger
18
+ from excel2moodle.ui.settings import Settings
19
+ from excel2moodle.ui.windowDoc import DocumentationWindow
20
+ import logging as logging
21
+
22
+ from excel2moodle import dirDocumentation
23
+
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+
28
+
29
+ class MainWindow(QtWidgets.QMainWindow):
30
+ def __init__(self, settings:Settings, testDB:QuestionDB)->None:
31
+ super().__init__()
32
+ # self.questionGenerator = questionGenerator
33
+ # self.readSettings()
34
+ self.settings = settings
35
+ self.excelPath: Path|None = None
36
+ if self.excelPath is not None:
37
+ self.ui.buttonSpreadSheet.setText(self.excelPath.name)
38
+ self.mainPath = (self.excelPath.parent if self.excelPath is not None else None)
39
+ self.exportFile = Path()
40
+ self.testDB = testDB
41
+ self.ui = Ui_MoodleTestGenerator()
42
+ self.ui.setupUi(self)
43
+
44
+ # self.ui.buttonRefresh.clicked.connect(lambda: self.refreshList(self.test))
45
+ self.ui.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
46
+ self.ui.treeWidget.header().setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
47
+
48
+ self.ui.retranslateUi(self)
49
+ logger.info(f"Settings are stored under: {self.settings.fileName()}")
50
+ self.ui.pointCounter.setReadOnly(True)
51
+ self.setStatus("Wählen Sie bitte eine Excel Tabelle und einen Export Ordner für die Fragen aus")
52
+ try:
53
+ self.resize(self.settings.value("windowSize"))
54
+ self.move(self.settings.value("windowPosition"))
55
+ except Exception:
56
+ pass
57
+ self.connectEvents()
58
+
59
+ def connectEvents(self)->None:
60
+ self.ui.treeWidget.itemClicked.connect(self.onSelectionChanged)
61
+ self.ui.checkBoxQuestionListSelectAll.checkStateChanged.connect(self.toggleQuestionSelectionState)
62
+ qSignalLogger.emitter.signal.connect(self.updateLog)
63
+ self.ui.actionEquationChecker.triggered.connect(self.onButOpenEqChecker)
64
+ self.ui.checkBoxIncludeCategories.checkStateChanged.connect(self.setIncludeCategoriesSetting)
65
+ # self.ui.buttonRefresh.clicked.connect(self.refreshList)
66
+ self.ui.actionParseAll.triggered.connect(self.onParseAll )
67
+ self.ui.buttonSpreadSheet.clicked.connect(self.onButSpreadsheet)
68
+ self.ui.buttonTestGen.clicked.connect(self.onButGenTest)
69
+ self.testDB.dataChanged.signal.connect(self.refreshList)
70
+ self.ui.actionPreviewQ.triggered.connect(self.previewQ)
71
+ self.ui.actionDocumentation.triggered.connect(self.onOpenDocumentation)
72
+ self.settings.shPathChanged.connect(self.onSheetPathChanged)
73
+
74
+
75
+ @QtCore.Slot(Path)
76
+ def onSheetPathChanged(self, sheet:Path)->None:
77
+ logger.debug("Slot, new Spreadsheet triggered")
78
+ self.spreadSheetPath = sheet
79
+ self.mainPath = sheet.parent
80
+
81
+ def updateLog(self,log)->None:
82
+ self.ui.loggerWindow.append(log)
83
+
84
+ def setIncludeCategoriesSetting(self):
85
+ if self.ui.checkBoxIncludeCategories.isChecked():
86
+ self.settings.set("testGen/includeCats", True)
87
+ else:
88
+ self.settings.set("testGen/includeCats", False)
89
+
90
+ @QtCore.Slot()
91
+ def onOpenDocumentation(self):
92
+ documentationWindow = DocumentationWindow(dirDocumentation, self)
93
+ documentationWindow.show()
94
+
95
+
96
+ def closeEvent(self, event):
97
+ self.settings.setValue("windowSize", self.size())
98
+ self.settings.setValue("windowPosition", self.pos())
99
+
100
+ @QtCore.Slot()
101
+ def onSelectionChanged(self, item, col):
102
+ """Whenever the selection changes the total of selected points needs to be recalculated"""
103
+
104
+ count: int = 0
105
+ questions: int = 0
106
+ selection = self.ui.treeWidget.selectedItems()
107
+ for q in selection:
108
+ questions += 1
109
+ count += q.getQuestion().points
110
+
111
+ logger.info(f'{questions} questions are selected with {count} points')
112
+ self.ui.pointCounter.setValue(count)
113
+
114
+ # val = item.text(2)
115
+ # if val == "nan":
116
+ # val = 0
117
+ # else: val = float(val)
118
+ # if item.text(0).startswith('KAT'):
119
+ # logger.debug(f'seems to be a Category, doing nothing\n')
120
+ # return None
121
+ # logger.debug(f'Current Item: {item.text(1)}, is Selected? {item.isSelected()}')
122
+ # if item.isSelected():
123
+ # count = count + val
124
+ # logger.debug(f'Selected, Count up: {count}')
125
+ # else:
126
+ # count = count - val
127
+ # logger.debug(f'unselected, Count down: {count}')
128
+
129
+ return None
130
+
131
+ @QtCore.Slot()
132
+ def toggleQuestionSelectionState(self, state):
133
+ if state == Qt.Checked:
134
+ setter = True
135
+ else: setter = False
136
+ root = self.ui.treeWidget.invisibleRootItem()
137
+ childN = root.childCount()
138
+ for i in range(childN):
139
+ qs = root.child(i).childCount()
140
+ for q in range(qs):
141
+ root.child(i).child(q).setSelected(setter)
142
+
143
+ @QtCore.Slot()
144
+ def onButGenTest(self)->None:
145
+ path = QtWidgets.QFileDialog.getSaveFileName(self, "Select Output File",
146
+ dir=f"{self.mainPath/"Testfile.xml"}",
147
+ filter="xml Files (*.xml)")
148
+ self.exportFile = Path(path[0])
149
+ logger.info(f"New Export File is set{self.exportFile=}")
150
+ selection:list[QuestionItem] = self.ui.treeWidget.selectedItems()
151
+ self.testDB.appendQuestions(selection, self.exportFile)
152
+ return None
153
+
154
+ @QtCore.Slot()
155
+ def refreshList(self):
156
+ logger.info("starting List refresh")
157
+ cats = self.testDB.categories
158
+ self.ui.treeWidget.clear()
159
+ for cat in cats.values():
160
+ catItem = CategoryItem(self.ui.treeWidget,cat)
161
+ catItem.setFlags(catItem.flags() & ~Qt.ItemIsSelectable)
162
+ for q in cat.questions.values():
163
+ QuestionItem(catItem,q)
164
+ self.setStatus("[OK] Fragen Liste wurde aktualisiert")
165
+ self.ui.buttonTestGen.setEnabled(True)
166
+
167
+ @QtCore.Slot()
168
+ def onButSpreadsheet(self):
169
+ file = QtWidgets.QFileDialog.getOpenFileName(self,
170
+ self.tr("Open Spreadsheet"),
171
+ dir = str(self.mainPath),
172
+ filter=self.tr("Spreadsheet(*.xlsx *.ods)"),
173
+ selectedFilter=("*.ods"))
174
+ self.excelPath = Path(file[0]).resolve()
175
+ self.settings.setSpreadsheet(self.excelPath)
176
+ self.mainPath = self.excelPath.parent
177
+ self.ui.buttonSpreadSheet.setText(self.excelPath.name)
178
+ logger.debug(f'Saved Spreadsheet Path: {self.excelPath}\n')
179
+ self.setStatus("[OK] Excel Tabelle wurde eingelesen")
180
+ return None
181
+
182
+
183
+ @QtCore.Slot()
184
+ def onParseAll (self)->None:
185
+ """Event triggered by the *Tools/Parse all Questions* Event
186
+
187
+ It parses all the Questions found in the spreadsheet and then refreshes the list of questions.
188
+ If successful it prints out a list of all exported Questions
189
+ """
190
+ self.testDB.parseAll()
191
+ self.setStatus("[OK] Alle Fragen wurden erfolgreich in XML-Dateien umgewandelt")
192
+ self.refreshList()
193
+ return None
194
+
195
+ @QtCore.Slot()
196
+ def previewQ(self)->None:
197
+ item = self.ui.treeWidget.currentItem()
198
+ if isinstance(item, QuestionItem):
199
+ dialog = dialogs.QuestinoPreviewDialog(self, item.getQuestion())
200
+ dialog.show()
201
+ else: logger.info(f"current Item is not a Question, can't preview")
202
+
203
+ def setStatus(self, status):
204
+ self.ui.statusbar.clearMessage()
205
+ self.ui.statusbar.showMessage(self.tr(status))
206
+
207
+ @QtCore.Slot()
208
+ def onButOpenEqChecker(self):
209
+ logger.debug(f"opening wEquationChecker \n")
210
+ self.uiEqChecker = EqCheckerWindow()
211
+ self.uiEqChecker.excelFile = self.excelPath
212
+ self.uiEqChecker.show()
213
+
214
+
215
+ class EqCheckerWindow(QtWidgets.QWidget):
216
+ def __init__(self):
217
+ super().__init__()
218
+ self.excelFile = Path()
219
+ self.ui = Ui_EquationChecker()
220
+ self.ui.setupUi(self)
221
+ self.ui.buttonRunCheck.clicked.connect(lambda: self.onButRunCheck(self.ui.catNumber.value(), self.ui.qNumber.value()))
222
+
223
+ def onButRunCheck(self, catN:int, qN:int)->None:
224
+ """
225
+ Is Triggered by the ``Run Check now`` Button and runs the Equation Check
226
+ """
227
+
228
+ self.ui.textResultsOutput.clear()
229
+ bullets, results, firstResult = eqVerif.equationChecker(f'KAT_{catN}',qN, self.excelFile)
230
+ check = False
231
+ self.ui.lineFirstResult.setText(f'{firstResult}')
232
+ for i, calculation in enumerate(results):
233
+ if i == 0 and firstResult !=0:
234
+ check = eqVerif.checkResult(firstResult, calculation)
235
+ self.ui.lineCalculatedRes.setText(f'{calculation}')
236
+ self.ui.textResultsOutput.append(f"Ergebnis {i+1}: \t{calculation}\n\tMit den Werten: \n{bullets[i]}\n")
237
+
238
+ if check == True:
239
+ self.ui.lineCheckResult.setText("[OK]")
240
+ logger.info(f"Das erste berechnete Ergebnis stimmt mit dem Wert in 'firstResult' überein\n")
241
+ else:
242
+ self.ui.lineCheckResult.setText("[ERROR]")
243
+