def __init__(self, mainwindow):
        self.mainwindow = mainwindow
        self.ui = mainwindow.ui
        self.config = mainwindow.config_manager

        self.ui.templateTextEdit.textChanged.connect(
            self.template_text_changed)
        self.ui.templateViewLabel.mousePressed.connect(self.on_mouse_press)
        self.ui.templateViewLabel.mouseReleased.connect(self.on_mouse_release)
        self.ui.templateViewLabel.mouseMove.connect(self.on_mouse_move)
        self.ui.templateViewLabel.wheelScrolled.connect(self.on_wheel_scroll)
        self.ui.templateViewLabel.keyPressed.connect(self.on_key_press)

        self.mode = Mode.CREATE
        self.edit_mode = Mode.MOVE
        self.tmp_rect = None
        self._default_exam = None
        self._selected_template = None
        self._scale = self.config.get_property(TEMPLATE_IMAGE_ZOOM, 1.0)
        self.scale_rect_margin = 30

        self.mouse_pressed = False
        self.mouse_pressed_pos = None
        self.mouse_pos_rect_offset = None
        self.original_rect_pos = None
        self.draw_time = 0

        self.drawing = Drawing(None, self._scale)
    def __init__(self, mainwindow):
        self.mainwindow = mainwindow
        self.ui = mainwindow.ui
        self.config = self.mainwindow.config_manager

        self.template_view_controller = mainwindow.template_view_controller
        self.ui.examViewLabel.wheelScrolled.connect(self.on_wheel_scroll)

        exams = self.config.get_property(EXAMS_LOADED, [])
        for ex in exams:
            if not os.path.exists(ex):
                exams.remove(ex)

        self.exams = exams
        self._selected_exam = None
        self._scale = self.config.get_property(EXAM_IMAGE_ZOOM, 1.0)
        self.drawing = Drawing(None, self._scale)
        self.ui.examListView.clicked.connect(self.on_exam_text_selection)
 def default_exam(self, exam):
     self._default_exam = exam
     self.drawing = Drawing(exam, self.scale)
     self._draw_template()
 def scale(self, value):
     self._scale = value
     self.config.set_property(TEMPLATE_IMAGE_ZOOM, self._scale)
     self.drawing = Drawing(self.default_exam, self._scale)
     self._draw_template()
