Ejemplo n.º 1
0
class Baseline:
    def __init__(self):
        pygame.mixer.init(22050, -16, 2, 64)
        pygame.init()
        self.keyboard = Keyboard(VISABLE_FEEDBACK=Keyboard.VISABLE_NO,
                                 WORD_CORRECTION=Keyboard.CORRECT_WORD)
        self.frame_id = 0
        self.save_folder = save_folder = 'data-baseline/' + sys.argv[1] + '/'
        if not os.path.exists(save_folder):
            os.makedirs(save_folder)
        else:
            print('\033[1;31;40m[Warning]\033[0m Folder exists')

    def save_data(self):
        data = self.keyboard.inputted_data
        for i in range(len(data)):
            data[i] = data[i][:-2] + [self.frame_id]
            self.frame_id += 1
        save_file = open(
            self.save_folder + str(self.keyboard.curr_task_id) + '.pickle',
            'wb')
        pickle.dump([
            self.keyboard.task, self.keyboard.inputted_text, data,
            self.keyboard.inputted_space_cnt
        ], save_file)

    def run(self):
        self.keyboard.draw()

        is_running = True
        while is_running:
            keys = []
            is_mouse_down = False
            mouse_pos = None
            for event in pygame.event.get():
                if event.type == pygame.KEYUP:
                    keys.append(event.key)
                if event.type == pygame.MOUSEBUTTONDOWN:
                    is_mouse_down = True
                    mouse_pos = pygame.mouse.get_pos()

            inputted = ''
            inputted_pos = [0, 0]
            if is_mouse_down:
                pos_list = self.keyboard.decoder.positions.copy()
                chr_list = [chr(i + ord('a')) for i in range(26)]
                pos_list.extend([[7, 2], [8, 2], [9, 1], [9, 2]])
                chr_list.extend([' ', ' ', '-', '-'])
                GRID = self.keyboard.GRID
                [x, y] = mouse_pos
                for (pos, ch) in zip(pos_list, chr_list):
                    [x0, x1
                     ] = [int((pos[0] + 0) * GRID),
                          int((pos[0] + 1) * GRID)]
                    [y0, y1
                     ] = [int((pos[1] + 1) * GRID),
                          int((pos[1] + 2) * GRID)]
                    if x0 <= x and x < x1 and y0 <= y and y < y1:
                        inputted = ch
                        inputted_pos = pos
                        break

                if inputted_pos[0] <= 4:
                    side = 'L'
                    finger = max(1, 4 - inputted_pos[0])
                else:
                    side = 'R'
                    finger = max(1, inputted_pos[0] - 5)
                corr_endpoint_x = inputted_pos[0] - 3
                highlight_row = inputted_pos[1] + 1

                # [side, which_finger, highlight_row, highlight_col, timestamp, palm_line, endpoint_x, endpoint_y, corr_endpoint_x, corr_endpoint_y, image_L, image_R]
                inputted_data = [
                    side, finger, highlight_row, 0,
                    time.clock(), 0, 0, 0, corr_endpoint_x, 0, 0, 0
                ]
                if inputted.isalpha():
                    self.keyboard.enter_a_letter(inputted_data, inputted)
                elif inputted == ' ':
                    self.keyboard.enter_a_space(inputted_data)
                elif inputted == '-':
                    self.keyboard.delete_a_letter()

            if pygame.K_q in keys:
                is_running = False

            if pygame.K_n in keys:
                if len(self.keyboard.inputted_text) == len(self.keyboard.task):
                    self.keyboard.enter_a_space(
                        inputted_data)  # Correct and show the last word
                    self.keyboard.draw()
                    start_time = float(self.keyboard.inputted_data[0][4])
                    end_time = float(self.keyboard.inputted_data[-1][4])
                    wpm = ((len(self.keyboard.inputted_text) - 1) / 5.0) / (
                        (end_time - start_time) / 60.0)
                    print('WPM = ', wpm)
                    self.save_data()
                    is_running = self.keyboard.next_phrase()

            if pygame.K_r in keys:  # Redo phrase
                self.keyboard.redo_phrase()

            self.keyboard.draw()
