def __init__(self, cfg, user_input_queue): self.cfg = cfg self.runtime_cfg = RuntimeCfg() # Queue to send keyboard inputs to CPU Thread: self.user_input_queue = user_input_queue self.op_delay = 0 self.burst_op_count = 100 self.cpu_after_id = None # Used to call CPU OP burst loop self.target_burst_duration = 0.1 # Duration how long should a CPU Op burst loop take self.init_statistics() # Called also after reset self.root = tk.Tk(className="DragonPy") # self.root.config(font="Helvetica 16 bold italic") self.root.geometry("+%d+%d" % (self.root.winfo_screenwidth() * 0.1, self.root.winfo_screenheight() * 0.1)) self.root.bind("<Key>", self.event_key_pressed) self.root.bind("<<Paste>>", self.paste_clipboard) menu_tk_font = TkFont.Font( family='Helvetica', # family='clean', size=11, weight='normal') self.status = tk.StringVar(value="startup %s...\n" % self.cfg.MACHINE_NAME) self.status_widget = tk.Label(self.root, textvariable=self.status, text="Info:", borderwidth=1, font=menu_tk_font) self.status_widget.grid(row=1, column=0) self.status_bar = MultiStatusBar( self.root, row=2, column=0, sticky=tk.NSEW, ) self.status_bar.set_label("python_version", get_python_info()) self.status_bar.set_label("dragonpy_version", "DragonPy v%s" % dragonpy.__version__) self.menubar = tk.Menu(self.root) filemenu = tk.Menu(self.menubar, tearoff=0) filemenu.add_command(label="Exit", command=self.exit) self.menubar.add_cascade(label="File", menu=filemenu) # 6809 menu self.cpu_menu = tk.Menu(self.menubar, tearoff=0) self.cpu_menu.add_command(label="pause", command=self.command_cpu_pause) self.cpu_menu.add_command(label="resume", command=self.command_cpu_pause, state=tk.DISABLED) self.cpu_menu.add_separator() self.cpu_menu.add_command(label="soft reset", command=self.command_cpu_soft_reset) self.cpu_menu.add_command(label="hard reset", command=self.command_cpu_hard_reset) self.menubar.add_cascade(label="6809", menu=self.cpu_menu) self.config_window = None self.menubar.add_command(label="config", command=self.command_config) # FIXME: Only for developing: Open config on startup! # self.root.after(200, self.command_config) # help menu helpmenu = tk.Menu(self.menubar, tearoff=0) helpmenu.add_command(label="help", command=self.menu_event_help) helpmenu.add_command(label="about", command=self.menu_event_about) self.menubar.add_cascade(label="help", menu=helpmenu) self.auto_shift = True # auto shift all input characters?
def __init__(self, cfg, user_input_queue): self.cfg = cfg self.runtime_cfg = RuntimeCfg() # Queue to send keyboard inputs to CPU Thread: self.user_input_queue = user_input_queue self.op_delay = 0 self.burst_op_count = 100 self.cpu_after_id = None # Used to call CPU OP burst loop self.target_burst_duration = 0.1 # Duration how long should a CPU Op burst loop take self.init_statistics() # Called also after reset self.root = tk.Tk(className="DragonPy") # self.root.config(font="Helvetica 16 bold italic") self.root.geometry("+%d+%d" % ( self.root.winfo_screenwidth() * 0.1, self.root.winfo_screenheight() * 0.1 )) self.root.bind("<Key>", self.event_key_pressed) self.root.bind("<<Paste>>", self.paste_clipboard) menu_tk_font = TkFont.Font( family='Helvetica', # family='clean', size=11, weight='normal' ) self.status = tk.StringVar(value="startup %s...\n" % self.cfg.MACHINE_NAME) self.status_widget = tk.Label( self.root, textvariable=self.status, text="Info:", borderwidth=1, font=menu_tk_font ) self.status_widget.grid(row=1, column=0) self.status_bar = MultiStatusBar(self.root, row=2, column=0, sticky=tk.NSEW, ) self.status_bar.set_label("python_version", get_python_info()) self.status_bar.set_label("dragonpy_version", "DragonPy v%s" % dragonpy.__version__) self.menubar = tk.Menu(self.root) filemenu = tk.Menu(self.menubar, tearoff=0) filemenu.add_command(label="Exit", command=self.exit) self.menubar.add_cascade(label="File", menu=filemenu) # 6809 menu self.cpu_menu = tk.Menu(self.menubar, tearoff=0) self.cpu_menu.add_command(label="pause", command=self.command_cpu_pause) self.cpu_menu.add_command(label="resume", command=self.command_cpu_pause, state=tk.DISABLED) self.cpu_menu.add_separator() self.cpu_menu.add_command(label="soft reset", command=self.command_cpu_soft_reset) self.cpu_menu.add_command(label="hard reset", command=self.command_cpu_hard_reset) self.menubar.add_cascade(label="6809", menu=self.cpu_menu) self.config_window = None self.menubar.add_command(label="config", command=self.command_config) # FIXME: Only for developing: Open config on startup! # self.root.after(200, self.command_config) # help menu helpmenu = tk.Menu(self.menubar, tearoff=0) helpmenu.add_command(label="help", command=self.menu_event_help) helpmenu.add_command(label="about", command=self.menu_event_about) self.menubar.add_cascade(label="help", menu=helpmenu) self.auto_shift=True # auto shift all input characters?
class BaseTkinterGUI(object): """ The complete Tkinter GUI window """ def __init__(self, cfg, user_input_queue): self.cfg = cfg self.runtime_cfg = RuntimeCfg() # Queue to send keyboard inputs to CPU Thread: self.user_input_queue = user_input_queue self.op_delay = 0 self.burst_op_count = 100 self.cpu_after_id = None # Used to call CPU OP burst loop self.target_burst_duration = 0.1 # Duration how long should a CPU Op burst loop take self.init_statistics() # Called also after reset self.root = tk.Tk(className="DragonPy") # self.root.config(font="Helvetica 16 bold italic") self.root.geometry("+%d+%d" % (self.root.winfo_screenwidth() * 0.1, self.root.winfo_screenheight() * 0.1)) self.root.bind("<Key>", self.event_key_pressed) self.root.bind("<<Paste>>", self.paste_clipboard) menu_tk_font = TkFont.Font( family='Helvetica', # family='clean', size=11, weight='normal') self.status = tk.StringVar(value="startup %s...\n" % self.cfg.MACHINE_NAME) self.status_widget = tk.Label(self.root, textvariable=self.status, text="Info:", borderwidth=1, font=menu_tk_font) self.status_widget.grid(row=1, column=0) self.status_bar = MultiStatusBar( self.root, row=2, column=0, sticky=tk.NSEW, ) self.status_bar.set_label("python_version", get_python_info()) self.status_bar.set_label("dragonpy_version", "DragonPy v%s" % dragonpy.__version__) self.menubar = tk.Menu(self.root) filemenu = tk.Menu(self.menubar, tearoff=0) filemenu.add_command(label="Exit", command=self.exit) self.menubar.add_cascade(label="File", menu=filemenu) # 6809 menu self.cpu_menu = tk.Menu(self.menubar, tearoff=0) self.cpu_menu.add_command(label="pause", command=self.command_cpu_pause) self.cpu_menu.add_command(label="resume", command=self.command_cpu_pause, state=tk.DISABLED) self.cpu_menu.add_separator() self.cpu_menu.add_command(label="soft reset", command=self.command_cpu_soft_reset) self.cpu_menu.add_command(label="hard reset", command=self.command_cpu_hard_reset) self.menubar.add_cascade(label="6809", menu=self.cpu_menu) self.config_window = None self.menubar.add_command(label="config", command=self.command_config) # FIXME: Only for developing: Open config on startup! # self.root.after(200, self.command_config) # help menu helpmenu = tk.Menu(self.menubar, tearoff=0) helpmenu.add_command(label="help", command=self.menu_event_help) helpmenu.add_command(label="about", command=self.menu_event_about) self.menubar.add_cascade(label="help", menu=helpmenu) self.auto_shift = True # auto shift all input characters? def init_statistics(self): self.op_count = 0 self.last_op_count = 0 self.last_cpu_cycles = 0 self.cpu_cycles_update_interval = 1 # Fequency for update GUI status information self.next_cpu_cycle_update = time.time( ) + self.cpu_cycles_update_interval self.last_cycles_per_second = sys.maxsize def menu_event_about(self): messagebox.showinfo( "DragonPy", "DragonPy the OpenSource emulator written in python.\n" "more info: https://github.com/jedie/DragonPy") def menu_event_help(self): messagebox.showinfo( "Help", "Please read the README:" "https://github.com/jedie/DragonPy#readme") def exit(self): log.critical("DragonTkinterGUI.exit()") try: self.root.destroy() except: pass # ----------------------------------------------------------------------------------------- def close_config(self): self.config_window.root.destroy() self.config_window = None def command_config(self): if self.config_window is None: self.config_window = BaseTkinterGUIConfig(self, self.runtime_cfg) self.config_window.root.protocol("WM_DELETE_WINDOW", self.close_config) else: self.config_window.focus() # ----------------------------------------------------------------------------------------- def status_paused(self): self.status.set("%s paused.\n" % self.cfg.MACHINE_NAME) def command_cpu_pause(self): if self.cpu_after_id is not None: # stop CPU self.root.after_cancel(self.cpu_after_id) self.cpu_after_id = None self.status_paused() self.cpu_menu.entryconfig(index=0, state=tk.DISABLED) self.cpu_menu.entryconfig(index=1, state=tk.NORMAL) else: # restart self.cpu_interval(interval=1) self.cpu_menu.entryconfig(index=0, state=tk.NORMAL) self.cpu_menu.entryconfig(index=1, state=tk.DISABLED) self.init_statistics() # Reset statistics def command_cpu_soft_reset(self): self.machine.cpu.reset() self.init_statistics() # Reset statistics def command_cpu_hard_reset(self): self.machine.hard_reset() self.init_statistics() # Reset statistics # ----------------------------------------------------------------------------------------- def add_user_input(self, txt): add_to_input_queue(self.user_input_queue, txt) def wait_until_input_queue_empty(self): for count in range(1, 10): self.cpu_interval() if self.user_input_queue.empty(): log.critical( "user_input_queue is empty, after %i burst runs, ok.", count) if self.cpu_after_id is None: self.status_paused() return if self.cpu_after_id is None: self.status_paused() log.critical("user_input_queue not empty, after %i burst runs!", count) def add_user_input_and_wait(self, txt): self.add_user_input(txt) self.wait_until_input_queue_empty() def paste_clipboard(self, event): """ Send the clipboard content as user input to the CPU. """ log.critical("paste clipboard") clipboard = self.root.clipboard_get() for line in clipboard.splitlines(): log.critical("paste line: %s", repr(line)) self.add_user_input(line + "\r") def event_key_pressed(self, event): log.critical( "event.char: %-6r event.keycode: %-3r event.keysym: %-11r event.keysym_num: %5r", event.char, event.keycode, event.keysym, event.keysym_num) inkey = inkey_from_tk_event(event, auto_shift=self.auto_shift) log.critical("inkey: %r", inkey) self.user_input_queue.put(inkey) total_burst_duration = 0 cpu_interval_calls = 0 last_display_queue_qsize = 0 def cpu_interval(self, interval=None): self.cpu_interval_calls += 1 if self.runtime_cfg.speedlimit: # Run CPU not faster than speedlimit target_cycles_per_sec = self.runtime_cfg.cycles_per_sec else: # Run CPU as fast as Python can... target_cycles_per_sec = None start_time = time.time() self.machine.cpu.run( max_run_time=self.runtime_cfg.max_run_time, target_cycles_per_sec=target_cycles_per_sec, ) now = time.time() self.total_burst_duration += (now - start_time) if interval is not None: if self.machine.cpu.running: self.cpu_after_id = self.root.after(interval, self.cpu_interval, interval) else: log.critical("CPU stopped.") last_update = 0 def update_status_interval(self, interval=500): # Update CPU settings: self.machine.cpu.max_burst_count = self.runtime_cfg.max_burst_count new_cycles = self.machine.cpu.cycles - self.last_cpu_cycles duration = time.time() - self.last_update cycles_per_sec = new_cycles / duration msg = ("%s cylces/sec (burst op count: outer: %s - inner: %s)\n" "%i CPU interval calls") % ( locale_format_number(cycles_per_sec), locale_format_number(self.machine.cpu.outer_burst_op_count), locale_format_number(self.machine.cpu.inner_burst_op_count), self.cpu_interval_calls, ) if self.runtime_cfg.speedlimit: msg += ( " (Burst delay: %f)\nSpeed target: %s cylces/sec - diff: %s cylces/sec" ) % ( self.machine.cpu.delay, locale_format_number(self.runtime_cfg.cycles_per_sec), locale_format_number(cycles_per_sec - self.runtime_cfg.cycles_per_sec), ) self.status.set(msg) self.last_cpu_cycles = self.machine.cpu.cycles self.cpu_interval_calls = 0 self.burst_loops = 0 self.last_update = time.time() self.root.after(interval, self.update_status_interval, interval) def mainloop(self, machine): self.machine = machine self.update_status_interval(interval=500) self.cpu_interval(interval=1) log.critical("Start root.mainloop()") try: self.root.mainloop() except KeyboardInterrupt: self.exit() log.critical("root.mainloop() has quit!")
class BaseTkinterGUI(object): """ The complete Tkinter GUI window """ def __init__(self, cfg, user_input_queue): self.cfg = cfg self.runtime_cfg = RuntimeCfg() # Queue to send keyboard inputs to CPU Thread: self.user_input_queue = user_input_queue self.op_delay = 0 self.burst_op_count = 100 self.cpu_after_id = None # Used to call CPU OP burst loop self.target_burst_duration = 0.1 # Duration how long should a CPU Op burst loop take self.init_statistics() # Called also after reset self.root = tk.Tk(className="DragonPy") # self.root.config(font="Helvetica 16 bold italic") self.root.geometry("+%d+%d" % ( self.root.winfo_screenwidth() * 0.1, self.root.winfo_screenheight() * 0.1 )) self.root.bind("<Key>", self.event_key_pressed) self.root.bind("<<Paste>>", self.paste_clipboard) menu_tk_font = TkFont.Font( family='Helvetica', # family='clean', size=11, weight='normal' ) self.status = tk.StringVar(value="startup %s...\n" % self.cfg.MACHINE_NAME) self.status_widget = tk.Label( self.root, textvariable=self.status, text="Info:", borderwidth=1, font=menu_tk_font ) self.status_widget.grid(row=1, column=0) self.status_bar = MultiStatusBar(self.root, row=2, column=0, sticky=tk.NSEW, ) self.status_bar.set_label("python_version", get_python_info()) self.status_bar.set_label("dragonpy_version", "DragonPy v%s" % dragonpy.__version__) self.menubar = tk.Menu(self.root) filemenu = tk.Menu(self.menubar, tearoff=0) filemenu.add_command(label="Exit", command=self.exit) self.menubar.add_cascade(label="File", menu=filemenu) # 6809 menu self.cpu_menu = tk.Menu(self.menubar, tearoff=0) self.cpu_menu.add_command(label="pause", command=self.command_cpu_pause) self.cpu_menu.add_command(label="resume", command=self.command_cpu_pause, state=tk.DISABLED) self.cpu_menu.add_separator() self.cpu_menu.add_command(label="soft reset", command=self.command_cpu_soft_reset) self.cpu_menu.add_command(label="hard reset", command=self.command_cpu_hard_reset) self.menubar.add_cascade(label="6809", menu=self.cpu_menu) self.config_window = None self.menubar.add_command(label="config", command=self.command_config) # FIXME: Only for developing: Open config on startup! # self.root.after(200, self.command_config) # help menu helpmenu = tk.Menu(self.menubar, tearoff=0) helpmenu.add_command(label="help", command=self.menu_event_help) helpmenu.add_command(label="about", command=self.menu_event_about) self.menubar.add_cascade(label="help", menu=helpmenu) self.auto_shift=True # auto shift all input characters? def init_statistics(self): self.op_count = 0 self.last_op_count = 0 self.last_cpu_cycles = 0 self.cpu_cycles_update_interval = 1 # Fequency for update GUI status information self.next_cpu_cycle_update = time.time() + self.cpu_cycles_update_interval self.last_cycles_per_second = sys.maxsize def menu_event_about(self): messagebox.showinfo("DragonPy", "DragonPy the OpenSource emulator written in python.\n" "more info: https://github.com/jedie/DragonPy" ) def menu_event_help(self): messagebox.showinfo("Help", "Please read the README:" "https://github.com/jedie/DragonPy#readme" ) def exit(self): log.critical("DragonTkinterGUI.exit()") try: self.root.destroy() except: pass # ----------------------------------------------------------------------------------------- def close_config(self): self.config_window.root.destroy() self.config_window = None def command_config(self): if self.config_window is None: self.config_window = BaseTkinterGUIConfig(self, self.runtime_cfg) self.config_window.root.protocol("WM_DELETE_WINDOW", self.close_config) else: self.config_window.focus() # ----------------------------------------------------------------------------------------- def status_paused(self): self.status.set("%s paused.\n" % self.cfg.MACHINE_NAME) def command_cpu_pause(self): if self.cpu_after_id is not None: # stop CPU self.root.after_cancel(self.cpu_after_id) self.cpu_after_id = None self.status_paused() self.cpu_menu.entryconfig(index=0, state=tk.DISABLED) self.cpu_menu.entryconfig(index=1, state=tk.NORMAL) else: # restart self.cpu_interval(interval=1) self.cpu_menu.entryconfig(index=0, state=tk.NORMAL) self.cpu_menu.entryconfig(index=1, state=tk.DISABLED) self.init_statistics() # Reset statistics def command_cpu_soft_reset(self): self.machine.cpu.reset() self.init_statistics() # Reset statistics def command_cpu_hard_reset(self): self.machine.hard_reset() self.init_statistics() # Reset statistics # ----------------------------------------------------------------------------------------- def add_user_input(self, txt): add_to_input_queue(self.user_input_queue, txt) def wait_until_input_queue_empty(self): for count in xrange(1, 10): self.cpu_interval() if self.user_input_queue.empty(): log.critical("user_input_queue is empty, after %i burst runs, ok.", count) if self.cpu_after_id is None: self.status_paused() return if self.cpu_after_id is None: self.status_paused() log.critical("user_input_queue not empty, after %i burst runs!", count) def add_user_input_and_wait(self, txt): self.add_user_input(txt) self.wait_until_input_queue_empty() def paste_clipboard(self, event): """ Send the clipboard content as user input to the CPU. """ log.critical("paste clipboard") clipboard = self.root.clipboard_get() for line in clipboard.splitlines(): log.critical("paste line: %s", repr(line)) self.add_user_input(line + "\r") def event_key_pressed(self, event): log.critical("event.char: %-6r event.keycode: %-3r event.keysym: %-11r event.keysym_num: %5r", event.char, event.keycode, event.keysym, event.keysym_num ) inkey = inkey_from_tk_event(event, auto_shift=self.auto_shift) log.critical("inkey: %r", inkey) self.user_input_queue.put(inkey) total_burst_duration = 0 cpu_interval_calls = 0 last_display_queue_qsize = 0 def cpu_interval(self, interval=None): self.cpu_interval_calls += 1 if self.runtime_cfg.speedlimit: # Run CPU not faster than speedlimit target_cycles_per_sec = self.runtime_cfg.cycles_per_sec else: # Run CPU as fast as Python can... target_cycles_per_sec = None start_time = time.time() self.machine.cpu.run( max_run_time=self.runtime_cfg.max_run_time, target_cycles_per_sec=target_cycles_per_sec, ) now = time.time() self.total_burst_duration += (now - start_time) if interval is not None: if self.machine.cpu.running: self.cpu_after_id = self.root.after(interval, self.cpu_interval, interval) else: log.critical("CPU stopped.") last_update = 0 def update_status_interval(self, interval=500): # Update CPU settings: self.machine.cpu.max_burst_count = self.runtime_cfg.max_burst_count new_cycles = self.machine.cpu.cycles - self.last_cpu_cycles duration = time.time() - self.last_update cycles_per_sec = new_cycles / duration msg = ( "%s cylces/sec (burst op count: outer: %s - inner: %s)\n" "%i CPU interval calls" ) % ( locale_format_number(cycles_per_sec), locale_format_number(self.machine.cpu.outer_burst_op_count), locale_format_number(self.machine.cpu.inner_burst_op_count), self.cpu_interval_calls, ) if self.runtime_cfg.speedlimit: msg += ( " (Burst delay: %f)\nSpeed target: %s cylces/sec - diff: %s cylces/sec" ) % ( self.machine.cpu.delay, locale_format_number(self.runtime_cfg.cycles_per_sec), locale_format_number( cycles_per_sec - self.runtime_cfg.cycles_per_sec ), ) self.status.set(msg) self.last_cpu_cycles = self.machine.cpu.cycles self.cpu_interval_calls = 0 self.burst_loops = 0 self.last_update = time.time() self.root.after(interval, self.update_status_interval, interval) def mainloop(self, machine): self.machine = machine self.update_status_interval(interval=500) self.cpu_interval(interval=1) log.critical("Start root.mainloop()") try: self.root.mainloop() except KeyboardInterrupt: self.exit() log.critical("root.mainloop() has quit!")