class TemplateViewController:
    def __init__(self, mainwindow):
        self.mainwindow = mainwindow
        self.ui = mainwindow.ui
        self.config = mainwindow.config_manager

        self.ui.templateTextEdit.textChanged.connect(
            self.template_text_changed)
        self.ui.templateViewLabel.mousePressed.connect(self.on_mouse_press)
        self.ui.templateViewLabel.mouseReleased.connect(self.on_mouse_release)
        self.ui.templateViewLabel.mouseMove.connect(self.on_mouse_move)
        self.ui.templateViewLabel.wheelScrolled.connect(self.on_wheel_scroll)
        self.ui.templateViewLabel.keyPressed.connect(self.on_key_press)

        self.mode = Mode.CREATE
        self.edit_mode = Mode.MOVE
        self.tmp_rect = None
        self._default_exam = None
        self._selected_template = None
        self._scale = self.config.get_property(TEMPLATE_IMAGE_ZOOM, 1.0)
        self.scale_rect_margin = 30

        self.mouse_pressed = False
        self.mouse_pressed_pos = None
        self.mouse_pos_rect_offset = None
        self.original_rect_pos = None
        self.draw_time = 0

        self.drawing = Drawing(None, self._scale)

    @property
    def scale(self):
        return self._scale

    @scale.setter
    def scale(self, value):
        self._scale = value
        self.config.set_property(TEMPLATE_IMAGE_ZOOM, self._scale)
        self.drawing = Drawing(self.default_exam, self._scale)
        self._draw_template()

    @property
    def selected_template(self):
        return self._selected_template

    @selected_template.setter
    def selected_template(self, filename):
        size = None
        if self._default_exam is not None:
            size = (self._default_exam.size[0], self._default_exam.size[1])
        self._selected_template = TemplateFile(filename, size)
        self._selected_template.statusChanged.connect(self._draw_template)
        self._selected_template.statusChanged.connect(self._change_title)
        self._selected_template.template.templateChanged.connect(
            self._draw_template)
        self._selected_template.template.templateChanged.connect(
            self._change_text)
        self._change_text()
        self._draw_template()

    @property
    def default_exam(self):
        return self._default_exam

    @default_exam.setter
    def default_exam(self, exam):
        self._default_exam = exam
        self.drawing = Drawing(exam, self.scale)
        self._draw_template()

    def _draw_template(self):
        if self._default_exam is not None:
            if self._selected_template is not None:
                image = self.drawing.draw_template(
                    self._selected_template.template, self.tmp_rect,
                    self.original_rect_pos)
            else:
                image = self.drawing.resize(self._default_exam, self._scale)
            self.ui.templateViewLabel.setPixmap(pil2pixmap(image))

    def _change_text(self):
        content = self._selected_template.template.to_json()
        self._selected_template.changed = True
        self.ui.templateTextEdit.setText(content)

    def _change_title(self):
        self.mainwindow.setWindowTitle(
            self.mainwindow.WINDOW_TITLE + " - " +
            self.selected_template.file.name if self.selected_template.
            file is not None else "")

    def template_text_changed(self):
        data = self.ui.templateTextEdit.toPlainText()
        try:
            if self.selected_template.template.to_json() != data:
                template = Template.from_json(data)
                self._selected_template.template = template
                self._selected_template.template.templateChanged.connect(
                    self._draw_template)
                self._selected_template.template.templateChanged.connect(
                    self._change_text)
            color = QtGui.QColor("white")
        except (ValueError, Exception):
            color = QtGui.QColor("#ff9999")
        palette = self.ui.templateTextEdit.palette()
        palette.setColor(QtGui.QPalette.Base, color)
        self.ui.templateTextEdit.setPalette(palette)

    def on_mouse_press(self, event):
        if self._selected_template is not None:
            x = int(event.pos().x() / self._scale)
            y = int(event.pos().y() / self._scale)
            self.mouse_pressed = True
            self.mouse_pressed_pos = (x, y)

            if self.mode == Mode.EDIT:
                clicked_field = self._selected_template.template.get_field_at(
                    x, y)
                if clicked_field and self.tmp_rect is None:
                    clicked_rect_tuple = (clicked_field.name,
                                          clicked_field.rect)
                    self.tmp_rect = clicked_rect_tuple if clicked_rect_tuple is None else clicked_rect_tuple[
                        1]
                    self.mouse_pos_rect_offset = (x - self.tmp_rect[0],
                                                  y - self.tmp_rect[1])
                    self.original_rect_pos = (self.tmp_rect[0],
                                              self.tmp_rect[1])
                    if self.distance_to_scale(self.mouse_pressed_pos,
                                              self.tmp_rect):
                        self.edit_mode = Mode.SCALE
                    else:
                        self.edit_mode = Mode.MOVE
                elif self.tmp_rect is not None:
                    self.commit_rect()
                self._draw_template()
            else:
                self.tmp_rect = (x, y, 0, 0)
                self._draw_template()

    def distance_to_scale(self, point, rect):
        x, y, w, h = rect
        xp, yp = point
        dx = x + w - xp
        dy = y + h - yp
        return 0 < dx < self.scale_rect_margin and 0 < dy < self.scale_rect_margin

    def commit_rect(self):
        if self.tmp_rect is not None:
            x, y, _, __ = self.tmp_rect
            o_x, o_y = self.original_rect_pos
            field = self._selected_template.template.get_field_at(
                o_x + 1, o_y + 1)
            if self.edit_mode == Mode.MOVE and self.original_rect_pos != (x,
                                                                          y):
                self._selected_template.template.move_field_to(
                    field.name, self.tmp_rect[0], self.tmp_rect[1])
            else:
                self._selected_template.template.scale_field(
                    field.name, self.tmp_rect[2], self.tmp_rect[3])
            self.original_rect_pos = None
            self.tmp_rect = None

    def on_mouse_move(self, event):
        if self._selected_template is not None:
            if self.mouse_pressed and self.tmp_rect is not None:
                if self.mode == Mode.CREATE:
                    self._common_move(event.pos())
                elif self.edit_mode == Mode.MOVE:
                    self._move_rect(event.pos())
                else:
                    self._common_move(event.pos())

                if time.time() - self.draw_time > 0.01:
                    self._draw_template()
                    self.draw_time = time.time()

    def _move_rect(self, pos):
        m_x, m_y = int(pos.x() / self._scale), int(pos.y() / self._scale)
        x, y, w, h = self.tmp_rect
        new_x = self.original_rect_pos[0] + m_x - self.mouse_pressed_pos[0]
        new_y = self.original_rect_pos[1] + m_y - self.mouse_pressed_pos[1]
        self.tmp_rect = (new_x, new_y, w, h)

    def _common_move(self, pos):
        m_x, m_y = int(pos.x() / self._scale), int(pos.y() / self._scale)
        x, y, w, h = self.tmp_rect
        self.tmp_rect = (x, y, m_x - x, m_y - y)

    def redraw(self):
        self._draw_template()

    def on_mouse_release(self, event):
        self.mouse_pressed = False
        if self._selected_template is not None and self.mode == Mode.CREATE:
            x, y, w, h = self.tmp_rect
            if w == 0 and h == 0:
                self.tmp_rect = None
                self._draw_template()

    def on_wheel_scroll(self, event):
        if self.default_exam is not None:
            delta = event.angleDelta().y()
            if delta > 0 and self.scale <= 2.0:
                self.scale += 0.1
            if delta < 0 and self.scale >= 0.2:
                self.scale -= 0.1

    def on_key_press(self, event):
        if event.key() in (QtCore.Qt.Key_Delete, QtCore.Qt.Key_Backspace):
            if self.mode == Mode.EDIT and self.tmp_rect:
                self.tmp_rect = self._selected_template.template.remove_field_at(
                    self.tmp_rect[0] + 1, self.tmp_rect[1] + 1)
                self.tmp_rect = None
        if event.key() == QtCore.Qt.Key_E:
            self.change_mode(True)
        if event.key() == QtCore.Qt.Key_C:
            self.change_mode(False)
            self.original_rect_pos = None
            self.tmp_rect = None
        self._draw_template()

    def change_mode(self, edit):
        if edit:
            self.mode = Mode.EDIT
            self.tmp_rect = None
            self._draw_template()
            self.ui.actionEditMode.setChecked(True)
        else:
            self.mode = Mode.CREATE
            self.commit_rect()
            self._draw_template()
            self.ui.actionEditMode.setChecked(False)
 def scale(self, value):
     self._scale = value
     self.config.set_property(EXAM_IMAGE_ZOOM, self._scale)
     self.drawing = Drawing(self._selected_exam, self._scale)
     self._draw_exam()
