class TtkScale(Scale):
    def __init__(self,
                 parent,
                 length=0,
                 from_=0,
                 to=255,
                 orient='horizontal',
                 variable=0,
                 digits=0,
                 tickinterval=None,
                 sliderlength=32,
                 command=None,
                 style=None,
                 showvalue=True,
                 resolution=1):

        self.from_ = from_
        self.to = to
        self.variable = variable
        self.length = length
        self.command = command
        self.parent = parent
        self.orient = orient

        super().__init__(parent,
                         length=length,
                         from_=from_,
                         to=to,
                         orient=orient,
                         variable=variable,
                         command=command,
                         style=style)

        self.digits = digits
        self.tickinterval = tickinterval
        self.showvalue = showvalue
        self.resolution = resolution
        self.sliderlength = sliderlength  # = 32

        theme_sl = {
            'alt': 9,
            'clam': 30,
            'classic': 30,
            'default': 30,
            'lime': 9,
            'winnative': 9
        }

        theme_bw = {
            'alt': 0,
            'clam': 1,
            'classic': 2,
            'default': 1,
            'lime': 6,
            'winnative': 0
        }

        # set trough borderwidth
        st = Style(self)
        theme_used = st.theme_use()
        if theme_used in ('alt', 'clam', 'classic', 'default', 'lime',
                          'winnative'):
            self.bw_val = bw_val = theme_bw[theme_used]
            self.sliderlength = sliderlength = theme_sl[theme_used]
        else:
            self.bw_val = bw_val = 1
        if showvalue:
            self.configure(command=self.display_value)

        def_font = font.nametofont('TkDefaultFont')
        # if from_ more than to swap values
        if from_ < to:
            pass
        else:
            from_, to = to, from_

        data = np.arange(from_,
                         (to + 1 if tickinterval >= 1 else to + tickinterval),
                         tickinterval)
        self.data = data = np.round(data, 1)
        range_vals = tuple(data)
        len_rvs = len(range_vals)
        if self.orient == 'horizontal':
            vals_size = [def_font.measure(str(i)) for i in range_vals]
            data_size = sum(vals_size)
            space_size = len_rvs * def_font.measure('0')
        else:
            lspace = def_font.metrics('linespace')
            data_size = len_rvs * lspace
            space_size = len_rvs * 3
        sizes = data_size + space_size
        min_len = (sizes if sizes % 50 == 0 else sizes + 50 - sizes % 50)
        self.len_val = len_val = min_len if length < min_len else length
        self.configure(length=len_val)

        self.rel_min = rel_min = (sliderlength // 2 + bw_val) / len_val
        self.rel_max = rel_max = 1 - (sliderlength // 2 + bw_val) / len_val

        if range_vals[-1] == to:
            pass
        else:
            max_rv = range_vals[-1]
            self.mult_l = ((max_rv - from_) * rel_max / (to - from_))

        self.bind("<Button-1>", self.resolve)

        self.build(from_, to, rel_min, rel_max, range_vals, len_rvs)

    def build(self, from_, to, rel_min, rel_max, range_vals, len_rvs):
        if self.orient == 'horizontal':
            for i, rv in enumerate(range_vals):
                item = Label(self.parent, text=rv)
                item.place(
                    in_=self,
                    bordermode='outside',
                    relx=(rel_min + i / (len_rvs - 1) *
                          ((rel_max if range_vals[-1] == to else self.mult_l) -
                           rel_min)),
                    rely=1,
                    anchor='n')
        else:
            for i, rv in enumerate(range_vals):
                item = Label(self.parent, text=rv)
                item.place(
                    in_=self,
                    bordermode='outside',
                    rely=(rel_min + i / (len_rvs - 1) *
                          ((rel_max if range_vals[-1] == to else self.mult_l) -
                           rel_min)),
                    relx=1,
                    anchor='w')

        if self.showvalue:
            self.disp_lab = Label(self.parent, text=self.get())
            rel_l = self.convert_to_rel(float(self.get()))
            if self.orient == 'horizontal':
                self.disp_lab.place(in_=self,
                                    bordermode='outside',
                                    relx=rel_l,
                                    rely=0,
                                    anchor='s')
            else:
                self.disp_lab.place(in_=self,
                                    bordermode='outside',
                                    rely=rel_l,
                                    relx=0,
                                    anchor='e')

    def convert_to_rel(self, curr_val):
        return ((curr_val - self.from_) * (self.rel_max - self.rel_min) /
                (self.to - self.from_) + self.rel_min)

    def convert_to_act(self, curr_val):
        l_max = self.rel_max * self.len_val
        l_min = self.rel_min * self.len_val
        return ((curr_val - self.from_) * (l_max - l_min) /
                (self.to - self.from_) + l_min)

    def display_value(self, value):
        # position (in pixel) of the center of the slider
        rel_l = self.convert_to_rel(float(value))
        self.disp_lab.config(text=value)  # text=""
        if self.orient == 'horizontal':
            self.disp_lab.place_configure(relx=rel_l)
        else:
            self.disp_lab.place_configure(rely=rel_l)
        digits = self.digits
        self.disp_lab.configure(text=f'{float(value):.{digits}f}')
        # if your python is not 3.6 or above use the following 2 lines
        #   instead of the line above
        #my_precision = '{:.{}f}'.format
        #self.disp_lab.configure(text=my_precision(float(value), digits))

    def resolve(self, evt):
        resolution = self.resolution
        if resolution < 1 or self.tickinterval < 1:
            pass
        else:
            value = self.get()
            curr_l = self.convert_to_act(value)
            if self.orient == 'horizontal':
                if evt.x < curr_l - self.sliderlength / 2:
                    self.set(value - resolution + 1)
                elif evt.x > curr_l + self.sliderlength / 2:
                    self.set(value + resolution - 1)
            else:
                if evt.y < curr_l - self.sliderlength / 2:
                    self.set(value - resolution + 1)
                elif evt.y > curr_l + self.sliderlength / 2:
                    self.set(value + resolution - 1)
class TtkScale(Scale):
    def __init__(self,
                 parent,
                 length=0,
                 from_=0,
                 to=255,
                 orient='vertical',
                 variable=0,
                 digits=None,
                 tickinterval=None,
                 command=None,
                 style=None,
                 showvalue=True,
                 resolution=1):

        self.from_ = from_
        self.to = to
        self.variable = variable
        self.length = length
        self.command = command
        self.parent = parent

        super().__init__(parent,
                         length=length,
                         from_=from_,
                         to=to,
                         orient=orient,
                         variable=variable,
                         command=command,
                         style=style)

        self.digits = digits
        self.tickinterval = tickinterval
        self.showvalue = showvalue
        self.resolution = resolution

        # set sliderlength
        st = Style(self)
        self.bw_val = bw_val = st.lookup('Vertical.Scale.trough',
                                         'borderwidth')
        self.sliderlength = sliderlength = 32

        if showvalue:
            self.configure(command=self.display_value)

        def_font = font.nametofont('TkDefaultFont')
        # if from_ more than to swap values
        if from_ < to:
            pass
        else:
            from_, to = to, from_

        data = np.arange(from_,
                         (to + 1 if tickinterval >= 1 else to + tickinterval),
                         tickinterval)
        self.data = data = np.round(data, 1)
        range_vals = tuple(data)
        len_rvs = len(range_vals)
        lspace = def_font.metrics('linespace')
        len_rvs = len(range_vals)
        data_size = len_rvs * lspace

        space_size = len_rvs * 3
        sizes = data_size + space_size
        min_len = (sizes if sizes % 50 == 0 else sizes + 50 - sizes % 50)
        self.len_val = len_val = min_len if length < min_len else length
        self.configure(length=len_val)

        self.rel_min = rel_min = (sliderlength / 2 + bw_val) / len_val
        self.rel_max = rel_max = 1 - (sliderlength / 2 - bw_val) / len_val
        if range_vals[-1] == to:
            pass
        else:
            max_rv = range_vals[-1]
            self.mult_y = mult_y = ((max_rv - from_) * rel_max / (to - from_))

        self.bind("<Button-1>", self.resolve)

        self.build(from_, to, rel_min, rel_max, range_vals, len_rvs)

    def build(self, from_, to, rel_min, rel_max, range_vals, len_rvs):

        for i, rv in enumerate(range_vals):
            item = Label(self.parent, text=rv)
            item.place(
                in_=self,
                bordermode='outside',
                rely=(rel_min + i / (len_rvs - 1) *
                      ((rel_max if range_vals[-1] == to else self.mult_y) -
                       rel_min)),
                relx=1,
                anchor='w')

        if self.showvalue:
            self.disp_lab = Label(self.parent, text=self.get())
            rel_y = self.convert_to_rely(float(
                self.get()))  #, textvariable = self.act_val)
            self.disp_lab.place(in_=self,
                                bordermode='outside',
                                rely=rel_y,
                                relx=0,
                                anchor='e')

    def convert_to_rely(self, curr_val):
        return ((curr_val - self.from_) * (self.rel_max - self.rel_min) /
                (self.to - self.from_) + self.rel_min)

    def convert_to_acty(self, curr_val):
        y_max = self.rel_max * self.len_val
        y_min = self.rel_min * self.len_val
        return ((curr_val - self.from_) * (y_max - y_min) /
                (self.to - self.from_) + y_min)

    def display_value(self, value):
        # position (in pixel) of the center of the slider
        rel_y = self.convert_to_rely(float(value))
        self.disp_lab.config(text=value)  # text=""
        self.disp_lab.place_configure(rely=rel_y)
        digits = self.digits
        self.disp_lab.configure(text=f'{float(value):.{dig_val}f}')
        # if your python is not 3.6 or above use the following 2 lines
        #   instead of the line above
        #my_precision = '{:.{}f}'.format
        #self.disp_lab.configure(text=my_precision(float(value), digits))

    def resolve(self, evt):
        resolution = self.resolution
        if resolution < 1 or self.tickinterval < 1:
            pass
        else:
            value = self.get()
            curr_y = self.convert_to_acty(value)
            if evt.y < curr_y - self.sliderlength / 2:
                self.set(value - resolution + 1)
            elif evt.y > curr_y + self.sliderlength / 2:
                self.set(value + resolution - 1)
Example #3
0
class TtkScale(Scale):
    """ Enhanced ttk Scale

    Parameters
    ----------
    parent : str
                The parent tk widget, normally a Frame.
    length : int
                The Scale length in pixels.
    from_ : int
                The minimum extent of the range.
    to : int
                the maximum extent of slider movement.
    orient : str
                Either 'horizontal' or 'vertical'
    variable : var
                Name of a tkinter variable.
    digits : int
                Number of decimal places displayed on actual value.
    tickinterval : int or float
                Spacing between range values.
    sliderlength : int
                Length used to set range position.
    command : var
                Method that is called whenever the slider moves.
    style : str
                Used to set the Scale's appearance.
    showvalue : bool
                True turns on the actual display value.
    resolution : int
                Amount slider moves when the trough is clicked, in pixels.

    """
    def __init__(self,
                 parent,
                 length=0,
                 from_=0,
                 to=255,
                 orient='horizontal',
                 variable=0,
                 digits=0,
                 tickinterval=None,
                 sliderlength=32,
                 command=None,
                 style=None,
                 showvalue=True,
                 resolution=1):

        self.from_ = from_
        self.to = to
        self.variable = variable
        self.length = length
        self.command = command
        self.parent = parent
        self.orient = orient

        super().__init__(parent,
                         length=length,
                         from_=from_,
                         to=to,
                         orient=orient,
                         variable=variable,
                         command=command,
                         style=style)

        self.digits = digits
        self.tickinterval = tickinterval
        self.showvalue = showvalue
        self.resolution = resolution
        self.sliderlength = sliderlength  # = 32

        theme_sl = {
            'alt': 9,
            'clam': 30,
            'classic': 30,
            'default': 30,
            'lime': 9,
            'winnative': 9
        }

        theme_bw = {
            'alt': 0,
            'clam': 1,
            'classic': 2,
            'default': 1,
            'lime': 6,
            'winnative': 0
        }

        # set trough borderwidth
        st = Style(self)
        theme_used = st.theme_use()
        if theme_used in ('alt', 'clam', 'classic', 'default', 'lime',
                          'winnative'):
            self.bw_val = bw_val = theme_bw[theme_used]
            self.sliderlength = sliderlength = theme_sl[theme_used]
        else:
            self.bw_val = bw_val = 1

        if showvalue:
            self.configure(command=self.display_value)

        if showvalue:
            self.configure(command=self.display_value)

        def_font = font.nametofont('TkDefaultFont')

        data = np.arange(from_,
                         (to + 1 if tickinterval >= 1 else to + tickinterval),
                         tickinterval)
        self.data = data = np.round(data, 1)
        range_vals = tuple(data)
        len_rvs = len(range_vals)
        if self.orient == 'horizontal':
            vals_size = [def_font.measure(str(i)) for i in range_vals]
            data_size = sum(vals_size)
            space_size = len_rvs * def_font.measure('0')
        else:
            lspace = def_font.metrics('linespace')
            data_size = len_rvs * lspace
            space_size = len_rvs * 3
        sizes = data_size + space_size
        min_len = (sizes if sizes % 50 == 0 else sizes + 50 - sizes % 50)
        self.len_val = len_val = min_len if length < min_len else length
        self.configure(length=len_val)

        self.rel_min = rel_min = (sliderlength // 2 + bw_val) / len_val
        self.rel_max = rel_max = 1 - (sliderlength // 2 - bw_val) / len_val

        if range_vals[-1] == to:
            pass
        else:
            max_rv = range_vals[-1]
            self.mult_l = ((max_rv - from_) * rel_max / (to - from_))

        self.bind("<Button-1>", self.resolve)

        self.build(to, rel_min, rel_max, range_vals, len_rvs)

    def build(self, to, rel_min, rel_max, range_vals, len_rvs):
        """Creates the Labels used to show the range and the first actual value.

        Parameters
        ----------
        to : int
                The maximum extent of slider movement.
        rel_min : float
                The range's minimum position as a relative size of the Scale.
        rel_max : float
                The range's maximum position as a relative size of the Scale.
        range_vals : tuple of int or floats
                The values shown in the range.
        len_rvs : int
                Size of the range values.

        """

        if self.orient == 'horizontal':
            for i, rv in enumerate(range_vals):
                item = Label(self.parent, text=rv)
                item.place(
                    in_=self,
                    bordermode='outside',
                    relx=(rel_min + i / (len_rvs - 1) *
                          ((rel_max if range_vals[-1] == to else self.mult_l) -
                           rel_min)),
                    rely=1,
                    anchor='n')
        else:
            for i, rv in enumerate(range_vals):
                item = Label(self.parent, text=rv)
                item.place(
                    in_=self,
                    bordermode='outside',
                    rely=(rel_min + i / (len_rvs - 1) *
                          ((rel_max if range_vals[-1] == to else self.mult_l) -
                           rel_min)),
                    relx=1,
                    anchor='w')

        if self.showvalue:
            self.disp_lab = Label(self.parent, text=self.get())
            rel_l = self.convert_to_rel(float(self.get()))
            if self.orient == 'horizontal':
                self.disp_lab.place(in_=self,
                                    bordermode='outside',
                                    relx=rel_l,
                                    rely=0,
                                    anchor='s')
            else:
                self.disp_lab.place(in_=self,
                                    bordermode='outside',
                                    rely=rel_l,
                                    relx=0,
                                    anchor='e')

    def convert_to_rel(self, curr_val):
        """Method to convert the actual value to a relative position.

        Parameters
        ----------
        curr_val : float
                    Actual value of the Scale.

        Returns
        -------
        float

        """
        return ((curr_val - self.from_) * (self.rel_max - self.rel_min) /
                (self.to - self.from_) + self.rel_min)

    def convert_to_act(self, curr_val):
        """Method to convert the actual value to an actual position.

        Parameters
        ----------
        curr_val : float
                    Actual value of the Scale.

        Returns
        -------
        float

        """

        l_max = self.rel_max * self.len_val
        l_min = self.rel_min * self.len_val
        return ((curr_val - self.from_) * (l_max - l_min) /
                (self.to - self.from_) + l_min)

    def display_value(self, value):
        """Position (in pixel) of the centre of the slider.

        Parameters
        ----------
        value : float
                    Actual value of the Scale.

        Returns
        -------
        float

        """

        rel_l = self.convert_to_rel(float(value))
        self.disp_lab.config(text=value)  # text=""
        if self.orient == 'horizontal':
            self.disp_lab.place_configure(relx=rel_l)
        else:
            self.disp_lab.place_configure(rely=rel_l)
        digits = self.digits
        self.disp_lab.configure(text=f'{float(value):.{digits}f}')
        # if your python is not 3.6 or above use the following 2 lines
        #   instead of the line above
        #my_precision = '{:.{}f}'.format
        #self.disp_lab.configure(text=my_precision(float(value), digits))

    def resolve(self, evt):
        """Enables mouse click in trough to move slider selected pixel
            amount

        Parameters
        ----------
        evt : str
                    Actual position of the cursor.

        """

        resolution = self.resolution
        if resolution < 1 or self.tickinterval < 1:
            pass
        else:
            value = self.get()
            curr_l = self.convert_to_act(value)
            if self.orient == 'horizontal':
                if evt.x < curr_l - self.sliderlength / 2:
                    self.set(value - resolution + 1)
                elif evt.x > curr_l + self.sliderlength / 2:
                    self.set(value + resolution - 1)
            else:
                if evt.y < curr_l - self.sliderlength / 2:
                    self.set(value - resolution + 1)
                elif evt.y > curr_l + self.sliderlength / 2:
                    self.set(value + resolution - 1)