class App_Runner: """ App runner that uses a Board and a Palette. """ def __init__(self, on_init, app_name, dev_stage, file_extension, deserializers, board_width, board_height, palette_height, directed_wires, label_tooltips_enabled, same_label_per_connector, init_file=None): """ |on_init|: method called every time a new board of this app's type is created. |app_name|: name of this app. |dev_stage|: development stage of this app. |file_extension|: the file extension used for boards of this app's type. |deserializers|: drawabel types to open files of this app's type. |board_width|, |board_height|: board dimensions. |palette_height|: height of palette, width will be the same as board width. |directed_wires|: True if the board is to have directed wires, False otherwise. |label_tooltips_enabled|: if True, show wire and drawable label tooltips. |same_label_per_connector|: if True, all wires from a connector will have the same label. If False, this will only be true for wire connectors. |init_file|: file to open at init time. """ self._on_init = on_init self._app_name = app_name self._dev_stage = dev_stage self._file_extension = file_extension self._deserializers = deserializers self._board_width = board_width self._board_height = board_height self._palette_height = palette_height self._directed_wires = directed_wires self._label_tooltips_enabled = label_tooltips_enabled self._same_label_per_connector = same_label_per_connector self._init_file = init_file self._init() self._setup_menu() self._setup_shortcuts() def _switch_cursor_to_draw(self, *args): """ Switches board cursor state to 'draw'. """ self._draw_display.highlight() self._drag_display.unhighlight() self.board.set_cursor_state('draw') def _switch_cursor_to_drag(self, *args): """ Switches board cursor state to 'drag'. """ self._draw_display.unhighlight() self._drag_display.highlight() self.board.set_cursor_state('drag') def _toggle_cursor(self): """ Toggles board cursor state. """ if self.board.get_cursor_state() == 'draw': self._switch_cursor_to_drag() else: self._switch_cursor_to_draw() def _init(self): """ Creates the board and palette. """ self._file_name = None self._root = Tk() self._root.resizable(0, 0) self._menu = Menu(self._root, tearoff=0) self.board = Board(self._root, self._menu, width=self._board_width, height=self._board_height, directed_wires=self._directed_wires, label_tooltips_enabled=self._label_tooltips_enabled, same_label_per_connector=self._same_label_per_connector, on_changed=self._on_changed, on_exit=self._request_save) self._init_board() self.palette = Palette(self._root, self.board, width=self._board_width, height=self._palette_height) # buttons to change board cursor state self.palette.current_left_x -= PALETTE_PADDING self._draw_display = self.palette.add_drawable_type(Image_Run_Drawable, LEFT, self._switch_cursor_to_draw, image_file=PENCIL_IMAGE) self.palette.current_left_x -= PALETTE_PADDING self._drag_display = self.palette.add_drawable_type(Image_Run_Drawable, LEFT, self._switch_cursor_to_drag, image_file=HAND_IMAGE) self.palette.draw_separator() self._switch_cursor_to_draw() def _setup_menu(self): """ Creates the menu. """ file_menu = Menu(self._menu, tearoff=0) file_menu.add_command(label='New', command=self._new_file, accelerator='Ctrl+N') file_menu.add_command(label='Open', command=self._open_file, accelerator='Ctrl+O') file_menu.add_command(label='Save', command=self._save_file, accelerator='Ctrl+S') file_menu.add_command(label='Save as', command=self._save_as) file_menu.add_separator() file_menu.add_command(label='Quit', command=self.board.quit, accelerator='Ctrl+Q') self._menu.add_cascade(label='File', menu=file_menu) edit_menu = Menu(self._menu, tearoff=0) edit_menu.add_command(label='Undo', command=self.board.undo, accelerator='Ctrl+Z') edit_menu.add_command(label='Redo', command=self.board.redo, accelerator='Ctrl+Y') edit_menu.add_command(label='Delete selected', command=self.board._delete_selected_items, accelerator='Delete') edit_menu.add_command(label='Rotate selected', command=self.board._rotate_selected_item, accelerator='r') edit_menu.add_command(label='Toggle cursor', command=self._toggle_cursor, accelerator='D') self._menu.add_cascade(label='Edit', menu=edit_menu) self._root.config(menu=self._menu) def _setup_shortcuts(self): """ Adds basic shortcuts. """ self.board.parent.bind('<Control-n>', lambda event: self._new_file()) self.board.parent.bind('<Control-N>', lambda event: self._new_file()) self.board.parent.bind('<Control-o>', lambda event: self._open_file()) self.board.parent.bind('<Control-O>', lambda event: self._open_file()) self.board.parent.bind('<Control-q>', lambda event: self.board.quit()) self.board.parent.bind('<Control-Q>', lambda event: self.board.quit()) self.board.parent.bind('<Control-s>', lambda event: self._save_file()) self.board.parent.bind('<Control-S>', lambda event: self._save_file()) self.board.parent.bind('<Control-y>', lambda event: self.board.redo()) self.board.parent.bind('<Control-Y>', lambda event: self.board.redo()) self.board.parent.bind('<Control-z>', lambda event: self.board.undo()) self.board.parent.bind('<Control-Z>', lambda event: self.board.undo()) self.board.add_key_binding('d', self._toggle_cursor) def _init_board(self): """ (Re)Initializes the board based on this app's specific needs, as per self._on_init. """ self.board.clear() self._on_init(self.board) self.board.reset() def _on_changed(self, board_changed): """ Callback for when the board is changed. Updates the title of the app window to indicate whether we need to save the file. """ self._root.title('%s (%s) %s %s' % (self._app_name, self._dev_stage, strip_file_name(self._file_name), '*' if board_changed else '')) def _save_as(self, file_name=None): """ Saves the current state of the app with the given |file_name|. """ saved_file_name = save_board(self.board, file_name, self._app_name, self._file_extension) if saved_file_name: self._file_name = saved_file_name self.board.set_changed(False) def _save_file(self): """ Saves the current state of the app with its current file name (asks for a a file path if it currently does not have one). """ self._save_as(self._file_name) def _request_save(self): """ Requests for a file save if necessary. Returns True if there isn't anything to save or, in the case that there is something to save, if the user either decides to save the file (will be presented with a dialog to do so) or decides not to save the file. Returns False if the user cancels the request for save (i.e. does neither). """ if self.board.changed(): save = request_save_board() if save == 'yes': self._save_file() return True elif save == 'no': return True else: return False else: return True def _open_file(self, new_file_name=None): """ Opens a saved file of this app's type. """ if self._request_save(): new_file_name = new_file_name or get_board_file_name(self._file_name, self._app_name, self._file_extension) if open_board_from_file(self.board, new_file_name, self._deserializers, self._file_extension): self._file_name = new_file_name self._on_changed(False) self.board.reset_cursor_state() def _new_file(self): """ Opens a new file of this app's type. """ if self._request_save(): self._file_name = None self._init_board() def run(self): """ Runs this app. """ self.board.clear_action_history() self._root.title('%s (%s)' % (self._app_name, self._dev_stage)) if self._init_file: self._open_file(self._init_file) self._root.mainloop()
class App_Runner: """ App runner that uses a Board and a Palette. """ def __init__(self, on_init, app_name, dev_stage, file_extension, deserializers, board_width, board_height, palette_height, directed_wires, label_tooltips_enabled, same_label_per_connector, init_file=None): """ |on_init|: method called every time a new board of this app's type is created. |app_name|: name of this app. |dev_stage|: development stage of this app. |file_extension|: the file extension used for boards of this app's type. |deserializers|: drawabel types to open files of this app's type. |board_width|, |board_height|: board dimensions. |palette_height|: height of palette, width will be the same as board width. |directed_wires|: True if the board is to have directed wires, False otherwise. |label_tooltips_enabled|: if True, show wire and drawable label tooltips. |same_label_per_connector|: if True, all wires from a connector will have the same label. If False, this will only be true for wire connectors. |init_file|: file to open at init time. """ self._on_init = on_init self._app_name = app_name self._dev_stage = dev_stage self._file_extension = file_extension self._deserializers = deserializers self._board_width = board_width self._board_height = board_height self._palette_height = palette_height self._directed_wires = directed_wires self._label_tooltips_enabled = label_tooltips_enabled self._same_label_per_connector = same_label_per_connector self._init_file = init_file self._init() self._setup_menu() self._setup_shortcuts() def _switch_cursor_to_draw(self, *args): """ Switches board cursor state to 'draw'. """ self._draw_display.highlight() self._drag_display.unhighlight() self.board.set_cursor_state('draw') def _switch_cursor_to_drag(self, *args): """ Switches board cursor state to 'drag'. """ self._draw_display.unhighlight() self._drag_display.highlight() self.board.set_cursor_state('drag') def _toggle_cursor(self): """ Toggles board cursor state. """ if self.board.get_cursor_state() == 'draw': self._switch_cursor_to_drag() else: self._switch_cursor_to_draw() def _init(self): """ Creates the board and palette. """ self._file_name = None self._root = Tk() self._root.resizable(0, 0) self._menu = Menu(self._root, tearoff=0) self.board = Board( self._root, self._menu, width=self._board_width, height=self._board_height, directed_wires=self._directed_wires, label_tooltips_enabled=self._label_tooltips_enabled, same_label_per_connector=self._same_label_per_connector, on_changed=self._on_changed, on_exit=self._request_save) self._init_board() self.palette = Palette(self._root, self.board, width=self._board_width, height=self._palette_height) # buttons to change board cursor state self.palette.current_left_x -= PALETTE_PADDING self._draw_display = self.palette.add_drawable_type( Image_Run_Drawable, LEFT, self._switch_cursor_to_draw, image_file=PENCIL_IMAGE) self.palette.current_left_x -= PALETTE_PADDING self._drag_display = self.palette.add_drawable_type( Image_Run_Drawable, LEFT, self._switch_cursor_to_drag, image_file=HAND_IMAGE) self.palette.draw_separator() self._switch_cursor_to_draw() def _setup_menu(self): """ Creates the menu. """ file_menu = Menu(self._menu, tearoff=0) file_menu.add_command(label='New', command=self._new_file, accelerator='Ctrl+N') file_menu.add_command(label='Open', command=self._open_file, accelerator='Ctrl+O') file_menu.add_command(label='Save', command=self._save_file, accelerator='Ctrl+S') file_menu.add_command(label='Save as', command=self._save_as) file_menu.add_separator() file_menu.add_command(label='Quit', command=self.board.quit, accelerator='Ctrl+Q') self._menu.add_cascade(label='File', menu=file_menu) edit_menu = Menu(self._menu, tearoff=0) edit_menu.add_command(label='Undo', command=self.board.undo, accelerator='Ctrl+Z') edit_menu.add_command(label='Redo', command=self.board.redo, accelerator='Ctrl+Y') edit_menu.add_command(label='Delete selected', command=self.board._delete_selected_items, accelerator='Delete') edit_menu.add_command(label='Rotate selected', command=self.board._rotate_selected_item, accelerator='r') edit_menu.add_command(label='Toggle cursor', command=self._toggle_cursor, accelerator='D') self._menu.add_cascade(label='Edit', menu=edit_menu) self._root.config(menu=self._menu) def _setup_shortcuts(self): """ Adds basic shortcuts. """ self.board.parent.bind('<Control-n>', lambda event: self._new_file()) self.board.parent.bind('<Control-N>', lambda event: self._new_file()) self.board.parent.bind('<Control-o>', lambda event: self._open_file()) self.board.parent.bind('<Control-O>', lambda event: self._open_file()) self.board.parent.bind('<Control-q>', lambda event: self.board.quit()) self.board.parent.bind('<Control-Q>', lambda event: self.board.quit()) self.board.parent.bind('<Control-s>', lambda event: self._save_file()) self.board.parent.bind('<Control-S>', lambda event: self._save_file()) self.board.parent.bind('<Control-y>', lambda event: self.board.redo()) self.board.parent.bind('<Control-Y>', lambda event: self.board.redo()) self.board.parent.bind('<Control-z>', lambda event: self.board.undo()) self.board.parent.bind('<Control-Z>', lambda event: self.board.undo()) self.board.add_key_binding('d', self._toggle_cursor) def _init_board(self): """ (Re)Initializes the board based on this app's specific needs, as per self._on_init. """ self.board.clear() self._on_init(self.board) self.board.reset() def _on_changed(self, board_changed): """ Callback for when the board is changed. Updates the title of the app window to indicate whether we need to save the file. """ self._root.title( '%s (%s) %s %s' % (self._app_name, self._dev_stage, strip_file_name( self._file_name), '*' if board_changed else '')) def _save_as(self, file_name=None): """ Saves the current state of the app with the given |file_name|. """ saved_file_name = save_board(self.board, file_name, self._app_name, self._file_extension) if saved_file_name: self._file_name = saved_file_name self.board.set_changed(False) def _save_file(self): """ Saves the current state of the app with its current file name (asks for a a file path if it currently does not have one). """ self._save_as(self._file_name) def _request_save(self): """ Requests for a file save if necessary. Returns True if there isn't anything to save or, in the case that there is something to save, if the user either decides to save the file (will be presented with a dialog to do so) or decides not to save the file. Returns False if the user cancels the request for save (i.e. does neither). """ if self.board.changed(): save = request_save_board() if save == 'yes': self._save_file() return True elif save == 'no': return True else: return False else: return True def _open_file(self, new_file_name=None): """ Opens a saved file of this app's type. """ if self._request_save(): new_file_name = new_file_name or get_board_file_name( self._file_name, self._app_name, self._file_extension) if open_board_from_file(self.board, new_file_name, self._deserializers, self._file_extension): self._file_name = new_file_name self._on_changed(False) self.board.reset_cursor_state() def _new_file(self): """ Opens a new file of this app's type. """ if self._request_save(): self._file_name = None self._init_board() def run(self): """ Runs this app. """ self.board.clear_action_history() self._root.title('%s (%s)' % (self._app_name, self._dev_stage)) if self._init_file: self._open_file(self._init_file) self._root.mainloop()