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