simplified the engine navigation menu and signals
This commit is contained in:
parent
5add307607
commit
9e5567a99e
15 changed files with 429 additions and 436 deletions
|
@ -1,22 +0,0 @@
|
|||
.notification {
|
||||
/* center the div */
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
/* set the size to shrink to the text */
|
||||
width: fit-content;
|
||||
|
||||
/* set the text */
|
||||
text-align: center;
|
||||
font-size: 150%;
|
||||
font-family: "Arial", sans-serif;
|
||||
font-weight: bold;
|
||||
|
||||
/* set the border */
|
||||
border: 4px solid black;
|
||||
border-radius: 2vh;
|
||||
|
||||
padding: 20px;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" href="common.css" />
|
||||
<title>Succès</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="notification-container">
|
||||
<div class="notification">
|
||||
<p>
|
||||
Vous avez réussi cette étape.<br/>
|
||||
Vous pouvez à présent passer à la suivante.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
|
|||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSpinBox
|
||||
|
||||
from source import translate
|
||||
from source import translate, widget
|
||||
from source.survey.base import BaseSurvey
|
||||
|
||||
|
||||
|
@ -46,6 +46,11 @@ class IntegerQuestion(BaseSurvey):
|
|||
self.entry_response.setValue(default)
|
||||
self._layout.addWidget(self.entry_response)
|
||||
|
||||
# navigation
|
||||
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||
self._layout.addWidget(self.navigation)
|
||||
self.navigation.show_forward()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "IntegerQuestion":
|
||||
return cls(
|
||||
|
@ -56,13 +61,6 @@ class IntegerQuestion(BaseSurvey):
|
|||
signals=signals,
|
||||
)
|
||||
|
||||
# events
|
||||
|
||||
def on_show(self) -> None:
|
||||
# immediately mark the survey as successful
|
||||
if "success" in self.signals:
|
||||
self.signals["success"].emit() # NOQA: emit exist
|
||||
|
||||
# data collection
|
||||
|
||||
def get_collected_data(self) -> dict:
|
||||
|
|
|
@ -1,107 +1,15 @@
|
|||
from typing import Any
|
||||
from typing import Type
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QCheckBox, QLineEdit
|
||||
from PyQt6.QtWidgets import QAbstractButton, QCheckBox
|
||||
|
||||
from source import translate
|
||||
from source.survey.base import BaseSurvey
|
||||
from source.survey.base import BaseChoiceQuestion
|
||||
|
||||
|
||||
class MultipleChoiceQuestion(BaseSurvey):
|
||||
def __init__(
|
||||
self,
|
||||
title: translate.Translatable,
|
||||
choices: dict[Any, translate.Translatable],
|
||||
details_choice_enabled: bool = None,
|
||||
details_choice_id: str = None,
|
||||
details_choice_text: translate.Translatable = None,
|
||||
signals: dict[str, pyqtSignal] = None
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.details_choice_enabled = details_choice_enabled if details_choice_enabled is not None else None
|
||||
self.details_choice_id = details_choice_id if details_choice_id is not None else None
|
||||
self.details_choice_text = details_choice_text if details_choice_text is not None else None
|
||||
self.signals = signals if signals is not None else {}
|
||||
|
||||
# set layout
|
||||
self._layout = QVBoxLayout()
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# question title
|
||||
self.label_question = QLabel()
|
||||
self._layout.addWidget(self.label_question)
|
||||
self.label_question.setText(translate.translate(title))
|
||||
self.label_question.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
font_title = self.label_question.font()
|
||||
font_title.setPointSize(24)
|
||||
font_title.setWeight(QFont.Weight.Bold)
|
||||
self.label_question.setFont(font_title)
|
||||
|
||||
# responses
|
||||
self.frame_responses = QFrame()
|
||||
self._layout.addWidget(self.frame_responses)
|
||||
|
||||
self._layout_responses = QVBoxLayout()
|
||||
self.frame_responses.setLayout(self._layout_responses)
|
||||
|
||||
self.button_responses: dict[str, QCheckBox] = {}
|
||||
|
||||
for choice_id, choice_text in choices.items():
|
||||
# create a radio button for that choice
|
||||
button = QCheckBox()
|
||||
button.setText(translate.translate(choice_text))
|
||||
|
||||
# add the button to the frame
|
||||
self._layout_responses.addWidget(button)
|
||||
|
||||
# save the button
|
||||
self.button_responses[choice_id] = button
|
||||
|
||||
if self.details_choice_enabled:
|
||||
self.button_response_other = QCheckBox()
|
||||
self._layout_responses.addWidget(self.button_response_other)
|
||||
self.button_response_other.setText(translate.translate(self.details_choice_text))
|
||||
self.button_response_other.toggled.connect(self._on_response_other_check) # NOQA: connect exist
|
||||
|
||||
self.entry_response_other = QLineEdit()
|
||||
self._layout_responses.addWidget(self.entry_response_other)
|
||||
self.entry_response_other.setEnabled(False)
|
||||
class MultipleChoiceQuestion(BaseChoiceQuestion):
|
||||
@classmethod
|
||||
def get_button_choice_class(cls) -> Type[QAbstractButton]:
|
||||
return QCheckBox
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "MultipleChoiceQuestion":
|
||||
return cls(
|
||||
title=data["title"],
|
||||
choices=data["choices"],
|
||||
details_choice_enabled=data.get("details_choice_enabled"),
|
||||
details_choice_id=data.get("details_choice_id"),
|
||||
details_choice_text=data.get("details_choice_text"),
|
||||
|
||||
signals=signals,
|
||||
)
|
||||
|
||||
def on_show(self) -> None:
|
||||
if "success" in self.signals:
|
||||
# the user can skip a text whenever he wants to, directly signal a success
|
||||
self.signals["success"].emit() # NOQA: emit exist
|
||||
|
||||
def _on_response_other_check(self):
|
||||
# refresh the other entry response status
|
||||
self.entry_response_other.setEnabled(self.button_response_other.isChecked())
|
||||
|
||||
def get_collected_data(self) -> dict:
|
||||
collected_data = {
|
||||
"choice": [
|
||||
choice_id
|
||||
for choice_id, button in self.button_responses.items()
|
||||
if button.isChecked()
|
||||
]
|
||||
}
|
||||
|
||||
if self.details_choice_enabled:
|
||||
collected_data["choice"].append(self.details_choice_id)
|
||||
collected_data["other"] = self.entry_response_other.text()
|
||||
|
||||
return collected_data
|
||||
def are_buttons_exclusive(cls) -> bool:
|
||||
return False
|
||||
|
|
|
@ -1,111 +1,15 @@
|
|||
from typing import Any
|
||||
from typing import Type
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QRadioButton, QButtonGroup, QLineEdit, QAbstractButton
|
||||
from PyQt6.QtWidgets import QAbstractButton, QRadioButton
|
||||
|
||||
from source import translate
|
||||
from source.survey.base import BaseSurvey
|
||||
from source.survey.base import BaseChoiceQuestion
|
||||
|
||||
|
||||
class SingleChoiceQuestion(BaseSurvey):
|
||||
def __init__(
|
||||
self,
|
||||
title: translate.Translatable,
|
||||
details_choice_enabled: bool = None,
|
||||
details_choice_id: str = None,
|
||||
details_choice_text: translate.Translatable = None,
|
||||
choices: dict[Any, translate.Translatable] = None,
|
||||
signals: dict[str, pyqtSignal] = None
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.details_choice_enabled = details_choice_enabled if details_choice_enabled is not None else None
|
||||
self.details_choice_id = details_choice_id if details_choice_id is not None else None
|
||||
self.details_choice_text = details_choice_text if details_choice_text is not None else None
|
||||
|
||||
choices = choices if choices is not None else {}
|
||||
signals = signals if signals is not None else {}
|
||||
|
||||
# set layout
|
||||
self._layout = QVBoxLayout()
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# question title
|
||||
self.label_question = QLabel()
|
||||
self._layout.addWidget(self.label_question)
|
||||
self.label_question.setText(translate.translate(title))
|
||||
self.label_question.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
font_title = self.label_question.font()
|
||||
font_title.setPointSize(24)
|
||||
font_title.setWeight(QFont.Weight.Bold)
|
||||
self.label_question.setFont(font_title)
|
||||
|
||||
# responses
|
||||
self.frame_responses = QFrame()
|
||||
self._layout.addWidget(self.frame_responses)
|
||||
|
||||
self._layout_responses = QVBoxLayout()
|
||||
self.frame_responses.setLayout(self._layout_responses)
|
||||
|
||||
self.group_responses = QButtonGroup()
|
||||
|
||||
if "success" in signals:
|
||||
# checking any button allow the user to go to the next step
|
||||
self.group_responses.buttonClicked.connect(signals["success"].emit) # NOQA: connect and emit exists
|
||||
|
||||
self.button_responses_id: dict[QAbstractButton, str] = {}
|
||||
|
||||
for choice_id, choice_text in choices.items():
|
||||
# create a radio button for that choice
|
||||
button = QRadioButton()
|
||||
button.setText(translate.translate(choice_text))
|
||||
|
||||
# add the button to the frame
|
||||
self._layout_responses.addWidget(button)
|
||||
|
||||
# add the button to the group
|
||||
self.group_responses.addButton(button)
|
||||
self.button_responses_id[button] = choice_id
|
||||
|
||||
if self.details_choice_enabled:
|
||||
self.button_response_other = QRadioButton()
|
||||
self._layout_responses.addWidget(self.button_response_other)
|
||||
self.button_responses_id[self.button_response_other] = self.details_choice_id
|
||||
|
||||
self.button_response_other.setText(translate.translate(self.details_choice_text))
|
||||
self.group_responses.addButton(self.button_response_other)
|
||||
self.button_response_other.toggled.connect(self._on_response_other_check) # NOQA: connect exist
|
||||
|
||||
self.entry_response_other = QLineEdit()
|
||||
self._layout_responses.addWidget(self.entry_response_other)
|
||||
self.entry_response_other.setEnabled(False)
|
||||
class SingleChoiceQuestion(BaseChoiceQuestion):
|
||||
@classmethod
|
||||
def get_button_choice_class(cls) -> Type[QAbstractButton]:
|
||||
return QRadioButton
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "SingleChoiceQuestion":
|
||||
return cls(
|
||||
title=data["title"],
|
||||
choices=data["choices"],
|
||||
details_choice_enabled=data.get("details_choice_enabled"),
|
||||
details_choice_id=data.get("details_choice_id"),
|
||||
details_choice_text=data.get("details_choice_text"),
|
||||
|
||||
signals=signals,
|
||||
)
|
||||
|
||||
def get_collected_data(self) -> dict:
|
||||
checked_button = self.group_responses.checkedButton()
|
||||
|
||||
collected_data = {
|
||||
"choice": self.button_responses_id[checked_button] if checked_button is not None else None,
|
||||
}
|
||||
|
||||
if self.details_choice_enabled:
|
||||
collected_data["other"] = self.entry_response_other.text()
|
||||
|
||||
return collected_data
|
||||
|
||||
def _on_response_other_check(self):
|
||||
# refresh the other entry response status
|
||||
self.entry_response_other.setEnabled(self.button_response_other.isChecked())
|
||||
def are_buttons_exclusive(cls) -> bool:
|
||||
return True
|
||||
|
|
|
@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
|
|||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtWidgets import QVBoxLayout, QLabel
|
||||
|
||||
from source import translate
|
||||
from source import translate, widget
|
||||
from source.survey.base import BaseSurvey
|
||||
|
||||
|
||||
|
@ -19,7 +19,6 @@ class Text(BaseSurvey):
|
|||
super().__init__()
|
||||
|
||||
self.abandonable = abandonable if abandonable is not None else False
|
||||
self.signals = signals if signals is not None else {}
|
||||
|
||||
# set the layout
|
||||
self._layout = QVBoxLayout()
|
||||
|
@ -43,6 +42,14 @@ class Text(BaseSurvey):
|
|||
self.label_description.setText(translate.translate(description))
|
||||
self.label_description.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# navigation
|
||||
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||
self._layout.addWidget(self.navigation)
|
||||
|
||||
self.navigation.show_forward() # always show forward
|
||||
if self.abandonable:
|
||||
self.navigation.show_abandon() # if enabled, show abandon
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "Text":
|
||||
return cls(
|
||||
|
@ -52,12 +59,3 @@ class Text(BaseSurvey):
|
|||
|
||||
signals=signals
|
||||
)
|
||||
|
||||
def on_show(self) -> None:
|
||||
if "success" in self.signals:
|
||||
# the user can skip a text whenever he wants to, directly signal a success
|
||||
self.signals["success"].emit() # NOQA: emit exist
|
||||
|
||||
# if abandon is enabled, emit on the corresponding signal
|
||||
if self.abandonable and "abandon" in self.signals:
|
||||
self.signals["abandon"].emit() # NOQA: emit exist
|
||||
|
|
|
@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
|
|||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QTextEdit
|
||||
|
||||
from source import translate
|
||||
from source import translate, widget
|
||||
from source.survey.base import BaseSurvey
|
||||
|
||||
|
||||
|
@ -33,6 +33,11 @@ class TextQuestion(BaseSurvey):
|
|||
self.entry_response = QTextEdit()
|
||||
self._layout.addWidget(self.entry_response)
|
||||
|
||||
# navigation
|
||||
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||
self._layout.addWidget(self.navigation)
|
||||
self.navigation.show_forward()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "TextQuestion":
|
||||
return cls(
|
||||
|
@ -40,13 +45,6 @@ class TextQuestion(BaseSurvey):
|
|||
signals=signals,
|
||||
)
|
||||
|
||||
# events
|
||||
|
||||
def on_show(self) -> None:
|
||||
# immediately mark the survey as successful
|
||||
if "success" in self.signals:
|
||||
self.signals["success"].emit() # NOQA: emit exist
|
||||
|
||||
# data collection
|
||||
|
||||
def get_collected_data(self) -> dict:
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional, Any
|
||||
|
||||
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QUrl, QEvent, QObject, QPointF
|
||||
from PyQt6.QtGui import QFont, QMouseEvent, QResizeEvent, QKeyEvent
|
||||
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QSizePolicy
|
||||
|
||||
from source import translate
|
||||
from source import assets_path
|
||||
from source import translate, widget
|
||||
from source.survey.base import BaseSurvey
|
||||
from source.widget import Browser
|
||||
|
||||
page_success_path: Path = assets_path / "web/success.html"
|
||||
|
||||
|
||||
class WebMission(BaseSurvey):
|
||||
|
@ -52,7 +47,7 @@ class WebMission(BaseSurvey):
|
|||
self.label_title.setFont(font_title)
|
||||
|
||||
# web page
|
||||
self.browser = Browser()
|
||||
self.browser = widget.Browser()
|
||||
self._layout.addWidget(self.browser)
|
||||
self.browser.web.focusProxy().installEventFilter(self) # capture the event in eventFilter
|
||||
self.browser.web.urlChanged.connect(self._on_url_changed) # NOQA: connect exist
|
||||
|
@ -76,6 +71,10 @@ class WebMission(BaseSurvey):
|
|||
# setup the events
|
||||
self.browser.web.page().scrollPositionChanged.connect(self._on_scroll_position_changed)
|
||||
|
||||
# navigation
|
||||
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||
self._layout.addWidget(self.navigation)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "WebMission":
|
||||
return cls(
|
||||
|
@ -170,6 +169,8 @@ class WebMission(BaseSurvey):
|
|||
return super().eventFilter(obj, event)
|
||||
|
||||
def on_show(self) -> None:
|
||||
# TODO: remove ?
|
||||
|
||||
# initialize the start time
|
||||
self.start_time = time.time()
|
||||
|
||||
|
@ -190,6 +191,8 @@ class WebMission(BaseSurvey):
|
|||
self.timer_skip.start()
|
||||
|
||||
def on_hide(self) -> None:
|
||||
# TODO: remove ?
|
||||
|
||||
# disable full screen mode
|
||||
self.window().showNormal()
|
||||
|
||||
|
@ -215,13 +218,17 @@ class WebMission(BaseSurvey):
|
|||
# mark the mission as finished
|
||||
self._finished = True
|
||||
|
||||
# change the content of the page to the success message
|
||||
self.browser.web.load(QUrl.fromLocalFile(str(page_success_path.absolute())))
|
||||
def _on_url_changed(self):
|
||||
# log the new url
|
||||
self._save_event(
|
||||
type="url",
|
||||
url=self.browser.web.url().toString()
|
||||
)
|
||||
|
||||
def _on_time_skip(self):
|
||||
# when the timer to allow skip have run out
|
||||
if "skip" in self.signals:
|
||||
self.signals["skip"].emit() # NOQA: emit exist
|
||||
self.navigation.show_skip()
|
||||
|
||||
# condition
|
||||
|
||||
|
@ -258,10 +265,3 @@ class WebMission(BaseSurvey):
|
|||
return {
|
||||
"event": self._collected_events,
|
||||
}
|
||||
|
||||
def _on_url_changed(self):
|
||||
# log the new url
|
||||
self._save_event(
|
||||
type="url",
|
||||
url=self.browser.web.url().toString()
|
||||
)
|
||||
|
|
124
source/survey/base/BaseChoiceQuestion.py
Normal file
124
source/survey/base/BaseChoiceQuestion.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
from abc import abstractmethod
|
||||
from typing import Any, Type
|
||||
|
||||
from PyQt6.QtCore import Qt, pyqtSignal
|
||||
from PyQt6.QtGui import QFont
|
||||
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QButtonGroup, QLineEdit, QAbstractButton
|
||||
|
||||
from source import translate, widget
|
||||
from source.survey.base import BaseSurvey
|
||||
|
||||
|
||||
class BaseChoiceQuestion(BaseSurvey):
|
||||
"""
|
||||
Base for a question that contains multiple options
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_button_choice_class(cls) -> Type[QAbstractButton]:
|
||||
"""
|
||||
The class for the button representing the choices
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def are_buttons_exclusive(cls) -> bool:
|
||||
"""
|
||||
Are the buttons exclusive ?
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
title: translate.Translatable,
|
||||
choices: dict,
|
||||
signals: dict[str, pyqtSignal] = None
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
signals = signals if signals is not None else {}
|
||||
|
||||
# set layout
|
||||
self._layout = QVBoxLayout()
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# question title
|
||||
self.label_question = QLabel()
|
||||
self._layout.addWidget(self.label_question)
|
||||
self.label_question.setText(translate.translate(title))
|
||||
self.label_question.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
font_title = self.label_question.font()
|
||||
font_title.setPointSize(24)
|
||||
font_title.setWeight(QFont.Weight.Bold)
|
||||
self.label_question.setFont(font_title)
|
||||
|
||||
# prepare navigation
|
||||
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||
|
||||
# responses
|
||||
self.frame_responses = QFrame()
|
||||
self._layout.addWidget(self.frame_responses)
|
||||
|
||||
self._layout_responses = QVBoxLayout()
|
||||
self.frame_responses.setLayout(self._layout_responses)
|
||||
|
||||
self.group_responses = QButtonGroup()
|
||||
self.group_responses.setExclusive(self.are_buttons_exclusive())
|
||||
|
||||
# checking any button allow the user to go to the next step
|
||||
self.group_responses.buttonClicked.connect(self.navigation.show_forward) # NOQA: connect and emit exists
|
||||
|
||||
self.buttons_responses: dict[QAbstractButton, dict] = {}
|
||||
button_choice_class = self.get_button_choice_class()
|
||||
|
||||
for choice_id, choice_data in choices.items():
|
||||
# create a radio button for that choice
|
||||
button = button_choice_class()
|
||||
button.setText(translate.translate(choice_data["text"]))
|
||||
|
||||
# add the button to the frame
|
||||
self._layout_responses.addWidget(button)
|
||||
|
||||
# add the button to the group
|
||||
self.group_responses.addButton(button)
|
||||
|
||||
# if the choice should ask the user for details
|
||||
entry = None
|
||||
if choice_data.get("ask_details", False):
|
||||
entry = QLineEdit()
|
||||
self._layout_responses.addWidget(entry)
|
||||
entry.setEnabled(False)
|
||||
|
||||
# toggling the button should also toggle the entry
|
||||
button.toggled.connect(entry.setEnabled) # NOQA: connect exist
|
||||
|
||||
# save the button and some data
|
||||
self.buttons_responses[choice_id] = {
|
||||
"button": button,
|
||||
"entry": entry
|
||||
}
|
||||
|
||||
# add the navigation
|
||||
self._layout.addWidget(self.navigation)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "BaseChoiceQuestion":
|
||||
return cls(
|
||||
title=data["title"],
|
||||
choices=data["choices"],
|
||||
signals=signals,
|
||||
)
|
||||
|
||||
def get_collected_data(self) -> dict:
|
||||
collected_data = {
|
||||
"choices": {
|
||||
choice_id: {
|
||||
"checked": choice_data["button"].isEnabled(),
|
||||
"details": entry.text() if (entry := choice_data["entry"]) is not None else None
|
||||
}
|
||||
for choice_id, choice_data in self.buttons_responses.items()
|
||||
}
|
||||
}
|
||||
|
||||
return collected_data
|
|
@ -2,10 +2,10 @@ from abc import abstractmethod
|
|||
from typing import Optional, Any
|
||||
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtWidgets import QFrame
|
||||
from PyQt6.QtWidgets import QWidget
|
||||
|
||||
|
||||
class BaseSurvey(QFrame):
|
||||
class BaseSurvey(QWidget):
|
||||
"""
|
||||
A type of survey survey that can be in the user interface
|
||||
"""
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
from .BaseSurvey import BaseSurvey
|
||||
from .BaseChoiceQuestion import BaseChoiceQuestion
|
||||
|
|
|
@ -9,8 +9,9 @@ from typing import Optional
|
|||
import nextcord
|
||||
import requests
|
||||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QHBoxLayout, QPushButton, QProgressBar, QWidget
|
||||
from PyQt6.QtWidgets import QVBoxLayout, QProgressBar, QWidget
|
||||
|
||||
from source import translate
|
||||
from source.survey.base import BaseSurvey
|
||||
from source.survey import Empty, survey_get
|
||||
|
||||
|
@ -33,51 +34,16 @@ class SurveyEngine(QWidget):
|
|||
self.signal_success.connect(self._on_signal_success) # NOQA: connect exist
|
||||
|
||||
# prepare the survey collected data
|
||||
self.collected_datas: dict[str, dict] = {"time": time.time(), "surveys": {}}
|
||||
self.collected_datas: dict[str, dict] = {
|
||||
"time": time.time(), # get the time of the start of the survey
|
||||
"language": translate.get_language(), # get the user language
|
||||
"surveys": {} # prepare the individual surveys data
|
||||
}
|
||||
self.discord_webhook_result_url = discord_webhook_result_url
|
||||
self.current_survey_index = 0
|
||||
|
||||
# set the layout
|
||||
self._layout = QVBoxLayout()
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# prepare the frame for the survey elements
|
||||
self.frame_survey: BaseSurvey = Empty()
|
||||
self._layout.addWidget(self.frame_survey)
|
||||
|
||||
# navigations actions
|
||||
self.frame_navigation = QFrame()
|
||||
self._layout.addWidget(self.frame_navigation)
|
||||
|
||||
self._layout_navigation = QHBoxLayout()
|
||||
self.frame_navigation.setLayout(self._layout_navigation)
|
||||
|
||||
self._layout_navigation.addStretch(0) # add a stretch to put the buttons on the right
|
||||
|
||||
self.button_abandon = QPushButton()
|
||||
self._layout_navigation.addWidget(self.button_abandon)
|
||||
self.button_abandon.setText(self.tr("ABANDON"))
|
||||
self.button_abandon.setStyleSheet("QPushButton { color : red; }")
|
||||
self.button_abandon.clicked.connect(self.quit) # NOQA: connect exist
|
||||
|
||||
self.button_skip = QPushButton()
|
||||
self._layout_navigation.addWidget(self.button_skip)
|
||||
self.button_skip.setText(self.tr("SKIP"))
|
||||
self.button_skip.clicked.connect(self.next_survey) # NOQA: connect exist
|
||||
|
||||
self.button_forward = QPushButton()
|
||||
self._layout_navigation.addWidget(self.button_forward)
|
||||
self.button_forward.setText(self.tr("NEXT"))
|
||||
self.button_forward.clicked.connect(self.next_survey) # NOQA: connect exist
|
||||
|
||||
# progress bar
|
||||
self.progress = QProgressBar()
|
||||
self._layout.addWidget(self.progress)
|
||||
self.progress.setStyleSheet("QProgressBar::chunk { background-color: #03A9FC; }")
|
||||
self.progress.setTextVisible(False)
|
||||
self.progress.setFixedHeight(8)
|
||||
|
||||
# load the survey screens
|
||||
# TODO: create dynamically
|
||||
self.survey_screens = [
|
||||
(
|
||||
survey_id,
|
||||
|
@ -93,8 +59,21 @@ class SurveyEngine(QWidget):
|
|||
for survey_id, survey_data in surveys_data.items()
|
||||
]
|
||||
|
||||
# update the progress bar
|
||||
self.progress.setMaximum(len(self.survey_screens))
|
||||
# set the layout
|
||||
self._layout = QVBoxLayout()
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# prepare the frame for the survey elements
|
||||
self.frame_survey: BaseSurvey = Empty()
|
||||
self._layout.addWidget(self.frame_survey)
|
||||
|
||||
# progress bar
|
||||
self.progress = QProgressBar()
|
||||
self._layout.addWidget(self.progress)
|
||||
self.progress.setStyleSheet("QProgressBar::chunk { background-color: #03A9FC; }")
|
||||
self.progress.setTextVisible(False)
|
||||
self.progress.setFixedHeight(8)
|
||||
self.progress.setMaximum(len(surveys_data))
|
||||
|
||||
# finalize the initialisation
|
||||
self.update_survey()
|
||||
|
@ -116,16 +95,16 @@ class SurveyEngine(QWidget):
|
|||
# events
|
||||
|
||||
def _on_signal_abandon(self):
|
||||
# on success, show the button to give up
|
||||
self.button_abandon.show()
|
||||
# on abandon, quit the survey
|
||||
self.quit()
|
||||
|
||||
def _on_signal_skip(self):
|
||||
# on success, show the button to skip
|
||||
self.button_skip.show()
|
||||
# on skip, skip to the next survey
|
||||
self.next_survey()
|
||||
|
||||
def _on_signal_success(self):
|
||||
# on success, show the button to go forward
|
||||
self.button_forward.show()
|
||||
# on success, go to the next survey
|
||||
self.next_survey()
|
||||
|
||||
def next_survey(self):
|
||||
# get the collected data from the survey
|
||||
|
@ -145,11 +124,6 @@ class SurveyEngine(QWidget):
|
|||
self.finish_survey()
|
||||
|
||||
def update_survey(self):
|
||||
# disable the buttons
|
||||
self.button_abandon.hide()
|
||||
self.button_skip.hide()
|
||||
self.button_forward.hide()
|
||||
|
||||
# mark the actual survey as the old one
|
||||
old_frame_survey = self.frame_survey
|
||||
# call the old survey event
|
||||
|
|
77
source/widget/SurveyNavigation.py
Normal file
77
source/widget/SurveyNavigation.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
from PyQt6.QtCore import pyqtSignal
|
||||
from PyQt6.QtWidgets import QWidget, QHBoxLayout, QPushButton
|
||||
|
||||
|
||||
class SurveyNavigation(QWidget):
|
||||
def __init__(self, signals: dict[str, pyqtSignal] = None):
|
||||
super().__init__()
|
||||
|
||||
self._layout = QHBoxLayout()
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# force the element to be on the right with a stretch
|
||||
self._layout.addStretch()
|
||||
|
||||
# abandon button
|
||||
|
||||
self._button_abandon = QPushButton()
|
||||
self._layout.addWidget(self._button_abandon)
|
||||
self._button_abandon.setText(self.tr("ABANDON"))
|
||||
self._button_abandon.setStyleSheet("QPushButton { color : red; }")
|
||||
|
||||
if signals is not None and "abandon" in signals:
|
||||
self._button_abandon.clicked.connect(signals["abandon"].emit) # NOQA: connect and emit exist
|
||||
|
||||
# skip button
|
||||
|
||||
self._button_skip = QPushButton()
|
||||
self._layout.addWidget(self._button_skip)
|
||||
self._button_skip.setText(self.tr("SKIP"))
|
||||
|
||||
if signals is not None and "skip" in signals:
|
||||
self._button_skip.clicked.connect(signals["skip"].emit) # NOQA: connect and emit exist
|
||||
|
||||
# forward button
|
||||
|
||||
self._button_forward = QPushButton()
|
||||
self._layout.addWidget(self._button_forward)
|
||||
self._button_forward.setText(self.tr("NEXT"))
|
||||
|
||||
if signals is not None and "success" in signals:
|
||||
self._button_forward.clicked.connect(signals["success"].emit) # NOQA: connect and emit exist
|
||||
|
||||
# get all buttons
|
||||
self._buttons = [
|
||||
self._button_abandon,
|
||||
self._button_skip,
|
||||
self._button_forward
|
||||
]
|
||||
|
||||
# hide every button per default
|
||||
self.hide_all()
|
||||
|
||||
def show_abandon(self):
|
||||
self._button_abandon.setVisible(True)
|
||||
|
||||
def hide_abandon(self):
|
||||
self._button_abandon.setVisible(False)
|
||||
|
||||
def show_skip(self):
|
||||
self._button_skip.setVisible(True)
|
||||
|
||||
def hide_skip(self):
|
||||
self._button_skip.setVisible(False)
|
||||
|
||||
def show_forward(self):
|
||||
self._button_forward.setVisible(True)
|
||||
|
||||
def hide_forward(self):
|
||||
self._button_forward.setVisible(False)
|
||||
|
||||
def show_all(self):
|
||||
for button in self._buttons:
|
||||
button.setVisible(True)
|
||||
|
||||
def hide_all(self):
|
||||
for button in self._buttons:
|
||||
button.setVisible(False)
|
|
@ -1,3 +1,4 @@
|
|||
from .Browser import Browser
|
||||
from .SurveyEngine import SurveyEngine
|
||||
from .SurveyWindow import SurveyWindow
|
||||
from .SurveyNavigation import SurveyNavigation
|
||||
|
|
228
surveys.json
228
surveys.json
|
@ -51,29 +51,39 @@
|
|||
},
|
||||
"choices": {
|
||||
"always": {
|
||||
"en": "Always",
|
||||
"fr": "Tout le temps",
|
||||
"sp": "Siempre"
|
||||
"text": {
|
||||
"en": "Always",
|
||||
"fr": "Tout le temps",
|
||||
"sp": "Siempre"
|
||||
}
|
||||
},
|
||||
"often": {
|
||||
"en": "Often",
|
||||
"fr": "Souvent",
|
||||
"sp": "A menudo"
|
||||
"text": {
|
||||
"en": "Often",
|
||||
"fr": "Souvent",
|
||||
"sp": "A menudo"
|
||||
}
|
||||
},
|
||||
"sometime": {
|
||||
"en": "Sometimes",
|
||||
"fr": "De temps en temps",
|
||||
"sp": "De vez en cuando"
|
||||
"text": {
|
||||
"en": "Sometimes",
|
||||
"fr": "De temps en temps",
|
||||
"sp": "De vez en cuando"
|
||||
}
|
||||
},
|
||||
"rarely": {
|
||||
"en": "Rarely",
|
||||
"fr": "Rarement",
|
||||
"sp": "Raramente"
|
||||
"text": {
|
||||
"en": "Rarely",
|
||||
"fr": "Rarement",
|
||||
"sp": "Raramente"
|
||||
}
|
||||
},
|
||||
"never": {
|
||||
"en": "Never",
|
||||
"fr": "Jamais",
|
||||
"sp": "Nunca"
|
||||
"text": {
|
||||
"en": "Never",
|
||||
"fr": "Jamais",
|
||||
"sp": "Nunca"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -87,37 +97,48 @@
|
|||
},
|
||||
"choices": {
|
||||
"epic": {
|
||||
"en": "Epic Games Store",
|
||||
"fr": "Epic Games Store",
|
||||
"sp": "Epic Games Store"
|
||||
"text": {
|
||||
"en": "Epic Games Store",
|
||||
"fr": "Epic Games Store",
|
||||
"sp": "Epic Games Store"
|
||||
}
|
||||
},
|
||||
"gog": {
|
||||
"en": "GOG",
|
||||
"fr": "GOG",
|
||||
"sp": "GOG"
|
||||
"text": {
|
||||
"en": "GOG",
|
||||
"fr": "GOG",
|
||||
"sp": "GOG"
|
||||
}
|
||||
},
|
||||
"origin": {
|
||||
"en": "Origin",
|
||||
"fr": "Origin",
|
||||
"sp": "Origin"
|
||||
"text": {
|
||||
"en": "Origin",
|
||||
"fr": "Origin",
|
||||
"sp": "Origin"
|
||||
}
|
||||
},
|
||||
"uplay": {
|
||||
"en": "Uplay",
|
||||
"fr": "Uplay",
|
||||
"sp": "Uplay"
|
||||
"text": {
|
||||
"en": "Uplay",
|
||||
"fr": "Uplay",
|
||||
"sp": "Uplay"
|
||||
}
|
||||
},
|
||||
"battle": {
|
||||
"en": "Battle.net",
|
||||
"fr": "Battle.net",
|
||||
"sp": "Battle.net"
|
||||
"text": {
|
||||
"en": "Battle.net",
|
||||
"fr": "Battle.net",
|
||||
"sp": "Battle.net"
|
||||
}
|
||||
},
|
||||
"other": {
|
||||
"text": {
|
||||
"en": "Other",
|
||||
"fr": "Autre",
|
||||
"sp": "Otro"
|
||||
},
|
||||
"ask_details": true
|
||||
}
|
||||
},
|
||||
"details_choice_enabled": true,
|
||||
"details_choice_id": "other",
|
||||
"details_choice_text": {
|
||||
"en": "Other",
|
||||
"fr": "Autre",
|
||||
"sp": "Otro"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -130,17 +151,20 @@
|
|||
},
|
||||
"choices": {
|
||||
"no": {
|
||||
"en": "No",
|
||||
"fr": "Non",
|
||||
"sp": "No"
|
||||
"text": {
|
||||
"en": "No",
|
||||
"fr": "Non",
|
||||
"sp": "No"
|
||||
}
|
||||
},
|
||||
"yes": {
|
||||
"text": {
|
||||
"en": "Yes",
|
||||
"fr": "Oui",
|
||||
"sp": "Sí"
|
||||
},
|
||||
"ask_details": true
|
||||
}
|
||||
},
|
||||
"details_choice_enabled": true,
|
||||
"details_choice_id": "yes",
|
||||
"details_choice_text": {
|
||||
"en": "Yes",
|
||||
"fr": "Oui",
|
||||
"sp": "Sí"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -289,54 +313,74 @@
|
|||
},
|
||||
"choices": {
|
||||
"mission-language": {
|
||||
"en": "Change the language",
|
||||
"fr": "Changer la langue",
|
||||
"sp": "Cambiar el idioma"
|
||||
"text": {
|
||||
"en": "Change the language",
|
||||
"fr": "Changer la langue",
|
||||
"sp": "Cambiar el idioma"
|
||||
}
|
||||
},
|
||||
"mission-price": {
|
||||
"en": "Filter games by their price",
|
||||
"fr": "Filtrer les jeux par leur prix",
|
||||
"sp": "Filtrar juegos por su precio"
|
||||
"text": {
|
||||
"en": "Filter games by their price",
|
||||
"fr": "Filtrer les jeux par leur prix",
|
||||
"sp": "Filtrar juegos por su precio"
|
||||
}
|
||||
},
|
||||
"mission-community-hub": {
|
||||
"en": "Visit the community hub",
|
||||
"fr": "Se rendre sur le hub de la communauté",
|
||||
"sp": "Ir al hub de la comunidad"
|
||||
"text": {
|
||||
"en": "Visit the community hub",
|
||||
"fr": "Se rendre sur le hub de la communauté",
|
||||
"sp": "Ir al hub de la comunidad"
|
||||
}
|
||||
},
|
||||
"mission-game-page": {
|
||||
"en": "Visit a game page",
|
||||
"fr": "Se rendre sur la page d'un jeu",
|
||||
"sp": "Visitar la página de un juego"
|
||||
"text": {
|
||||
"en": "Visit a game page",
|
||||
"fr": "Se rendre sur la page d'un jeu",
|
||||
"sp": "Visitar la página de un juego"
|
||||
}
|
||||
},
|
||||
"mission-game-dlc": {
|
||||
"en": "Visit the downloadable content (DLC) page of a game",
|
||||
"fr": "Se rendre sur la page du contenu additionnel (DLC) d'un jeu",
|
||||
"sp": "Visitar la página del contenido adicional descargable (DLC) de un juego"
|
||||
"text": {
|
||||
"en": "Visit the downloadable content (DLC) page of a game",
|
||||
"fr": "Se rendre sur la page du contenu additionnel (DLC) d'un jeu",
|
||||
"sp": "Visitar la página del contenido adicional descargable (DLC) de un juego"
|
||||
}
|
||||
},
|
||||
"mission-actuality-new": {
|
||||
"en": "Visit the \"Featured\" news page",
|
||||
"fr": "Se rendre sur la page des actualités \"À la une\"",
|
||||
"sp": "Visitar la página de noticias \"Destacadas\""
|
||||
"text": {
|
||||
"en": "Visit the \"Featured\" news page",
|
||||
"fr": "Se rendre sur la page des actualités \"À la une\"",
|
||||
"sp": "Visitar la página de noticias \"Destacadas\""
|
||||
}
|
||||
},
|
||||
"mission-profile": {
|
||||
"en": "Visit a user's profile",
|
||||
"fr": "Se rendre sur le profil d'un utilisateur",
|
||||
"sp": "Visitar el perfil de un usuario"
|
||||
"text": {
|
||||
"en": "Visit a user's profile",
|
||||
"fr": "Se rendre sur le profil d'un utilisateur",
|
||||
"sp": "Visitar el perfil de un usuario"
|
||||
}
|
||||
},
|
||||
"mission-game-discussion": {
|
||||
"en": "Visit the discussion page of a game",
|
||||
"fr": "Se rendre sur la page de discussion d'un jeu",
|
||||
"sp": "Visitar la página de discusión de un juego"
|
||||
"text": {
|
||||
"en": "Visit the discussion page of a game",
|
||||
"fr": "Se rendre sur la page de discussion d'un jeu",
|
||||
"sp": "Visitar la página de discusión de un juego"
|
||||
}
|
||||
},
|
||||
"mission-gift-card": {
|
||||
"en": "Visit the gift cards page",
|
||||
"fr": "Se rendre sur la page des cartes cadeaux",
|
||||
"sp": "Ir a la página de tarjetas de regalo"
|
||||
"text": {
|
||||
"en": "Visit the gift cards page",
|
||||
"fr": "Se rendre sur la page des cartes cadeaux",
|
||||
"sp": "Ir a la página de tarjetas de regalo"
|
||||
}
|
||||
},
|
||||
"mission-workshop": {
|
||||
"en": "Visit the modification (mods) page of a game",
|
||||
"fr": "Se rendre sur la page de la modification (mods) d'un jeu",
|
||||
"sp": "Visitar la página de modificación (mods) de un juego"
|
||||
"text": {
|
||||
"en": "Visit the modification (mods) page of a game",
|
||||
"fr": "Se rendre sur la page de la modification (mods) d'un jeu",
|
||||
"sp": "Visitar la página de modificación (mods) de un juego"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -350,19 +394,25 @@
|
|||
},
|
||||
"choices": {
|
||||
"yes": {
|
||||
"en": "Yes",
|
||||
"fr": "Oui",
|
||||
"sp": "Sí"
|
||||
"text": {
|
||||
"en": "Yes",
|
||||
"fr": "Oui",
|
||||
"sp": "Sí"
|
||||
}
|
||||
},
|
||||
"mixed": {
|
||||
"en": "Partially",
|
||||
"fr": "Mitigé",
|
||||
"sp": "Parcialmente"
|
||||
"text": {
|
||||
"en": "Partially",
|
||||
"fr": "Mitigé",
|
||||
"sp": "Parcialmente"
|
||||
}
|
||||
},
|
||||
"no": {
|
||||
"en": "No",
|
||||
"fr": "Non",
|
||||
"sp": "No"
|
||||
"text": {
|
||||
"en": "No",
|
||||
"fr": "Non",
|
||||
"sp": "No"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -393,9 +443,9 @@
|
|||
"sp": "Agradecimientos"
|
||||
},
|
||||
"description": {
|
||||
"en": "We greatly appreciate your contribution to our survey and your time.\n\nYour collected data is located in the \"resultat\" folder.",
|
||||
"fr": "Nous vous remercions grandement pour votre contribution à notre questionnaire et pour votre temps.\n\nVos données collectées sont situées dans le dossier \"resultat\".",
|
||||
"sp": "Agradecemos enormemente su contribución a nuestro cuestionario y su tiempo.\n\nSus datos recopilados se encuentran en la carpeta \"resultat\"."
|
||||
"en": "We greatly appreciate your contribution to our survey and your time.\n\nYour collected data is located in the \"results\" folder.",
|
||||
"fr": "Nous vous remercions grandement pour votre contribution à notre questionnaire et pour votre temps.\n\nVos données collectées sont situées dans le dossier \"results\".",
|
||||
"sp": "Agradecemos enormemente su contribución a nuestro cuestionario y su tiempo.\n\nSus datos recopilados se encuentran en la carpeta \"results\"."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue