Beispiel #1
0
    def move_desk(self, start, end):
        """Moves a desk from start to end
        if end is out of the screen, we remove the desk"""

        max_row = int(AssetManager.getInstance().config(
            "size", "default_room_rows"))
        max_col = int(AssetManager.getInstance().config(
            "size", "default_room_columns"))
        if len(start) == 0:
            return

        id_desk_start = self.mod_bdd.get_desk_id_in_course_by_coords(
            self.main_ctrl.id_course, start[0], start[1])
        if 0 <= end[0] < max_row and 0 <= end[1] < max_col:
            # Destination is in the canvas
            id_desk_end = self.mod_bdd.get_desk_id_in_course_by_coords(
                self.main_ctrl.id_course, end[0], end[1])
            if id_desk_end == 0 and id_desk_start != 0:
                # We just change the coordinates
                self.mod_bdd.move_desk_by_id(id_desk_start, end[0], end[1])
                # We update the view
                self.v_canvas.move_tile(id_desk_start, end)
            elif id_desk_end != 0 and id_desk_start != 0:
                # We swap the two desks
                self.mod_bdd.move_desk_by_id(id_desk_start, end[0], end[1])
                self.mod_bdd.move_desk_by_id(id_desk_end, start[0], start[1])
                # We update the view
                self.v_canvas.move_tile(id_desk_start, end)
                self.v_canvas.move_tile(id_desk_end, start, True)
        else:
            self.mod_bdd.remove_desk_by_id(id_desk_start)
            self.show_course()
        self.__bdd.commit()
        self.v_canvas.repaint()
Beispiel #2
0
    def on_edit_config(self) -> None:
        """
        Displays the edition dialog for application settings
        """
        dlg = SettingsEditionDialog()

        if dlg.exec_():
            if dlg.restore_default():
                AssetManager.getInstance().restore_default_settings()
            else:
                AssetManager.getInstance().save_config(dlg.new_config())

            self.status_bar.showMessage(tr("acknowledge_changes"), 3000)
            self.repaint()

            if dlg.need_restart():
                restart_confirm = VConfirmDialog(self, "need_restart")
                restart_confirm.ok_btn.setText(tr("restart_now"))
                restart_confirm.ok_btn.setFixedSize(QSize(105, 33))
                restart_confirm.cancel_btn.setText(tr("restart_later"))
                restart_confirm.cancel_btn.setFixedSize(QSize(105, 33))

                if restart_confirm.exec_():
                    self.reboot_requested = True
                    self.close()
                    self.restart()
Beispiel #3
0
    def __draw_dragged_tile(self, painter, tile, x, y):
        """
        Draws the given tile under the mouse position

        :param painter: painter object
        :param tile: tile data object
        :param x: real mouse position x
        :param y: real mouse position y
        """
        if not self.hovered:  # If there is no tile under the dragged one, we draw a light gray rect below it
            # Convert x and y to get the hovered empty tile position
            hov_x = x // self.desk_h_size * self.desk_h_size
            hov_y = y // self.desk_w_size * self.desk_w_size
            hov_rect = self.__get_rect_at(hov_y, hov_x)
            painter.fillRect(
                hov_rect,
                QColor(AssetManager.getInstance().config(
                    'colors', 'hovered_empty_tile')))

        rect = QRect(
            QPoint(PADDING + y - self.desk_w_size / 2,
                   PADDING + x - self.desk_h_size / 2),
            QPoint(y + self.desk_w_size / 2 - PADDING,
                   x + self.desk_h_size / 2 - PADDING))
        painter.fillRect(
            rect,
            QColor(AssetManager.getInstance().config('colors',
                                                     'dragged_tile')))
        painter.drawText(rect, Qt.AlignCenter | Qt.TextWordWrap,
                         f"{tile.lastname()}\n{tile.firstname()}")
Beispiel #4
0
    def __init__(self, sig_move_animation_ended):
        """
        Application's main canvas, in which is drawn desks and student's names.

        :param sig_move_animation_ended: signal to trigger when the move animation ends
        :type sig_move_animation_ended: Signal
        """
        QWidget.__init__(self)

        self.desk_h_size = int(AssetManager.getInstance().config(
            'size', 'desk_height'))
        self.desk_w_size = int(AssetManager.getInstance().config(
            'size', 'desk_width'))
        self.setAutoFillBackground(True)

        self.__tiles = {}  # All drawn tiles
        self.tmp = {}
        self.hovered = False

        self.is_view_students = True
        self.__do_switch = False
        self.__is_config = False

        self.sig_canvas_click = None  # Signal triggered when a click is performed on a desk
        self.sig_desk_selected = None  # Signal triggered when a non empty desk is selected
        self.sig_canvas_drag = None  # Signal triggered when a drag operation is performed on the canvas
        self.sig_select_tile = None  # pushed by the controller. emits when tile selection change.
        self.sig_tile_info = None  # Signal triggered when a right-click is performed on a desk. Info is to be displayed
        self.sig_move_animation_ended = sig_move_animation_ended

        # Tracking for drag/drop operation
        self.__click_pos = ()  # Stored (row, column) position
        self.__mouse_pos = ()  # Stored (x, y) mouse position

        # Signals
        self.sig_move_ended.connect(self.on_move_ended)

        self.__running_animations = 0
        self.update_timer = None  # Timer running only during animations to perform the UI update

        self.nb_rows = int(AssetManager.getInstance().config(
            'size', 'default_room_rows'))
        self.nb_columns = int(AssetManager.getInstance().config(
            'size', 'default_room_columns'))
        self.setFixedSize(self.desk_w_size * self.nb_columns,
                          self.desk_h_size * self.nb_rows)
        self.__init_style()