Ejemplo n.º 2
0
class View(Gtk.DrawingArea):
    def __init__(self):
        Gtk.DrawingArea.__init__(self)

        self.caret = Gdk.Rectangle()

        self.connect("draw", self.on_draw)
        self.connect("key-press-event", self.on_key_press)
        self.connect("key-release-event", self.on_key_release)
        self.connect("focus-in-event", self.on_focus_in)
        self.connect("focus-out-event", self.on_focus_out)

        self.im_context = Gtk.IMMulticontext()
        self.im_context.set_client_window(self.get_window())
        self.im_context.connect("commit", self.on_commit)
        self.im_context.connect("delete-surrounding",
                                self.on_delete_surrounding)
        self.im_context.connect("retrieve-surrounding",
                                self.on_retrieve_surrounding)
        self.im_context.connect("preedit-changed", self.on_preedit_changed)
        self.im_context.connect("preedit-end", self.on_preedit_end)
        self.im_context.connect("preedit-start", self.on_preedit_start)

        self.set_can_focus(True)

        self.roomazi = Roomazi()
        self.keyboard = Keyboard(self.roomazi)
        self.engine = Engine(self.roomazi)

    def _clear(self, wid, ctx):
        width = wid.get_allocated_width()
        height = wid.get_allocated_height()
        ctx.set_source_rgb(1, 1, 1)
        ctx.rectangle(0, 0, width, height)
        ctx.fill()
        ctx.set_source_rgb(0, 0, 0)

    def _draw_caret(self, ctx, layout, current, x, y):
        ctx.save()
        st, we = layout.get_cursor_pos(len(current.encode()))
        self.caret.x = x + st.x / Pango.SCALE - 1
        self.caret.y = y + st.y / Pango.SCALE
        self.caret.width = st.width / Pango.SCALE + 2
        self.caret.height = st.height / Pango.SCALE
        if (1, 13) <= cairo.version_info:
            ctx.set_operator(cairo.Operator.DIFFERENCE)
            ctx.set_source_rgb(1, 1, 1)
        ctx.rectangle(self.caret.x, self.caret.y, self.caret.width,
                      self.caret.height)
        ctx.fill()
        ctx.restore()
        x, y = self.translate_coordinates(self.get_toplevel(), self.caret.x,
                                          self.caret.y)
        self.caret.x = x
        self.caret.y = y
        self.im_context.set_cursor_location(self.caret)

    def _draw_hints(self, wid, ctx: cairo.Context, hint):
        hint = self.engine.markup(hint)

        ctx.set_source_rgb(0x99 / 255, 0x99 / 255, 0x99 / 255)
        ctx.set_line_width(1)
        ctx.move_to(MARGIN_LEFT - 10, 5)
        ctx.line_to(MARGIN_LEFT - 10, WINDOW_HEIGHT - 5)
        ctx.stroke()

        ctx.set_source_rgb(0, 0, 0)
        hurigana = HuriganaLayout(ctx)
        hurigana.set_ruby_size(HINT_SIZE / 2.5)
        desc = Pango.font_description_from_string(HINT_FONT)
        hurigana.set_font_description(desc)
        hurigana.set_width(MARGIN_LEFT - 30)
        hurigana.set_spacing(HINT_SPACING)
        hurigana.set_markup(hint)
        hurigana.draw(10, MARGIN_TOP)

    def _draw_menu(self, wid, ctx):
        # Draw text
        hurigana = HuriganaLayout(ctx)
        hurigana.set_ruby_size(FONT_SIZE / 2.5)
        desc = Pango.font_description_from_string(DEFAULT_FONT)
        hurigana.set_font_description(desc)
        hurigana.set_width(WIDTH)
        hurigana.set_spacing(LINE_SPACING)
        markup = self.engine.markup(self.engine.get_text())
        hurigana.set_markup(markup)
        hurigana.draw(MARGIN_LEFT, MARGIN_TOP)

        self._draw_hints(wid, ctx, self.engine.get_hint())

    if Pango.version_check(1, 44, 0) is None:

        def _draw_typed(self, ctx, layout, hurigana):
            typed = get_prefix(self.engine.get_plain(),
                               self.engine.get_typed())
            correct_length = len(typed)
            typed = hurigana.adjust_typed(typed)
            attr_list = Pango.AttrList().new()

            formatted = typed
            so = 0
            eo = len(formatted.encode())
            attr = Pango.attr_foreground_new(0x0000, 0x6600, 0xcc00)
            attr.start_index = so
            attr.end_index = eo
            attr_list.insert(attr)

            if correct_length < len(self.engine.get_typed()):
                typed += self.engine.get_typed()[correct_length:]
                formatted += self.engine.get_typed()[correct_length:]
                so = eo
                eo = so + len(
                    self.engine.get_typed()[correct_length:].encode())
                attr = Pango.attr_background_new(0xff00, 0xcc00, 0xff00)
                attr.start_index = so
                attr.end_index = eo
                attr_list.insert(attr)
                attr = Pango.attr_foreground_new(0xff00, 0x0000, 0x0000)
                attr.start_index = so
                attr.end_index = eo
                attr_list.insert(attr)

            preedit = self.engine.get_preedit()
            if preedit[0] and 0 < preedit[2]:
                typed += preedit[0][:preedit[2]]
                formatted += preedit[0][:preedit[2]]
                so = eo
                eo = so + len(preedit[0][:preedit[2]].encode())
                attr = Pango.attr_foreground_new(0x0000, 0x6600, 0xff00)
                attr.start_index = so
                attr.end_index = eo
                attr_list.insert(attr)

                # Note with Pango 1.48, the following splice() does not work
                # as expected. It works with Pango 1.44, though. So we will
                # manually marge preedit attributes into attr_list:
                #   attr_list.splice(preedit[1], so, 0)
                attributes = preedit[1].get_attributes()
                for i in attributes:
                    i.start_index += so
                    i.end_index += so
                    attr_list.change(i)

            layout.set_text(formatted, -1)
            layout.set_attributes(attr_list)
            PangoCairo.update_layout(ctx, layout)
            PangoCairo.show_layout(ctx, layout)
            return layout, typed

    else:

        def _draw_typed(self, ctx, layout, hurigana):
            attr_list_preedit = Pango.AttrList().new()
            typed = get_prefix(self.engine.get_plain(),
                               self.engine.get_typed())
            correct_length = len(typed)
            typed = hurigana.adjust_typed(typed)
            formatted = '<span foreground="#0066CC">' + typed + '</span>'
            if correct_length < len(self.engine.get_typed()):
                formatted += '<span foreground="#FF0000" background="#FFCCFF">' + \
                         self.engine.get_typed()[correct_length:] + \
                         '</span>'
                typed += self.engine.get_typed()[correct_length:]
            preedit = self.engine.get_preedit()
            if preedit[0] and 0 < preedit[2]:
                attr_list_preedit.splice(preedit[1], len(typed.encode()),
                                         len(preedit[0][:preedit[2]].encode()))
                formatted += '<span foreground="#0066FF">' + preedit[
                    0][:preedit[2]] + '</span>'
                typed += preedit[0][:preedit[2]]
            layout.set_markup(formatted, -1)
            if preedit[0] and 0 < preedit[2]:
                attr_list = layout.get_attributes()
                attr_list.splice(attr_list_preedit, 0, 0)
                layout.set_attributes(attr_list)
            PangoCairo.update_layout(ctx, layout)
            PangoCairo.show_layout(ctx, layout)
            return layout, typed

    def _draw_practice(self, wid, ctx):
        ctx.select_font_face("Noto Sans Mono CJK JP", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_NORMAL)
        ctx.set_font_size(FONT_SIZE)

        x = MARGIN_LEFT
        y = MARGIN_TOP
        desc = Pango.font_description_from_string(DEFAULT_FONT)

        # Draw text for practicing.
        hurigana = HuriganaLayout(ctx)
        hurigana.set_font_description(desc)
        hurigana.set_width(WIDTH)
        hurigana.set_spacing(PRACTICE_LINE_SPACING)
        hurigana.set_text(self.engine.get_text())
        hurigana.draw(x, y)

        # Draw what has been typed.
        y += LINE_HEIGHT
        ctx.move_to(x, y)
        layout = PangoCairo.create_layout(ctx)
        layout.set_font_description(desc)
        layout.set_width(WIDTH * Pango.SCALE)
        layout.set_spacing(PRACTICE_LINE_SPACING * Pango.SCALE)
        (layout, current) = self._draw_typed(ctx, layout, hurigana)

        # Draw caret
        ctx.move_to(x, y)
        layout.set_text(current, -1)
        PangoCairo.update_layout(ctx, layout)
        self._draw_caret(ctx, layout, current, x, y)

        # Draw keyboard:
        if self.engine.get_show_keyboard():
            text = self.engine.get_text()
            current = get_prefix(text, self.engine.get_typed())
            if len(current) <= len(text):
                current = text[len(current):]
            pair = self.keyboard.draw(ctx, x + MARGIN_RIGHT / 2,
                                      WINDOW_HEIGHT - 256, current)
            if pair[0]:
                ctx.set_source_rgb(0, 0, 0)
                t = pair[0]
                if pair[0] != pair[1]:
                    t += ' [' + pair[1] + ']'
                self._show_centered_text(
                    ctx, t, PRACTICE_CENTER,
                    WINDOW_HEIGHT - MARGIN_BOTTOM - STOPWATCH_HEIGHT)

        # Show stopwatch
        ctx.set_source_rgb(0, 0, 0)
        elapsed = self.engine.get_duration()
        ctx.move_to(WINDOW_WIDTH - MARGIN_RIGHT - STOPWATCH_WIDTH,
                    WINDOW_HEIGHT - MARGIN_BOTTOM - STOPWATCH_HEIGHT)
        ctx.show_text("[{:4d}] {:02d}:{:04.1f}".format(
            self.engine.get_touch_count(), int(elapsed / 60), elapsed % 60))

        hint = self.engine.get_hint()
        if not hint:
            hint = "<kbd>Esc</kbd> メニューにもどる"
        self._draw_hints(wid, ctx, hint)

    def _draw_score(self, wid, ctx):
        ctx.select_font_face("Noto Sans Mono CJK JP", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_NORMAL)
        x = MARGIN_LEFT
        y = MARGIN_TOP + 2 * FONT_SIZE

        ctx.set_font_size(2 * FONT_SIZE)
        ctx.set_source_rgb(0xff / 255, 0xcc / 255, 0x33 / 255)
        ctx.move_to(x, y)
        score = self.engine.get_score()
        extra_score = 0
        if 6 < score:
            extra_score = score - 6
            score = 6
        ctx.show_text("★" * score + '☆' * (6 - score))
        if extra_score:
            ctx.set_source_rgb(0x00 / 255, 0xcc / 255, 0xff / 255)
            ctx.show_text("★" * extra_score)
        y += 2 * LINE_HEIGHT

        ctx.set_font_size(FONT_SIZE)
        ctx.set_source_rgb(0, 0, 0)
        duration = self.engine.get_duration()
        touch_count = self.engine.get_touch_count()
        correct_count = self.engine.get_correct_count()
        error_count = self.engine.get_error_count()
        text = ("練習時間れんしゅうじかん: {:02d}:{:04.1f}\n" + "タッチ数すう: {:d}\n" +
                "ミスタッチ数すう: {:d}\n" +
                "1分間 ぷんかんあたりのただしいタッチ数すう: {:.0f} [CPM] = {:d} [WPM]\n" +
                "1分間 ぷんかんあたりのただしい文字もじ数すう: {:.0f} [文字もじ/分ふん]\n" +
                "ミスタッチのわりあい: {:.1f} [%]").format(
                    int(duration / 60), duration % 60, touch_count,
                    error_count, self.engine.get_cpm(), self.engine.get_wpm(),
                    len(self.engine.get_plain()) * 60 / duration,
                    self.engine.get_error_ratio() * 100)
        hurigana = HuriganaLayout(ctx)
        hurigana.set_ruby_size(FONT_SIZE / 2.5)
        desc = Pango.font_description_from_string(DEFAULT_FONT)
        hurigana.set_font_description(desc)
        hurigana.set_width(WIDTH)
        hurigana.set_spacing(LINE_SPACING)
        hurigana.set_text(text)
        hurigana.draw(x, y)

        hint = self.engine.get_hint()
        if not hint:
            hint = "<kbd>Enter</kbd> つぎにすすむ\n<kbd>Esc</kbd> メニューにもどる"
        self._draw_hints(wid, ctx, hint)

    def _draw_stats(self, wid, ctx: cairo.Context):
        ctx.select_font_face("Noto Sans Mono CJK JP", cairo.FONT_SLANT_NORMAL,
                             cairo.FONT_WEIGHT_NORMAL)
        ctx.set_font_size(FONT_SIZE)

        stats = self.engine.get_stats().get_stats()
        max_wpm = round(self.engine.get_stats().get_max_wpm() + 9, -1)
        today = date.today()
        period = 0
        if stats:
            first_day = stats[0][0]
            period = (today - first_day).days
        if period == 0:
            first_day = today
            period = 1

        x = MARGIN_LEFT + 50
        y = 2 * LINE_HEIGHT

        chart = Chart(ctx, x, y, CHART_WIDTH, CHART_HEIGHT)
        ctx.set_source_rgb(0xcc / 255, 0xcc / 255, 0xcc / 255)
        step = WIDTH / period
        while step < 10:
            step *= 10
        chart.axis(step, CHART_HEIGHT / max_wpm * 10)

        ctx.set_source_rgb(0xcc / 255, 0xcc / 255, 0xcc / 255)
        ctx.move_to(x, y + CHART_HEIGHT)
        self._show_aligned_text(ctx, first_day.strftime('%m/%d'), -0.5, 1)
        ctx.move_to(x + CHART_WIDTH, y + CHART_HEIGHT)
        self._show_aligned_text(ctx, today.strftime('%m/%d'), -0.5, 1)
        ctx.set_source_rgb(0x00 / 255, 0xCC / 255, 0x33 / 255)
        ctx.move_to(x, y)
        self._show_aligned_text(ctx, '100%', -1.2, 0.5)
        ctx.set_source_rgb(0xff / 255, 0x66 / 255, 0x00 / 255)
        ctx.move_to(x + CHART_WIDTH, y)
        self._show_aligned_text(ctx, str(max_wpm) + ' WPM', 0.2, 0.5)

        notes = ' <span foreground="#00CC33">ー 正確せいかくさ</span>\n <span foreground="#FF6600">ー WPM</span>'
        hurigana = HuriganaLayout(ctx)
        hurigana.set_ruby_size(FONT_SIZE / 2.5)
        desc = Pango.font_description_from_string(DEFAULT_FONT)
        hurigana.set_font_description(desc)
        hurigana.set_width(MARGIN_RIGHT)
        hurigana.set_spacing(LINE_SPACING)
        hurigana.set_markup(notes)
        hurigana.draw(x + CHART_WIDTH, y + CHART_HEIGHT - 3 * LINE_HEIGHT)

        # WPM
        ctx.set_source_rgb(0xff / 255, 0x66 / 255, 0x00 / 255)
        data = list(map(lambda x: ((x[0] - first_day).days, x[2]), stats))
        logger.info(data)
        chart.set_x_range(0, period)
        chart.set_y_range(0, max_wpm)
        chart.scatter_line(data)
        chart.scatter_dot(data, 5)

        # Accuracy
        ctx.set_source_rgb(0x00 / 255, 0xCC / 255, 0x33 / 255)
        data = list(map(lambda x: ((x[0] - first_day).days, x[3]), stats))
        logger.info(data)
        chart.set_x_range(0, period)
        chart.set_y_range(0, 1)
        chart.scatter_line(data)
        chart.scatter_dot(data, 5)

        self._draw_hints(wid, ctx,
                         '<kbd>Esc</kbd> もどる\n<kbd>F2</kbd> きろくのリセット')

    def _show_aligned_text(self, ctx: cairo.Context, text, x, y):
        extents = ctx.text_extents(text)
        ctx.rel_move_to(x * extents.width, y * extents.height)
        ctx.show_text(text)

    def _show_centered_text(self, ctx: cairo.Context, text, x, y):
        extents = ctx.text_extents(text)
        ctx.move_to(x - extents.width / 2, y)
        ctx.show_text(text)

    def get_engine(self):
        return self.engine

    def on_commit(self, im, str):
        self.engine.append(str)
        self.queue_draw()

    def on_delete_surrounding(self, im, offset, n_chars):
        self.engine.delete(offset, n_chars, reset=False)
        self.queue_draw()
        return True

    def on_draw(self, wid, ctx: cairo.Context):
        if self.engine.run(self.keyboard):
            GLib.timeout_add(100, self.timeout)
        if self.engine.get_mode() == EngineMode.EXIT:
            self.get_toplevel().destroy()
            return

        title = self.engine.get_title()
        if title:
            title += " – " + get_title()
        else:
            title = get_title()
        self.get_toplevel().set_title(title)

        self._clear(wid, ctx)
        if self.engine.get_mode() == EngineMode.SCORE:
            self._draw_score(wid, ctx)
        elif self.engine.get_mode() == EngineMode.PRACTICE:
            self._draw_practice(wid, ctx)
        elif self.engine.get_mode() == EngineMode.MENU:
            self._draw_menu(wid, ctx)
        elif self.engine.get_mode() == EngineMode.STATS:
            self._draw_stats(wid, ctx)

    def on_focus_in(self, wid, event):
        ime.check_engine()
        ime.set_mode(self.get_engine().get_ime_mode())
        self.im_context.focus_in()
        return True

    def on_focus_out(self, wid, event):
        self.im_context.focus_out()
        ime.restore_engine()
        return True

    def on_key_press(self, wid, event):
        logger.info("'%s', %08x", Gdk.keyval_name(event.keyval), event.state)
        self.engine.inc_touch_count(event, self.keyboard)
        if self.engine.get_mode() == EngineMode.MENU:
            try:
                n = (Gdk.KEY_1, Gdk.KEY_2, Gdk.KEY_3, Gdk.KEY_4, Gdk.KEY_5,
                     Gdk.KEY_6, Gdk.KEY_7, Gdk.KEY_8, Gdk.KEY_9,
                     Gdk.KEY_0).index(event.keyval)
                if self.engine.select(n):
                    self.queue_draw()
                return True
            except ValueError:
                pass
        if self.im_context.filter_keypress(event):
            return True
        if event.keyval == Gdk.KEY_Escape:
            self.engine.escape()
            self.queue_draw()
            return True
        if event.keyval == Gdk.KEY_BackSpace:
            self.engine.backspace()
            self.queue_draw()
            return True
        if event.keyval == Gdk.KEY_Return:
            self.engine.enter(self.keyboard)
            self.queue_draw()
            return True
        if event.keyval == Gdk.KEY_F5:
            if self.engine.get_mode() == EngineMode.MENU:
                self.engine.show_stats()
                self.queue_draw()
            return True
        if event.keyval == Gdk.KEY_F2:
            if self.engine.get_mode() == EngineMode.STATS:
                self.engine.get_stats().reset()
                self.queue_draw()
            return True
        return False

    def on_key_release(self, wid, event):
        if self.im_context.filter_keypress(event):
            return True
        return False

    def on_preedit_changed(self, im):
        self.engine.set_preedit(self.im_context.get_preedit_string())
        self.queue_draw()
        return False

    def on_preedit_end(self, im):
        self.engine.set_preedit(self.im_context.get_preedit_string())
        self.queue_draw()
        return False

    def on_preedit_start(self, im):
        self.engine.set_preedit(self.im_context.get_preedit_string())
        self.queue_draw()
        return False

    def on_retrieve_surrounding(self, im):
        if self.engine.is_practice_mode():
            typed = self.engine.get_typed()
            length = len(typed.encode())
            self.im_context.set_surrounding(typed, length, length)
        else:
            self.im_context.set_surrounding('', 0, 0)
        return True

    def timeout(self):
        self.queue_draw()
        if self.engine.is_practice_mode():
            return True
        self.im_context.reset()
        return False