class DateWidget(Frame): """Gets a date from the user.""" def __init__(self, master): """Make boxes, register callbacks etc.""" Frame.__init__(self, master) self.label = Label(self, text="När är du född?") self.label.pack() self.entry_text = StringVar() self.entry_text.trace("w", lambda *args: self.onEntryChanged()) self.entry = Entry(self, width=date_entry_width, textvariable=self.entry_text) self.entry.insert(0, "ÅÅÅÅ-MM-DD") self.entry.pack(pady=small_pad) self.button = Button(self, text="Uppdatera", command=lambda: self.onDateChanged()) self.button.pack() self.entry.focus_set() self.entry.select_range(0, END) self.entry.bind("<Return>", lambda x: self.onDateChanged()) def setListener(self, pred_view): """Select whom to notify when a new date is entered.""" self.pred_view = pred_view def onDateChanged(self): """Notifies the PredictionWidget that the date has been changed.""" try: date = datetime.datetime.strptime(self.entry.get(), "%Y-%m-%d").date() self.pred_view.update(date) except ValueError: self.entry.configure(foreground="red") def onEntryChanged(self): """Reset the text color.""" self.entry.configure(foreground="")
def get_number_from(entry: Entry) -> float: try: return float(fix_separator(entry.get())) except ValueError: entry.focus_set() entry.select_range(0, END) stop_execution("Неверный формат строки")
def focus_set(entry: ttk.Entry): """Set initial focus for this class.""" entry.focus_set() entry.select_range(0, tk.END) entry.icursor(tk.END)
class App(Tk): DEFAULT_SIGN_WIDTH = 150.0 DEFAULT_SIGN_HEIGHT = 22.0 DEFAULT_SHEET_WIDHT = 300.0 DEFAULT_SHEET_HEIGHT = 300.0 DEFAULT_SHEETS_PER_FILE = 0 MAX_SHEET_WIDTH = 470 MAX_SHEET_HEIGHT = 310 MAX_SHEETS_PER_FILE = 100 SPINBOX_WIDTH = 8 PADDING = 2 DXF_VERSIONS = ('R2000', 'R2004', 'R2007', 'R2010', 'R2013', 'R2018') # Initialize GUI layout. def __init__(self) -> None: super().__init__() self.title('KylttiMaker') self.minsize(640, 480) # Tree widget that displays fields and their relative marks in a hierarchy. self.tree = Treeview(self, selectmode='browse') self.tree.heading('#0', text='Fields', command=self.remove_selection) self.tree.bind('<Button-3>', self.tree_right_click) self.tree.bind('<<TreeviewSelect>>', self.tree_selection_changed) self.tree.bind('<Double-Button-1>', self.rename) self.bind('<Escape>', self.remove_selection) self.bind('<Delete>', self.remove) self.tree.pack(side=LEFT, fill=BOTH) self.properties = LabelFrame(self, text='Properties') self.properties.pack(side=RIGHT, fill=BOTH, expand=1) self.fields = {} self.selected_iid = None # Entry field that get's temporarily shown to the user whilst renaming a field or a mark. self.new_name = StringVar(self.tree) self.new_name_entry = Entry(self.tree, textvariable=self.new_name) self.new_name_entry.bind('<Key-Return>', self.new_name_entered) # Output options that get's shown to the user when nothing else is selected from the hierarchy. self.frame = Frame(self.properties) Label(self.frame, text='Sheet size').grid(column=0, row=0, sticky='E', pady=App.PADDING) self.sheet_width_var = StringVar(self.frame) self.sheet_width_var.set(App.DEFAULT_SHEET_WIDHT) Spinbox(self.frame, to=App.MAX_SHEET_WIDTH, textvariable=self.sheet_width_var, width=App.SPINBOX_WIDTH).grid(column=1, row=0, sticky='WE') Label(self.frame, text='x').grid(column=2, row=0) self.sheet_height_var = StringVar(self.frame) self.sheet_height_var.set(App.DEFAULT_SHEET_HEIGHT) Spinbox(self.frame, to=App.MAX_SHEET_HEIGHT, textvariable=self.sheet_height_var, width=App.SPINBOX_WIDTH).grid(column=3, row=0, sticky='WE') Label(self.frame, text='Sign size').grid(column=0, row=1, sticky='E', pady=App.PADDING) self.sign_width_var = StringVar(self.frame) self.sign_width_var.set(App.DEFAULT_SIGN_WIDTH) Spinbox(self.frame, to=App.MAX_SHEET_WIDTH, textvariable=self.sign_width_var, width=App.SPINBOX_WIDTH).grid(column=1, row=1, sticky='WE') Label(self.frame, text='x').grid(column=2, row=1) self.sign_height_var = StringVar(self.frame) self.sign_height_var.set(App.DEFAULT_SIGN_HEIGHT) Spinbox(self.frame, to=App.MAX_SHEET_HEIGHT, textvariable=self.sign_height_var, width=App.SPINBOX_WIDTH).grid(column=3, row=1, sticky='WE') Label(self.frame, text='Layers per sheet').grid(column=0, row=2, sticky='W', pady=App.PADDING) self.layers_per_sheet_var = StringVar(self.frame) self.layers_per_sheet_var.set(App.DEFAULT_SHEETS_PER_FILE) Spinbox(self.frame, to=App.MAX_SHEETS_PER_FILE, textvariable=self.layers_per_sheet_var, width=App.SPINBOX_WIDTH).grid(column=1, row=2, sticky='WE') Label(self.frame, text='(0 = No limit)').grid(column=2, row=2, columnspan=2, sticky='W') Label(self.frame, text='DXF version').grid(column=0, row=4, sticky='E', pady=App.PADDING) self.dxf_version = StringVar(self.frame) OptionMenu(self.frame, self.dxf_version, App.DXF_VERSIONS[0], *App.DXF_VERSIONS).grid(column=1, row=4, sticky='W') Button(self.frame, text='Create', command=self.create).grid(column=2, row=4, columnspan=2) self.frame.pack() # Display a popup menu with relevant options when right clicking on the tree widget item. def tree_right_click(self, event: Event) -> None: menu = Menu(self, tearoff=0) iid = self.tree.identify_row(event.y) if iid: if iid in self.fields: menu.add_command(label='Add QR', command=lambda: self.add_mark(QR, iid)) menu.add_command(label='Add Text', command=lambda: self.add_mark(Text, iid)) menu.add_command(label='Add Hole', command=lambda: self.add_mark(Hole, iid)) menu.add_command(label='Rename', command=lambda: self.rename(iid=iid)) menu.add_command(label='Remove', command=lambda: self.remove(iid=iid)) else: menu.add_command(label='Add field', command=self.add_field) menu.tk_popup(event.x_root, event.y_root) # Display the properties of the selected item. def tree_selection_changed(self, event: Event) -> None: # Hide the items previously shown in the properties pane. self.new_name_entry.place_forget() for child in self.properties.winfo_children(): child.pack_forget() selected_items = self.tree.selection() if selected_items: self.selected_iid = selected_items[0] # Check if the selected item is a field or a mark object, in which case show its properties. if self.selected_iid in self.fields: self.fields[self.selected_iid].frame.pack() else: for field_iid in self.fields: if self.selected_iid in self.fields[field_iid].marks: self.fields[field_iid].marks[ self.selected_iid].frame.pack() else: # Clear the properties pane. self.selected_iid = None self.frame.pack() # Create a new field object and add a corresponding node to the hierarchy. def add_field(self) -> None: iid = self.tree.insert('', END, text='Field') self.fields[iid] = Field(self.properties) # Display a entry for the user to input a new name for the item to be renamed. def rename(self, event: Event = None, iid: int = None) -> None: if not iid: if self.selected_iid: iid = self.selected_iid else: return self.editing_iid = iid self.new_name.set(self.tree.item(iid)['text']) self.new_name_entry.place(x=20, y=0) self.new_name_entry.focus_set() self.new_name_entry.select_range(0, END) # Display the renamed item in the hierarchy. def new_name_entered(self, event: Event) -> None: self.tree.item(self.editing_iid, text=self.new_name.get()) self.new_name_entry.place_forget() # Link a new mark speciefied by mark_type parameter to the field speciefied by field_iid parameter. def add_mark(self, mark_type: Union[QR, Text, Hole], field_iid: int = None) -> None: if not field_iid: if self.selected_iid in self.fields: field_iid = self.selected_iid else: print('Select a field first.') return iid = self.tree.insert(field_iid, END, text=mark_type.__name__) self.fields[field_iid].marks[iid] = mark_type(self.properties) self.tree.see(iid) # Remove a tree item speciefied by iid parameter, else removes the currently selected item. def remove(self, event: Event = None, iid: int = None) -> None: if not iid: if self.selected_iid: iid = self.selected_iid else: print('Select something first.') return # Check if the item to be removed is a field item, else check if it is a mark item. if iid in self.fields: self.remove_selection() self.tree.delete(iid) del self.fields[iid] else: for field_iid in self.fields: if iid in self.fields[field_iid].marks: self.remove_selection() self.tree.delete(iid) del self.fields[field_iid].marks[iid] # Clear the selection. def remove_selection(self, event: Event = None) -> None: for item in self.tree.selection(): self.tree.selection_remove(item) # Create sheets according to entered settings. def create(self) -> None: if not self.fields: print('No fields.') return # Calculate the length of the longest field (some fields can have less values than others). total_signs = 0 for field_iid in self.fields: total_signs = max(total_signs, len(self.fields[field_iid].data)) if total_signs == 0: print('No fields with data.') return try: sheet_width = float(self.sheet_width_var.get()) sheet_height = float(self.sheet_height_var.get()) sign_width = float(self.sign_width_var.get()) sign_height = float(self.sign_height_var.get()) layers_per_sheet = int(self.layers_per_sheet_var.get()) assert sign_width > 0, 'Sign width must be greater than 0.' assert sign_height > 0, 'Sign height must be greater than 0.' assert sheet_width >= sign_width, 'Sheet width must be greater than sign width.' assert sheet_height >= sign_height, 'Sheet height must be greater than sign height.' except ValueError: print('Invalid dimensions.') return except AssertionError as e: print(e) return # Show progress bar. progress_bar = Progressbar(self.frame) progress_bar.grid(column=0, row=5, columnspan=4, sticky='WE') # Calculate the needed values to define sheet layout. signs_per_row = int(sheet_width // sign_width) signs_per_column = int(sheet_height // sign_height) signs_per_layer = signs_per_row * signs_per_column # Ceiling division. total_layers = -int(-total_signs // signs_per_layer) if layers_per_sheet > 0: total_sheets = -int(-total_layers // layers_per_sheet) else: total_sheets = 1 print( f'Marking total of {total_signs} signs ({sign_width} x {sign_height}).' ) print( f'Sheet size of {sheet_width} x {sheet_height} fits {signs_per_row} x {signs_per_column} signs,' ) print( f'so the effective sheet size is {signs_per_row * sign_width} x {signs_per_column * sign_height}.' ) print(f'Total of {total_layers} layer(s) are needed.') if layers_per_sheet == 0: print( 'There is no limit on the maximum amount of layers per sheet,') else: print( f'There are maximum of {layers_per_sheet} layer(s) per sheet,') print(f'so total of {total_sheets} sheet(s) are needed.') # Create needed sheet objects. print('Creating sheets.') sheets = [] for _ in range(total_sheets): sheets.append(ezdxf.new(self.dxf_version.get())) # Iterate over all layers and draw their outline based on how many signs that layer will have. print('Drawing layer outlines.') for layer in range(total_layers): max_x = sign_width * signs_per_row max_y = -sign_height * signs_per_column if layer == total_layers - 1: # If last layer. signs_in_last_sheet = total_signs - layer * signs_per_layer if signs_in_last_sheet < signs_per_row: max_x = sign_width * signs_in_last_sheet max_y = sign_height * (-signs_in_last_sheet // signs_per_row) if layers_per_sheet > 0: sheet_index = layer // layers_per_sheet else: sheet_index = 0 # Draw layer outline (left and top side bounds). sheets[sheet_index].modelspace().add_lwpolyline( [(0, max_y), (0, 0), (max_x, 0)], dxfattribs={'layer': str(layer)}) # Iterate over each sign. print('Drawing marks.') for sign_index in range(total_signs): # Update progress bar value. progress_bar['value'] = (sign_index + 1) / total_signs * 100 progress_bar.update() # Calculate in which position, in which layer of which sheet the current sign should be drawn. layer = sign_index // signs_per_layer layer_position = sign_index % signs_per_layer sign_origin_x = (layer_position % signs_per_row) * sign_width sign_origin_y = -(layer_position // signs_per_row) * sign_height if layers_per_sheet > 0: sheet_index = layer // layers_per_sheet else: sheet_index = 0 sheet = sheets[sheet_index] # Draw marks (QR, Text and Hole objects). for field_iid in self.fields: try: self.fields[field_iid].draw(sign_index, sheet, layer, sign_origin_x, sign_origin_y, sign_width, sign_height) except Exception as e: print(e) progress_bar.grid_forget() return # Draw sign outline (right and bottom side bounds). sign_outline = [(sign_origin_x, sign_origin_y - sign_height), (sign_origin_x + sign_width, sign_origin_y - sign_height), (sign_origin_x + sign_width, sign_origin_y)] sheet.modelspace().add_lwpolyline(sign_outline, dxfattribs={'layer': str(layer)}) # Save sheets. # Get a output directory if there are multiple sheets to be saved, otherwise get path for the single output (.dxf) file. print('Saving.') if total_sheets > 1: if directory := tkinter.filedialog.askdirectory(): for index, sheet in enumerate(sheets): # Indicate save progress. progress_bar['value'] = (index + 1) / len(sheets) * 100 progress_bar.update() sheet.saveas(Path(directory) / f'sheet{index}.dxf') elif path := tkinter.filedialog.asksaveasfilename( defaultextension='.dxf', filetypes=(('DXF', '*.dxf'), ('All files', '*.*'))): sheets[0].saveas(path)