Beispiel #5
0
    def __init__(self, sig_move_animation_ended):
        """
        Widget containing the main canvas (classroom), the board's position and topic selection.

        :param sig_move_animation_ended: Signal to trigger when the move animation ended
        """
        QWidget.__init__(self)

        # Widgets
        self.v_canvas = ViewCanvas(sig_move_animation_ended)  # Central canvas
        self.view_students = ViewTeacherDeskLabel(
            tr("perspective_student_txt"),
            AssetManager.getInstance().config('colors', 'board_bg'))
        self.view_teacher = ViewTeacherDeskLabel(
            tr("perspective_teacher_txt"),
            AssetManager.getInstance().config('colors', 'board_bg'))

        # Layout
        self.__set_layout()
Beispiel #6
0
    def __init_style(self):
        """
        Sets the background of this canvas widget
        """
        color = AssetManager.getInstance().config(
            'colors', 'room_bg') if self.__is_config else "white"

        pal = QPalette()
        pal.setColor(QPalette.Background, QColor(color))
        self.setPalette(pal)
Beispiel #7
0
    def student_attr_pick(self):
        """Randomly chooses a student among not selected ones"""
        desks_id = self.get_unselected_occupied_desks_id()
        list_id_attr = self.gui.sidewidget.attributes().selected_attributes()
        if not desks_id or len(list_id_attr) != 1:
            # should be always be true otherwise the hutton is disabled
            self.gui.status_bar.showMessage("Houston, we have a problem !")
            return

        id_attr = list_id_attr[0]
        attr_type = self.mod_bdd.get_attribute_type_from_id(id_attr)
        id_topic = self.mod_bdd.get_topic_id_by_course_id(
            self.main_ctrl.id_course)

        canditates = dict()
        colors = AssetManager.getInstance().config('colors',
                                                   'attr_colors').split()

        for d_id in desks_id:
            desk = self.mod_bdd.get_desk_by_id(d_id)
            val = self.mod_bdd.get_attribute_value(desk.id_student, id_attr,
                                                   id_topic)
            # Determine a order key depending on the attribute type
            if attr_type == EAttributesTypes.TEXT.value:
                # for text attr, we select empty attributes first
                key = 1 if val else 0
            elif attr_type == EAttributesTypes.MARK.value:
                # for mark attribute, the key is the number of marks
                key = len(val.split())
            elif attr_type == EAttributesTypes.COLOR.value:
                # for color value, key is the order in config.ini
                if val in colors:
                    key = colors.index(val)
                else:
                    key = -1
            elif attr_type == EAttributesTypes.COUNTER.value:
                # for counter attribute, key is the counter value
                key = 0 if val == "" else int(val)
            else:
                key = 0

            # Update the dictionary of candidates
            if key in canditates:
                canditates[key].append(d_id)
            else:
                canditates[key] = [d_id]

        # Now we pick a desk by choosing in the minimum key value
        minkey = min(canditates.keys())
        desk_id = choice(canditates[minkey])

        # Make selection
        self.do_desk_selection_change(desk_id, True)  # change selection on app
        self.on_desk_selection_changed_on_app(desk_id,
                                              True)  # change selection on web
Beispiel #8
0
def start_app():

    while True:
        AssetManager.start_instance()
        try:
            app = QApplication(sys.argv)
        except RuntimeError:
            app = QtCore.QCoreApplication.instance()
        flask = flask_app.FlaskThread()
        ctrl = MainController()

        exit_code = None
        if ctrl.mod_bdd is not None:
            flask.init_controller(ctrl)
            ctrl.gui.show()
            exit_code = app.exec_()
        else:
            flask.stop_flask()
        if exit_code != EXIT_CODE_REBOOT:
            break
    return exit_code
    def export_csv(self, file_path: str) -> None:
        """
        Saves the attributes table in a CSV format at the specified file path
        """
        attributes, students, data = self.get_attributes_matrix()

        # build the attribute type dict
        attr_type = dict()
        for a in attributes:
            attr_type[a[0]] = self.mod_bdd.get_attribute_type_from_id(a[0])

        # generate CSV file
        with open(file_path, 'w', newline='') as csvfile:
            writer = csv.writer(csvfile,
                                delimiter=AssetManager.getInstance().config(
                                    "main", "csv_separator"),
                                quoting=csv.QUOTE_MINIMAL)
            # Write first row
            first_row = ['Nom Prenom']
            for a in attributes:
                first_row.append(a[1])
            writer.writerow(first_row)

            # Write datas
            for s in students:
                row = [s[1]]
                for a in attributes:
                    id_a, id_s = a[0], s[0]
                    if (id_a, id_s) in data:
                        # We have a data to export
                        if attr_type[id_a] == EAttributesTypes.TEXT.value:
                            row.append(data[(id_a, id_s)])
                        elif attr_type[id_a] == EAttributesTypes.MARK.value:
                            mark_list = data[(id_a, id_s)].split()
                            try:
                                somme = sum(map(int, mark_list))
                                row.append(somme / len(mark_list))
                            except:
                                row.append("")
                        elif attr_type[id_a] == EAttributesTypes.COLOR.value:
                            hexcol = data[(id_a, id_s)].name().lower()
                            col_name = COLOR_DICT[
                                hexcol] if hexcol in COLOR_DICT else hexcol
                            row.append(col_name)
                        elif attr_type[id_a] == EAttributesTypes.COUNTER.value:
                            row.append(int(data[(id_a, id_s)]))
                    else:
                        # No data in this cell
                        row.append("")
                # write current row
                writer.writerow(row)