class ExamController:
    def __init__(self, mainwindow):
        self.mainwindow = mainwindow
        self.ui = mainwindow.ui
        self.config = self.mainwindow.config_manager

        self.template_view_controller = mainwindow.template_view_controller
        self.ui.examViewLabel.wheelScrolled.connect(self.on_wheel_scroll)

        exams = self.config.get_property(EXAMS_LOADED, [])
        for ex in exams:
            if not os.path.exists(ex):
                exams.remove(ex)

        self.exams = exams
        self._selected_exam = None
        self._scale = self.config.get_property(EXAM_IMAGE_ZOOM, 1.0)
        self.drawing = Drawing(None, self._scale)
        self.ui.examListView.clicked.connect(self.on_exam_text_selection)

    @property
    def scale(self):
        return self._scale

    @scale.setter
    def scale(self, value):
        self._scale = value
        self.config.set_property(EXAM_IMAGE_ZOOM, self._scale)
        self.drawing = Drawing(self._selected_exam, self._scale)
        self._draw_exam()

    def on_exam_text_selection(self, index):
        self.selected_exam = self._exams[index.row()]

    @property
    def selected_exam(self):
        return self._selected_exam

    @selected_exam.setter
    def selected_exam(self, value):
        if os.path.exists(value):
            image = Image.open(value)
            if self._selected_exam != image:
                self._selected_exam = image
                self.template_view_controller.default_exam = image
                self._draw_exam()
        else:
            QMessageBox.warning(self.mainwindow, "Warning", "No exam file under this path")

    def remove_selected_exams(self):
        indices = self.ui.examListView.selectionModel().selectedRows()
        indices = sorted(indices, key=lambda idx: idx.row(), reverse=True)
        for i in indices:
            del self._exams[i.row()]
        self.exams = self._exams
        self.config.set_property(EXAMS_LOADED, self._exams)

    @property
    def exams(self):
        return self._exams

    @exams.setter
    def exams(self, value):
        self._exams = value
        model = QStringListModel(map(lambda x: str(x), value))
        self.ui.examListView.setModel(model)
        if self._exams:
            image = Image.open(self._exams[0])
            self.template_view_controller.default_exam = image
        self.config.set_property(EXAMS_LOADED, value)

    def _draw_exam(self):
        image = self.drawing.resize(self._selected_exam, self._scale)
        self.ui.examViewLabel.setPixmap(pil2pixmap(image))

    def on_wheel_scroll(self, event):
        if self.selected_exam is not None:
            delta = event.angleDelta().y()
            if delta > 0 and self.scale <= 2.0:
                self.scale += 0.1
            if delta < 0 and self.scale >= 0.2:
                self.scale -= 0.1