Пример #1
0
 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="")
Пример #2
0
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("Неверный формат строки")
Пример #3
0
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)
Пример #4
0
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)