Beispiel #10
0
    def __init__(self, parent):
        """
        Confirm dialog for dangerous actions

        :param parent: gui's main window
        """
        QDialog.__init__(self, parent)

        # QR Code
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.has_internet = True

        try:
            s.connect(('8.8.8.8', 1))  # connect() for UDP does not send packets, this will require an internet connection

            # IP and Port
            local_ip_address = s.getsockname()[0]
            s.close()
            port = AssetManager.getInstance().config('webapp', 'port')

            # get tmp folder
            qr_path = tempfile.mktemp()

            s = f"http://{local_ip_address}:{port}/mobile"  # String which represents the QR code
            self.url = pyqrcode.create(s)  # Generate QR code
            self.url.png(qr_path, scale=6)  # Create and save the QR png file

            # Widget
            self.qr = QLabel()  # Label that contains the QR
            pix = QPixmap(QImage(qr_path))
            self.qr.setPixmap(pix)

            # Layout
            layout = QVBoxLayout()
            layout.setMargin(0)
            layout.addWidget(self.qr, alignment=Qt.AlignCenter)
            layout.addWidget(InfoToolTip("qr_tip"), alignment=Qt.AlignCenter)
            layout.addSpacing(5)
            self.setLayout(layout)

            self.setStyleSheet("background: white;")
        except OSError:
            self.has_internet = False
Beispiel #11
0
    def __init__(self, parent, current_val):
        """
        Colors editor

        :param parent: gui's main window
        :param current_val: current field actual value (to set by default)
        """
        VDialogEdit.__init__(self, parent, current_val)

        self.colors = AssetManager.getInstance().config(
            "colors", "attr_colors").split()
        self.btns = []

        layout = QHBoxLayout()

        for c in self.colors:
            b = ColorButton(c, self.__set_selection, c == current_val.upper())
            self.btns.append(b)
            layout.addWidget(b)

        self.main_layout.addLayout(layout, 0, 0, 1, 2)
    def import_pronote(self) -> None:
        groups = self.mod_bdd.get_groups()
        dlg = DialogImportCsv(self.gui, ["Nouveau Groupe"] + groups)

        if dlg.exec_():
            name_group = dlg.selected_group()
            file_path = dlg.selected_file()
            if name_group == "Nouveau Groupe":
                f = file_path.split("/")[-1].upper()
                name_group = f[0:f.index(".CSV")]
            csv_sep = AssetManager.getInstance().config(
                "main", "csv_separator")
            names = import_csv(file_path, csv_sep)
            id_group = self.mod_bdd.create_group(name_group)
            order = 0
            for std in names:
                self.mod_bdd.insert_student_in_group_id(
                    std[1], std[0], order, id_group)
                order += 1
            self.__bdd.commit()

            self.show_all_groups()
Beispiel #13
0
 def run(self):
     asset_manager = AssetManager.getInstance()
     socket_io.run(flask_app,
                   port=asset_manager.config('webapp', 'port'),
                   host='0.0.0.0')
Beispiel #14
0
def get_bdd_connection():
    bdd_path, _ = AssetManager.getInstance().bdd_path()
    bdd = sqlite3.connect(bdd_path)
    return ModBdd(bdd)
