simplified the engine navigation menu and signals

This commit is contained in:
Faraphel 2023-12-30 21:50:48 +01:00
parent 5add307607
commit 9e5567a99e
15 changed files with 429 additions and 436 deletions

View file

@ -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;
}

View file

@ -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>

View file

@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QFont from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSpinBox from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSpinBox
from source import translate from source import translate, widget
from source.survey.base import BaseSurvey from source.survey.base import BaseSurvey
@ -46,6 +46,11 @@ class IntegerQuestion(BaseSurvey):
self.entry_response.setValue(default) self.entry_response.setValue(default)
self._layout.addWidget(self.entry_response) self._layout.addWidget(self.entry_response)
# navigation
self.navigation = widget.SurveyNavigation(signals=signals)
self._layout.addWidget(self.navigation)
self.navigation.show_forward()
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "IntegerQuestion": def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "IntegerQuestion":
return cls( return cls(
@ -56,13 +61,6 @@ class IntegerQuestion(BaseSurvey):
signals=signals, 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 # data collection
def get_collected_data(self) -> dict: def get_collected_data(self) -> dict:

View file

@ -1,107 +1,15 @@
from typing import Any from typing import Type
from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtWidgets import QAbstractButton, QCheckBox
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QCheckBox, QLineEdit
from source import translate from source.survey.base import BaseChoiceQuestion
from source.survey.base import BaseSurvey
class MultipleChoiceQuestion(BaseSurvey): class MultipleChoiceQuestion(BaseChoiceQuestion):
def __init__( @classmethod
self, def get_button_choice_class(cls) -> Type[QAbstractButton]:
title: translate.Translatable, return QCheckBox
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)
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "MultipleChoiceQuestion": def are_buttons_exclusive(cls) -> bool:
return cls( return False
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

View file

@ -1,111 +1,15 @@
from typing import Any from typing import Type
from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtWidgets import QAbstractButton, QRadioButton
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QRadioButton, QButtonGroup, QLineEdit, QAbstractButton
from source import translate from source.survey.base import BaseChoiceQuestion
from source.survey.base import BaseSurvey
class SingleChoiceQuestion(BaseSurvey): class SingleChoiceQuestion(BaseChoiceQuestion):
def __init__( @classmethod
self, def get_button_choice_class(cls) -> Type[QAbstractButton]:
title: translate.Translatable, return QRadioButton
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)
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "SingleChoiceQuestion": def are_buttons_exclusive(cls) -> bool:
return cls( return True
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())

View file

@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QFont from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QVBoxLayout, QLabel from PyQt6.QtWidgets import QVBoxLayout, QLabel
from source import translate from source import translate, widget
from source.survey.base import BaseSurvey from source.survey.base import BaseSurvey
@ -19,7 +19,6 @@ class Text(BaseSurvey):
super().__init__() super().__init__()
self.abandonable = abandonable if abandonable is not None else False self.abandonable = abandonable if abandonable is not None else False
self.signals = signals if signals is not None else {}
# set the layout # set the layout
self._layout = QVBoxLayout() self._layout = QVBoxLayout()
@ -43,6 +42,14 @@ class Text(BaseSurvey):
self.label_description.setText(translate.translate(description)) self.label_description.setText(translate.translate(description))
self.label_description.setAlignment(Qt.AlignmentFlag.AlignCenter) 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 @classmethod
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "Text": def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "Text":
return cls( return cls(
@ -52,12 +59,3 @@ class Text(BaseSurvey):
signals=signals 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

View file

@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
from PyQt6.QtGui import QFont from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QTextEdit from PyQt6.QtWidgets import QVBoxLayout, QLabel, QTextEdit
from source import translate from source import translate, widget
from source.survey.base import BaseSurvey from source.survey.base import BaseSurvey
@ -33,6 +33,11 @@ class TextQuestion(BaseSurvey):
self.entry_response = QTextEdit() self.entry_response = QTextEdit()
self._layout.addWidget(self.entry_response) self._layout.addWidget(self.entry_response)
# navigation
self.navigation = widget.SurveyNavigation(signals=signals)
self._layout.addWidget(self.navigation)
self.navigation.show_forward()
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "TextQuestion": def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "TextQuestion":
return cls( return cls(
@ -40,13 +45,6 @@ class TextQuestion(BaseSurvey):
signals=signals, 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 # data collection
def get_collected_data(self) -> dict: def get_collected_data(self) -> dict:

View file

@ -1,17 +1,12 @@
import time import time
from pathlib import Path
from typing import Optional, Any from typing import Optional, Any
from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QUrl, QEvent, QObject, QPointF from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QUrl, QEvent, QObject, QPointF
from PyQt6.QtGui import QFont, QMouseEvent, QResizeEvent, QKeyEvent from PyQt6.QtGui import QFont, QMouseEvent, QResizeEvent, QKeyEvent
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QSizePolicy from PyQt6.QtWidgets import QLabel, QVBoxLayout, QSizePolicy
from source import translate from source import translate, widget
from source import assets_path
from source.survey.base import BaseSurvey from source.survey.base import BaseSurvey
from source.widget import Browser
page_success_path: Path = assets_path / "web/success.html"
class WebMission(BaseSurvey): class WebMission(BaseSurvey):
@ -52,7 +47,7 @@ class WebMission(BaseSurvey):
self.label_title.setFont(font_title) self.label_title.setFont(font_title)
# web page # web page
self.browser = Browser() self.browser = widget.Browser()
self._layout.addWidget(self.browser) self._layout.addWidget(self.browser)
self.browser.web.focusProxy().installEventFilter(self) # capture the event in eventFilter self.browser.web.focusProxy().installEventFilter(self) # capture the event in eventFilter
self.browser.web.urlChanged.connect(self._on_url_changed) # NOQA: connect exist self.browser.web.urlChanged.connect(self._on_url_changed) # NOQA: connect exist
@ -76,6 +71,10 @@ class WebMission(BaseSurvey):
# setup the events # setup the events
self.browser.web.page().scrollPositionChanged.connect(self._on_scroll_position_changed) self.browser.web.page().scrollPositionChanged.connect(self._on_scroll_position_changed)
# navigation
self.navigation = widget.SurveyNavigation(signals=signals)
self._layout.addWidget(self.navigation)
@classmethod @classmethod
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "WebMission": def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "WebMission":
return cls( return cls(
@ -170,6 +169,8 @@ class WebMission(BaseSurvey):
return super().eventFilter(obj, event) return super().eventFilter(obj, event)
def on_show(self) -> None: def on_show(self) -> None:
# TODO: remove ?
# initialize the start time # initialize the start time
self.start_time = time.time() self.start_time = time.time()
@ -190,6 +191,8 @@ class WebMission(BaseSurvey):
self.timer_skip.start() self.timer_skip.start()
def on_hide(self) -> None: def on_hide(self) -> None:
# TODO: remove ?
# disable full screen mode # disable full screen mode
self.window().showNormal() self.window().showNormal()
@ -215,13 +218,17 @@ class WebMission(BaseSurvey):
# mark the mission as finished # mark the mission as finished
self._finished = True self._finished = True
# change the content of the page to the success message def _on_url_changed(self):
self.browser.web.load(QUrl.fromLocalFile(str(page_success_path.absolute()))) # log the new url
self._save_event(
type="url",
url=self.browser.web.url().toString()
)
def _on_time_skip(self): def _on_time_skip(self):
# when the timer to allow skip have run out # when the timer to allow skip have run out
if "skip" in self.signals: if "skip" in self.signals:
self.signals["skip"].emit() # NOQA: emit exist self.navigation.show_skip()
# condition # condition
@ -258,10 +265,3 @@ class WebMission(BaseSurvey):
return { return {
"event": self._collected_events, "event": self._collected_events,
} }
def _on_url_changed(self):
# log the new url
self._save_event(
type="url",
url=self.browser.web.url().toString()
)

View 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

View file

@ -2,10 +2,10 @@ from abc import abstractmethod
from typing import Optional, Any from typing import Optional, Any
from PyQt6.QtCore import pyqtSignal 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 A type of survey survey that can be in the user interface
""" """

View file

@ -1 +1,2 @@
from .BaseSurvey import BaseSurvey from .BaseSurvey import BaseSurvey
from .BaseChoiceQuestion import BaseChoiceQuestion

View file

@ -9,8 +9,9 @@ from typing import Optional
import nextcord import nextcord
import requests import requests
from PyQt6.QtCore import pyqtSignal 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.base import BaseSurvey
from source.survey import Empty, survey_get 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 self.signal_success.connect(self._on_signal_success) # NOQA: connect exist
# prepare the survey collected data # 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.discord_webhook_result_url = discord_webhook_result_url
self.current_survey_index = 0 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 # load the survey screens
# TODO: create dynamically
self.survey_screens = [ self.survey_screens = [
( (
survey_id, survey_id,
@ -93,8 +59,21 @@ class SurveyEngine(QWidget):
for survey_id, survey_data in surveys_data.items() for survey_id, survey_data in surveys_data.items()
] ]
# update the progress bar # set the layout
self.progress.setMaximum(len(self.survey_screens)) 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 # finalize the initialisation
self.update_survey() self.update_survey()
@ -116,16 +95,16 @@ class SurveyEngine(QWidget):
# events # events
def _on_signal_abandon(self): def _on_signal_abandon(self):
# on success, show the button to give up # on abandon, quit the survey
self.button_abandon.show() self.quit()
def _on_signal_skip(self): def _on_signal_skip(self):
# on success, show the button to skip # on skip, skip to the next survey
self.button_skip.show() self.next_survey()
def _on_signal_success(self): def _on_signal_success(self):
# on success, show the button to go forward # on success, go to the next survey
self.button_forward.show() self.next_survey()
def next_survey(self): def next_survey(self):
# get the collected data from the survey # get the collected data from the survey
@ -145,11 +124,6 @@ class SurveyEngine(QWidget):
self.finish_survey() self.finish_survey()
def update_survey(self): 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 # mark the actual survey as the old one
old_frame_survey = self.frame_survey old_frame_survey = self.frame_survey
# call the old survey event # call the old survey event

View 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)

View file

@ -1,3 +1,4 @@
from .Browser import Browser from .Browser import Browser
from .SurveyEngine import SurveyEngine from .SurveyEngine import SurveyEngine
from .SurveyWindow import SurveyWindow from .SurveyWindow import SurveyWindow
from .SurveyNavigation import SurveyNavigation

View file

@ -51,29 +51,39 @@
}, },
"choices": { "choices": {
"always": { "always": {
"en": "Always", "text": {
"fr": "Tout le temps", "en": "Always",
"sp": "Siempre" "fr": "Tout le temps",
"sp": "Siempre"
}
}, },
"often": { "often": {
"en": "Often", "text": {
"fr": "Souvent", "en": "Often",
"sp": "A menudo" "fr": "Souvent",
"sp": "A menudo"
}
}, },
"sometime": { "sometime": {
"en": "Sometimes", "text": {
"fr": "De temps en temps", "en": "Sometimes",
"sp": "De vez en cuando" "fr": "De temps en temps",
"sp": "De vez en cuando"
}
}, },
"rarely": { "rarely": {
"en": "Rarely", "text": {
"fr": "Rarement", "en": "Rarely",
"sp": "Raramente" "fr": "Rarement",
"sp": "Raramente"
}
}, },
"never": { "never": {
"en": "Never", "text": {
"fr": "Jamais", "en": "Never",
"sp": "Nunca" "fr": "Jamais",
"sp": "Nunca"
}
} }
} }
}, },
@ -87,37 +97,48 @@
}, },
"choices": { "choices": {
"epic": { "epic": {
"en": "Epic Games Store", "text": {
"fr": "Epic Games Store", "en": "Epic Games Store",
"sp": "Epic Games Store" "fr": "Epic Games Store",
"sp": "Epic Games Store"
}
}, },
"gog": { "gog": {
"en": "GOG", "text": {
"fr": "GOG", "en": "GOG",
"sp": "GOG" "fr": "GOG",
"sp": "GOG"
}
}, },
"origin": { "origin": {
"en": "Origin", "text": {
"fr": "Origin", "en": "Origin",
"sp": "Origin" "fr": "Origin",
"sp": "Origin"
}
}, },
"uplay": { "uplay": {
"en": "Uplay", "text": {
"fr": "Uplay", "en": "Uplay",
"sp": "Uplay" "fr": "Uplay",
"sp": "Uplay"
}
}, },
"battle": { "battle": {
"en": "Battle.net", "text": {
"fr": "Battle.net", "en": "Battle.net",
"sp": "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": { "choices": {
"no": { "no": {
"en": "No", "text": {
"fr": "Non", "en": "No",
"sp": "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": { "choices": {
"mission-language": { "mission-language": {
"en": "Change the language", "text": {
"fr": "Changer la langue", "en": "Change the language",
"sp": "Cambiar el idioma" "fr": "Changer la langue",
"sp": "Cambiar el idioma"
}
}, },
"mission-price": { "mission-price": {
"en": "Filter games by their price", "text": {
"fr": "Filtrer les jeux par leur prix", "en": "Filter games by their price",
"sp": "Filtrar juegos por su precio" "fr": "Filtrer les jeux par leur prix",
"sp": "Filtrar juegos por su precio"
}
}, },
"mission-community-hub": { "mission-community-hub": {
"en": "Visit the community hub", "text": {
"fr": "Se rendre sur le hub de la communauté", "en": "Visit the community hub",
"sp": "Ir al hub de la comunidad" "fr": "Se rendre sur le hub de la communauté",
"sp": "Ir al hub de la comunidad"
}
}, },
"mission-game-page": { "mission-game-page": {
"en": "Visit a game page", "text": {
"fr": "Se rendre sur la page d'un jeu", "en": "Visit a game page",
"sp": "Visitar la página de un juego" "fr": "Se rendre sur la page d'un jeu",
"sp": "Visitar la página de un juego"
}
}, },
"mission-game-dlc": { "mission-game-dlc": {
"en": "Visit the downloadable content (DLC) page of a game", "text": {
"fr": "Se rendre sur la page du contenu additionnel (DLC) d'un jeu", "en": "Visit the downloadable content (DLC) page of a game",
"sp": "Visitar la página del contenido adicional descargable (DLC) de un juego" "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": { "mission-actuality-new": {
"en": "Visit the \"Featured\" news page", "text": {
"fr": "Se rendre sur la page des actualités \"À la une\"", "en": "Visit the \"Featured\" news page",
"sp": "Visitar la página de noticias \"Destacadas\"" "fr": "Se rendre sur la page des actualités \"À la une\"",
"sp": "Visitar la página de noticias \"Destacadas\""
}
}, },
"mission-profile": { "mission-profile": {
"en": "Visit a user's profile", "text": {
"fr": "Se rendre sur le profil d'un utilisateur", "en": "Visit a user's profile",
"sp": "Visitar el perfil de un usuario" "fr": "Se rendre sur le profil d'un utilisateur",
"sp": "Visitar el perfil de un usuario"
}
}, },
"mission-game-discussion": { "mission-game-discussion": {
"en": "Visit the discussion page of a game", "text": {
"fr": "Se rendre sur la page de discussion d'un jeu", "en": "Visit the discussion page of a game",
"sp": "Visitar la página de discusión de un juego" "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": { "mission-gift-card": {
"en": "Visit the gift cards page", "text": {
"fr": "Se rendre sur la page des cartes cadeaux", "en": "Visit the gift cards page",
"sp": "Ir a la página de tarjetas de regalo" "fr": "Se rendre sur la page des cartes cadeaux",
"sp": "Ir a la página de tarjetas de regalo"
}
}, },
"mission-workshop": { "mission-workshop": {
"en": "Visit the modification (mods) page of a game", "text": {
"fr": "Se rendre sur la page de la modification (mods) d'un jeu", "en": "Visit the modification (mods) page of a game",
"sp": "Visitar la página de modificación (mods) de un juego" "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": { "choices": {
"yes": { "yes": {
"en": "Yes", "text": {
"fr": "Oui", "en": "Yes",
"sp": "Sí" "fr": "Oui",
"sp": "Sí"
}
}, },
"mixed": { "mixed": {
"en": "Partially", "text": {
"fr": "Mitigé", "en": "Partially",
"sp": "Parcialmente" "fr": "Mitigé",
"sp": "Parcialmente"
}
}, },
"no": { "no": {
"en": "No", "text": {
"fr": "Non", "en": "No",
"sp": "No" "fr": "Non",
"sp": "No"
}
} }
} }
}, },
@ -393,9 +443,9 @@
"sp": "Agradecimientos" "sp": "Agradecimientos"
}, },
"description": { "description": {
"en": "We greatly appreciate your contribution to our survey and your time.\n\nYour collected data is located in the \"resultat\" folder.", "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 \"resultat\".", "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 \"resultat\"." "sp": "Agradecemos enormemente su contribución a nuestro cuestionario y su tiempo.\n\nSus datos recopilados se encuentran en la carpeta \"results\"."
} }
} }
} }