Beispiel #15
0
    def sort_desks(self, sort_type="Z"):
        """Sort students by their position
        detects clusters of students to make a clever sort"""
        def build_matrix():
            matrix = []
            for row in range(max_row):
                line = []
                for col in range(max_col):
                    id_desk = self.mod_bdd.get_desk_id_in_course_by_coords(
                        self.main_ctrl.id_course, row, col)
                    if id_desk != 0:
                        student = self.mod_bdd.get_student_by_desk_id(id_desk)
                        if student is not None:
                            line.append(student.id)
                        else:
                            line.append(-1)  # empty desk
                    else:
                        line.append(0)  # no desk
                matrix.append(line)
            return matrix

        def search_cluster(coords, row, col):
            """search all cells in a cluster from row, col position"""
            if not (0 <= row < max_row and 0 <= col < max_col):
                return False
            if (row, col) in visited:
                return False
            if student_matrix[row][col] == 0:
                return False

            if coords in clusters:
                clusters[coords].append((row, col))
            else:
                clusters[coords] = [(row, col)]

            visited.add((row, col))
            search_cluster(coords, row - 1, col - 1)
            search_cluster(coords, row - 1, col)
            search_cluster(coords, row - 1, col + 1)
            search_cluster(coords, row, col - 1)
            search_cluster(coords, row, col + 1)
            search_cluster(coords, row + 1, col - 1)
            search_cluster(coords, row + 1, col)
            search_cluster(coords, row + 1, col + 1)
            return True

        def type_Z():
            # we sort the students using the clusters in a simple row by row pattern
            ic, ir = 0, 0
            while (ir, ic) in clusters:
                while (ir, ic) in clusters:
                    for coords in clusters[(ir, ic)]:
                        # we add all students in the cluster
                        sortlist.append(student_matrix[coords[0]][coords[1]])
                    ic += 1
                ic = 0
                ir += 1

        def type_2():
            # we sort the students using the clusters in 2 pattern
            ic, ir = 0, 0
            alt = True
            while (ir, ic) in clusters:
                # gets the number of cols in this row
                while (ir, ic) in clusters:
                    ic += 1
                nb_cols = ic
                for ic in range(nb_cols):
                    if alt:
                        ic_alt = ic
                    else:
                        ic_alt = nb_cols - ic - 1
                        # reverse the list when we walk right to left
                        clusters[(ir, ic_alt)] = clusters[(ir, ic_alt)][::-1]
                    for coords in clusters[(ir, ic_alt)]:
                        # we add all students in the cluster
                        sortlist.append(student_matrix[coords[0]][coords[1]])
                alt = not alt
                ic = 0
                ir += 1

        def type_U():
            # we sort the students using the clusters in U pattern
            ic, ir = 0, 0
            alt = True
            while (ir, ic) in clusters:
                # gets the number of rows in this col
                while (ir, ic) in clusters:
                    ir += 1
                nb_rows = ir
                for ir in range(nb_rows):
                    ir_alt = ir if alt else nb_rows - ir - 1
                    for coords in clusters[(ir_alt, ic)]:
                        # we add all students in the cluster
                        sortlist.append(student_matrix[coords[0]][coords[1]])
                alt = not alt
                ir = 0
                ic += 1

        self.gui.status_bar.showMessage(tr("grp_action_sort_by_place"), 3000)
        max_row = int(AssetManager.getInstance().config(
            "size", "default_room_rows"))
        max_col = int(AssetManager.getInstance().config(
            "size", "default_room_columns"))
        infty = max_row * max_col + 1
        group_name = self.mod_bdd.get_group_name_by_id(self.main_ctrl.id_group)
        # First we initialize the sort key for the group to infty
        group_students_id = self.mod_bdd.get_students_in_group(
            self.mod_bdd.get_group_name_by_id(self.main_ctrl.id_group))
        for std in group_students_id:
            self.mod_bdd.update_student_order_with_id(std.id, infty)

        student_matrix = build_matrix()
        clusters = dict()
        visited = set()
        # populating clusters dictionnary
        ro = 0
        for r in range(max_row):
            co = 0
            empty = True
            for c in range(max_col):
                if search_cluster((ro, co), r, c):
                    co += 1
                    empty = False
            if not empty:
                ro += 1

        # Now we get an ordered list of students matching the desk disposition
        sortlist = []  # this list is modified in one of the following function
        if sort_type == "Z":
            type_Z()
        elif sort_type == "2":
            type_2()
        else:
            type_U()
        # At last, we update the sort key to re-order the list
        orderkey = 0
        for s in sortlist:
            if s != -1:
                # -1 is an empty desk
                self.mod_bdd.update_student_order_with_id(s, orderkey)
                orderkey += 1
        self.__bdd.commit()
        self.main_ctrl.on_config_changed()
        self.synchronize_canvas_selection_with_side_list()
    def _set_labels_and_layout(self):
        """
        Creates this Widget's content and layout
        """
        # --- Content ---

        # Lecluse DevCorp. Logo
        logo = QLabel()
        logo.setPixmap(QPixmap("assets/LDC-dark.png"))
        logo.setFixedSize(QSize(
            512, 222))  # Original dimension is 2048x888, we divided by 4
        logo.setScaledContents(True)

        # App credo
        lab_app = QLabel(f'<b>{tr("app_title")}</b> {tr("about_sdc")}')

        # Devs
        features_lab = QLabel(tr("about_features"))
        ihm_lab = QLabel(tr("about_ihm"))
        web_lab = QLabel(tr("about_web"))
        features_dev = QLabel(
            f'{self.links_style}<a href="https://www.lecluse.fr">Olivier Lécluse</a>'
        )
        features_dev.setOpenExternalLinks(True)
        ihm_dev = QLabel(
            f'{self.links_style}<a href="https://www.linkedin.com/in/thomas-lécluse-62130395/">Thomas Lécluse</a>'
        )
        ihm_dev.setOpenExternalLinks(True)
        web_dev = QLabel(
            f'{self.links_style}<a href="https://www.linkedin.com/in/nicolas-lecluse-a3488752/">Nicolas Lécluse</a>'
        )
        web_dev.setOpenExternalLinks(True)

        # Documentation link
        lab_link_doc = QLabel(
            f'{tr("link_to")} {self.links_style}<a href="https://sdc.lecluse.fr">{tr("about_doc")}</a>'
        )
        lab_link_doc.setOpenExternalLinks(True)

        # Github link
        lab_link_git = QLabel(
            f'{tr("link_to")} {self.links_style}<a href="https://github.com/wawachief/SalleDeClasse">{tr("about_github")}</a>'
        )
        lab_link_git.setOpenExternalLinks(True)

        # Contact
        lab_contact = QLabel(
            f'{tr("about_contact")} {self.links_style}<a href="mailto:[email protected]">[email protected]</a>'
        )
        lab_contact.setOpenExternalLinks(True)

        # License
        lab_license = QLabel(
            f'{self.links_style}<a href="https://www.gnu.org/licenses/gpl-3.0.fr.html">GPL-3.0 License</a>'
        )
        lab_license.setOpenExternalLinks(True)

        # Version
        lab_app_version = QLabel(tr("app_version"))
        lab_bdd_version = QLabel(tr("bdd_version"))
        app_version = QLabel(AssetManager.getInstance().config(
            'main', 'version'))
        bdd_version = QLabel(str(self.bdd_version))

        # --- Layout ---
        box = QVBoxLayout()
        box.setMargin(0)
        box.setSpacing(0)

        # Logo
        box.addWidget(logo, alignment=Qt.AlignCenter)

        Separator(self.width(), box)  # ----

        # 'Salle de Classe' credo
        box.addWidget(lab_app, alignment=Qt.AlignCenter)
        box.addSpacing(SPACING_SIZE)

        # Devs roles
        dev_grid = QGridLayout()
        dev_grid.setContentsMargins(0, 0, 0, 0)
        dev_grid.addWidget(features_lab, 0, 0, alignment=Qt.AlignRight)
        dev_grid.addWidget(ihm_lab, 1, 0, alignment=Qt.AlignRight)
        dev_grid.addWidget(web_lab, 2, 0, alignment=Qt.AlignRight)
        dev_grid.addWidget(features_dev, 0, 1, alignment=Qt.AlignLeft)
        dev_grid.addWidget(ihm_dev, 1, 1, alignment=Qt.AlignLeft)
        dev_grid.addWidget(web_dev, 2, 1, alignment=Qt.AlignLeft)
        box.addLayout(dev_grid)

        # Contact
        box.addSpacing(SPACING_SIZE)
        box.addWidget(lab_contact, alignment=Qt.AlignCenter)

        Separator(self.width(), box)  # ----

        # Links of doc, git and license
        box.addWidget(lab_link_doc, alignment=Qt.AlignCenter)
        box.addWidget(lab_link_git, alignment=Qt.AlignCenter)
        box.addSpacing(SPACING_SIZE)
        box.addWidget(lab_license, alignment=Qt.AlignCenter)

        Separator(self.width(), box)  # ----

        # Version
        grid_version = QGridLayout()
        grid_version.addWidget(lab_app_version, 0, 0, alignment=Qt.AlignRight)
        grid_version.addWidget(lab_bdd_version, 1, 0, alignment=Qt.AlignRight)
        grid_version.addWidget(app_version, 0, 1, alignment=Qt.AlignLeft)
        grid_version.addWidget(bdd_version, 1, 1, alignment=Qt.AlignLeft)
        box.addLayout(grid_version)
        box.addSpacing(SPACING_SIZE)

        self.setLayout(box)
Beispiel #17
0
    def __init__(self):
        QDialog.__init__(self)

        self.setWindowTitle(tr("btn_config"))
        self.setFixedSize(QSize(700, 670))

        # Retrieve current settings
        self.settings = AssetManager.getInstance().config_to_dico(
            AssetManager.getInstance().get_config_parser())
        self.__restart_needed = False
        self.__restore_required = False

        # Version
        self.lab_version = QLabel(self.settings['main']['version'])

        # Language
        self.combo_language = QComboBox()
        self.combo_language.addItems(list(self.languages.keys()))
        for lang in self.languages:  # Look for the current language to select it
            if self.languages[lang] == self.settings['main']['language']:
                self.combo_language.setCurrentText(lang)
                break

        # CSV separator
        self.csv_sep_edit = QLineEdit()
        self.csv_sep_edit.setMaxLength(2)
        self.csv_sep_edit.setFixedWidth(25)
        self.csv_sep_edit.setAlignment(Qt.AlignCenter)
        self.csv_sep_edit.setText(self.settings['main']['csv_separator'])

        # BDD path
        self.btn_bdd_path = QPushButton(self.settings['main']['bdd_path'])
        self.btn_bdd_path.clicked.connect(self.choose_bdd_path)

        # Port
        self.wepapp_port = QSpinBox()
        self.wepapp_port.setMinimum(1024)
        self.wepapp_port.setMaximum(65535)
        self.wepapp_port.setValue(int(self.settings['webapp']['port']))

        # Colors
        self.tile_color = ColorChooser(self.settings['colors']['tile'])
        self.hovered_tile_color = ColorChooser(
            self.settings['colors']['hovered_tile'])
        self.hovered_empty_tile_color = ColorChooser(
            self.settings['colors']['hovered_empty_tile'])
        self.dragged_tile_color = ColorChooser(
            self.settings['colors']['dragged_tile'])
        self.drag_selected_tile_color = ColorChooser(
            self.settings['colors']['drag_selected_tile'])
        self.selected_tile_color = ColorChooser(
            self.settings['colors']['selected_tile'])
        self.tile_text_color = ColorChooser(
            self.settings['colors']['tile_text'])
        self.room_bg_color = ColorChooser(self.settings['colors']['room_bg'])
        self.room_grid_color = ColorChooser(
            self.settings['colors']['room_grid'])
        self.main_bg_color = ColorChooser(self.settings['colors']['main_bg'])
        self.board_bg_color = ColorChooser(self.settings['colors']['board_bg'])

        self.attr_colors = ""  # Chosen colors
        self.attributes_colors_chooser = AttrColorsChooser(
            self.settings['colors']['attr_colors'].split())

        # Sizes (unmodifiable)
        self.unmodifiable = QLabel(tr("unmodifiable_data"))
        self.unmodifiable.setAlignment(Qt.AlignCenter)
        self.desk_size = QLineEdit(self.settings['size']['desk'])
        self.desk_size.setEnabled(False)
        self.desk_size.setFixedWidth(50)
        self.grid_rows = QLineEdit(self.settings['size']['default_room_rows'])
        self.grid_rows.setEnabled(False)
        self.grid_rows.setFixedWidth(50)
        self.grid_cols = QLineEdit(
            self.settings['size']['default_room_columns'])
        self.grid_cols.setEnabled(False)
        self.grid_cols.setFixedWidth(50)

        # --- Buttons ---

        # Confirm button
        self.ok_btn = QPushButton(tr("btn_save"))
        self.ok_btn.clicked.connect(self.accept)
        self.ok_btn.setFocus()

        # Cancel button
        self.cancel_btn = QPushButton(tr("btn_cancel"))
        self.cancel_btn.clicked.connect(self.reject)

        # Restore defaults button
        self.restore_btn = QPushButton(tr("btn_restore"))
        self.restore_btn.clicked.connect(self.__restore)

        self.__set_layout()
Beispiel #18
0
    def paintEvent(self, event):
        """
        Draws the desks and students' names given the self.tiles list
        """
        painter = QPainter(self)
        color = AssetManager.getInstance().config(
            'colors', 'room_grid') if self.__is_config else "white"

        pen = QPen()
        pen.setColor(QColor(color))
        pen.setWidth(2)
        painter.setPen(pen)

        # Draw the grid
        for r in range(self.nb_rows):
            painter.drawLine(
                QPoint(0, r * self.desk_h_size),
                QPoint(self.desk_w_size * self.nb_columns,
                       r * self.desk_h_size))

        for c in range(self.nb_columns):
            painter.drawLine(
                QPoint(c * self.desk_w_size, 0),
                QPoint(c * self.desk_w_size, self.desk_h_size * self.nb_rows))

        # Update painter color and font size for tiles
        pen.setColor(
            QColor(AssetManager.getInstance().config('colors', 'tile_text')))
        painter.setPen(pen)

        font = QFont()
        font.setPixelSize(
            int(AssetManager.getInstance().config('size', 'font_size')))
        painter.setFont(font)

        # Current tile selected by the mouse
        tile_selected_pos = self.__convert_point(
            self.__mouse_pos[0],
            self.__mouse_pos[1]) if self.__mouse_pos else None
        tile_selected = None
        self.hovered = False

        # Drawing of all the tiles
        for t in list(self.__tiles.values()):
            y, x = self.__relative_mouse_position(t.real_position())
            if self.__relative_grid_position(
                    t.grid_position()
            ) == self.__click_pos and self.__is_config:  # If the tile is selected
                tile_selected = t
                color = QColor(AssetManager.getInstance().config(
                    'colors', 'drag_selected_tile'))
            elif self.__relative_grid_position(
                    t.grid_position()
            ) == tile_selected_pos and self.__is_config:  # If the mouse is hover
                self.hovered = True
                color = QColor(AssetManager.getInstance().config(
                    'colors', 'hovered_tile'))
            elif t.is_selected():
                color = QColor(AssetManager.getInstance().config(
                    'colors', 'selected_tile'))
            else:  # Regular tile
                color = QColor(AssetManager.getInstance().config(
                    'colors', 'tile'))
            rect = self.__get_rect_at(y, x)
            painter.fillRect(rect, color)
            painter.drawText(rect, Qt.AlignCenter | Qt.TextWordWrap,
                             f"{t.lastname()}\n{t.firstname()}")

        # Dragged tile
        if self.__click_pos != self.__relative_grid_position(tile_selected_pos) \
                and tile_selected and self.__mouse_pos and self.__is_config:
            # If the mouse is no longer hover the clicked tile we draw the dragged tile
            self.__draw_dragged_tile(painter, tile_selected,
                                     self.__mouse_pos[0], self.__mouse_pos[1])
Beispiel #19
0
    def __init__(self):
        """
        Application main controller.
        """
        QObject.__init__(self)

        # Create the Views
        self.gui = ViewMainFrame(self.sig_quit, self.sig_config_mode_changed,
                                 self.sig_export_csv)
        self.v_canvas = self.gui.central_widget.classroom_tab.v_canvas

        # BDD connection
        bdd_path, bdd_exists = AssetManager.getInstance().bdd_path()
        if not bdd_exists:
            bp = QFileDialog.getExistingDirectory(self.gui, tr("select_db"))
            if bp == "" or not path.isdir(bp):
                self.mod_bdd = None
                return
            bdd_path = path.normpath(bp + "/sdc_db")
            if path.isfile(bdd_path):
                self.__bdd = sqlite3.connect(bdd_path)
            else:
                # we initialize a new BDD
                if not VConfirmDialog(self.gui, "confirm_db_creation").exec_():
                    self.mod_bdd = None
                    return
                self.__bdd = self.initialize_bdd(bdd_path)
            config = AssetManager.getInstance().get_config_parser()
            config.set('main', 'bdd_path', bdd_path)
            AssetManager.getInstance().save_config(config)
        else:
            self.__bdd = sqlite3.connect(bdd_path)
        self.mod_bdd = ModBdd(self.__bdd)
        self.gui.set_bdd_version(self.mod_bdd.get_version())

        # Create secondary controllers
        self.attr_ctrl = AttrController(self, self.__bdd)
        self.course_ctrl = CourseController(self, self.__bdd)
        self.group_ctrl = GroupController(self, self.__bdd)

        # Plugs the signals into the views
        self.v_canvas.sig_select_tile = self.sig_select_tile
        self.gui.central_widget.sig_shuffle = self.sig_shuffle
        self.gui.sig_quit = self.sig_quit
        self.v_canvas.sig_canvas_click = self.sig_canvas_click
        self.v_canvas.sig_desk_selected = self.sig_desk_selected
        self.v_canvas.sig_canvas_drag = self.sig_canvas_drag
        self.v_canvas.sig_tile_info = self.sig_canvas_right_click
        self.gui.sidewidget.courses(
        ).sig_course_changed = self.sig_course_changed
        self.gui.sidewidget.courses(
        ).courses_toolbar.add_widget.sig_new_element = self.sig_create_course
        self.gui.sidewidget.courses(
        ).sig_topic_changed = self.sig_topic_changed
        self.gui.sidewidget.students(
        ).students_toolbar.sig_combo_changed = self.sig_student_group_changed
        ViewMenuButton.sig_action = self.sig_action_triggered
        self.gui.maintoolbar.sig_TBbutton = self.sig_TBbutton
        self.gui.sidewidget.students(
        ).students_toolbar.create_field.sig_create = self.sig_create_grp_std
        self.gui.sidewidget.attributes(
        ).attributes_toolbar.add_widget.sig_new_element = self.sig_create_attribute
        self.gui.sidewidget.attributes(
        ).attributes_toolbar.add_widget.sig_delete = self.sig_delete_attributes
        self.gui.sidewidget.courses(
        ).courses_toolbar.sig_delete = self.sig_delete_course
        self.gui.sidewidget.attributes(
        ).sig_selection_changed = self.sig_attr_selection_changed
        self.gui.central_widget.attributes_tab.sig_cell_clicked = self.sig_attribute_cell_selected

        # Signals connection
        self.sig_select_tile.connect(
            self.attr_ctrl.on_attribute_selection_changed)
        self.sig_quit.connect(self.do_quit)
        self.sig_canvas_click.connect(self.course_ctrl.add_desk)
        self.sig_desk_selected.connect(
            self.course_ctrl.on_desk_selection_changed_on_app)
        self.sig_canvas_drag.connect(self.course_ctrl.move_desk)
        self.sig_canvas_right_click.connect(
            self.attr_ctrl.show_student_attributes)
        self.sig_shuffle.connect(self.course_ctrl.desk_shuffle)
        self.sig_course_changed.connect(self.course_ctrl.on_course_changed)
        self.sig_create_course.connect(self.course_ctrl.on_create_course)
        self.sig_topic_changed.connect(self.course_ctrl.on_topic_changed)
        self.sig_student_group_changed.connect(
            self.group_ctrl.on_student_group_changed)
        self.sig_action_triggered.connect(self.action_triggered)
        self.sig_TBbutton.connect(self.action_triggered)
        self.sig_create_grp_std.connect(self.group_ctrl.on_create_grp_std)
        self.sig_create_attribute.connect(self.attr_ctrl.on_create_attr)
        self.sig_delete_attributes.connect(self.attr_ctrl.on_delete_attributes)
        self.sig_delete_course.connect(self.course_ctrl.on_delete_course)
        self.sig_attr_selection_changed.connect(
            self.attr_ctrl.on_attribute_selection_changed)
        self.sig_attribute_cell_selected.connect(
            self.attr_ctrl.on_attribute_cell_selected)
        self.sig_flask_desk_selection_changed.connect(
            self.course_ctrl.on_desk_selection_changed_on_web)
        self.sig_close_qr.connect(self.close_qr)
        self.sig_config_mode_changed.connect(self.on_config_changed)
        self.sig_export_csv.connect(self.attr_ctrl.export_csv)

        self.actions_table = {  # Action buttons
            "import_csv": self.group_ctrl.import_pronote,
            "auto_place": self.group_ctrl.auto_place,
            "sort_asc": lambda: self.group_ctrl.sort_alpha(False),
            "sort_desc": lambda: self.group_ctrl.sort_alpha(True),
            "sort_desks_Z": lambda: self.course_ctrl.sort_desks(sort_type="Z"),
            "sort_desks_2": lambda: self.course_ctrl.sort_desks(sort_type="2"),
            "sort_desks_U": lambda: self.course_ctrl.sort_desks(sort_type="U"),
            "killstudent": self.group_ctrl.killstudent,
            "delete_group": self.group_ctrl.on_delete_group,

            # Toolbar buttons
            "filter_select": self.attr_ctrl.change_filter_selection,
            "select": self.course_ctrl.auto_select_desks,
            "choice": self.course_ctrl.student_random_pick,
            "choice_attr": self.course_ctrl.student_attr_pick,
            "delete": self.course_ctrl.delete,
            "lot_change": self.attr_ctrl.lot_change,
            "print": self.course_ctrl.export_pdf,
            "show_qr": self.show_qr
        }

        # properties
        self.id_course = 0
        self.id_group = 0
        self.selection_mode = self.SEL_ALL
        self.filter_selection = False
        self.std_dialog_info: VStdAttributesDialog = None
        self.qr_dialog: VQRCode = None

        # initialize the views
        self.course_ctrl.show_all_courses()
        self.group_ctrl.show_all_groups()
        self.attr_ctrl.show_all_attributes()
        self.gui.on_config_mode(False)
        self.gui.update()

        # initialize connection to flask server
        self.flask_client = socketio.Client()
        self.flask_client.connect(
            'http://localhost:' +
            AssetManager.getInstance().config('webapp', 'port'))
        self.flask_server = None

        # search for new version
        latest_version = AssetManager.getInstance().get_latest_version()
        if latest_version > AssetManager.getInstance().config(
                'main', 'version'):
            self.gui.status_bar.showMessage(tr("new_version") + latest_version)
    def auto_place(self):
        """Autoplacement of students on the free tiles"""
        max_row = int(AssetManager.getInstance().config(
            "size", "default_room_rows"))
        max_col = int(AssetManager.getInstance().config(
            "size", "default_room_columns"))
        group_name = self.mod_bdd.get_group_name_by_id(self.main_ctrl.id_group)
        list_idstd = self.gui.sidewidget.students().selected_students()
        if not list_idstd:
            list_students = self.mod_bdd.get_students_in_group(group_name)
        else:
            list_students = [
                self.mod_bdd.get_student_by_id(i) for i in list_idstd
            ]

        students_ids = {s.id for s in list_students}

        to_be_placed = len(list_students)
        list_available_desks = []
        list_to_remove = []
        for row in range(max_row):
            for col in range(max_col):
                id_desk = self.mod_bdd.get_desk_id_in_course_by_coords(
                    self.main_ctrl.id_course, row, col)
                if id_desk != 0:
                    student = self.mod_bdd.get_student_by_desk_id(id_desk)
                    if student is None:
                        # We have a free spot
                        list_available_desks.append((id_desk, row, col))
                    else:
                        list_to_remove.append(student.id)
        # Adjust the number of students to place
        for s in list_to_remove:
            if s in students_ids:
                to_be_placed -= 1

        info = None
        if to_be_placed > len(list_available_desks):
            info = str(to_be_placed -
                       len(list_available_desks)) + tr("seats_are_missing")
            self.gui.status_bar.showMessage(info)
        else:
            self.gui.status_bar.showMessage(tr("seats_are_OK"), 3000)

        index_std = 0
        for dsk in list_available_desks:
            while index_std < len(list_students) and list_students[
                    index_std].id in list_to_remove:
                index_std += 1
            if index_std >= len(list_students):
                break
            student = list_students[index_std]
            # update the model
            self.mod_bdd.set_student_in_desk_by_id(student.id, dsk[0])
            # update the view
            self.v_canvas.set_student(dsk[0], student.firstname,
                                      student.lastname)
            self.v_canvas.repaint()
            index_std += 1

        self.__bdd.commit()

        if info is not None:
            VInfoDialog(self.gui, info).exec_()