class DoubleSlider(ttk.Frame): def __init__(self, parent, controller): ttk.Frame.__init__(self, parent) self.controller = controller # style style = ttk.Style() style.configure('Double.Horizontal.TScale', sliderlenght=30) self.top_slider = TickScale(self, style='Double.Horizontal.TScale', tickinterval=1, resolution=1, from_=1, to=12, length=400, labelpos='s', command=self.update_value) self.top_slider.grid(row=0, column=0, sticky=S) self.top_slider.set(12) self.top_slider.bind("<ButtonRelease-1>", self.update_value) self.bot_slider = TickScale(self, style='Double.Horizontal.TScale', tickinterval=1, resolution=1, from_=1, to=12, length=400, labelpos='n', command=self.update_value) self.bot_slider.grid(row=1, column=0, sticky=N) def reset(self): self.top_slider.set(12) self.bot_slider.set(1) # self.update_value(None) def update_value(self, event): try: entries_len = len(self.master.entries) // 14 total = [0] * (entries_len) if self.top_slider.get() <= self.bot_slider.get(): smaller_slider = int(self.top_slider.get()) bigger_slider = int(self.bot_slider.get()) else: smaller_slider = int(self.bot_slider.get()) bigger_slider = int(self.top_slider.get()) for i in range(1, 13): self.master.labels[i].configure(foreground="black") for i in range(smaller_slider, bigger_slider + 2): if i < bigger_slider + 1 and i >= smaller_slider: self.master.labels[i].configure(foreground="#0099ff") if i > smaller_slider: for j in range(1, entries_len + 1): entry = self.master.entries[i, j].get() if ":" in entry: delta = int(entry.split(":")[0]) * 3600 + int( entry.split(":")[1]) * 60 total[j - 1] += delta else: total[j - 1] += int(entry) * 3600 for i in range(1, entries_len + 1): self.master.entries[14, i].delete(0, 10) self.master.entries[14, i].insert( 0, str(int((total[i - 1] // 3600))) + ":" + str(int(total[i - 1] % 3600) // 60).zfill(2)) except AttributeError: pass # returns the range of sliders e.g (2,7) def get_range(self): less = min(int(self.top_slider.get()), int(self.bot_slider.get())) more = max(int(self.top_slider.get()), int(self.bot_slider.get())) return ((less, more))
def test_tickscale_methods(self): scale = TickScale(self.window, from_=0, to=10, orient='horizontal') scale.pack() self.window.update() self.assertEqual(scale.cget('digits'), -1) self.assertEqual(scale['tickinterval'], 0) self.assertEqual(scale['resolution'], 0) self.assertTrue(scale.cget('showvalue')) self.assertEqual(scale['from'], 0) self.assertEqual(scale.cget('to'), 10) keys = [ 'command', 'variable', 'orient', 'from', 'to', 'value', 'length', 'takefocus', 'cursor', 'style', 'class', 'tickinterval', 'showvalue', 'digits' ] self.assertTrue(all(key in scale.keys() for key in keys)) scale.config(from_=-1) self.window.update() self.assertEqual(scale['from'], -1) scale.configure({'to': 20, 'tickinterval': 5, 'digits': 1}) self.window.update() self.assertEqual(scale['to'], 20) self.assertEqual(scale['digits'], 1) self.assertEqual(scale['tickinterval'], 5) scale.configure(tickinterval=0.01, resolution=0.05) self.window.update() self.assertEqual(scale['digits'], 2) self.assertEqual(scale['resolution'], 0.05) self.assertEqual(scale['tickinterval'], 0.05) scale.set(1.1036247) self.assertEqual(scale.get(), 1.10) scale['labelpos'] = 's' self.window.update() self.assertEqual(scale['labelpos'], 's') scale['labelpos'] = 'e' self.window.update() self.assertEqual(scale['labelpos'], 'e') scale['labelpos'] = 'w' self.window.update() self.assertEqual(scale['labelpos'], 'w') scale['tickpos'] = 'n' self.window.update() self.assertEqual(scale['tickpos'], 'n') scale['orient'] = 'vertical' self.window.update() scale['tickpos'] = 'e' self.window.update() self.assertEqual(scale['tickpos'], 'e') with self.assertRaises(ValueError): scale.configure(orient='vertical', tickpos='n') with self.assertRaises(ValueError): scale.configure(orient='horizontal', tickpos='e') with self.assertRaises(ValueError): TickScale(self.window, labelpos='ne') scale.configure(style='my.Vertical.TScale') self.window.update() scale.style.configure('my.Vertical.TScale', font='TkDefaultFont 20 italic', sliderlength=50) scale.x = 0 def cmd(value): scale.x = 2 * float(value) scale.configure(command=cmd) self.window.update() scale.configure(digits=1) scale.set(10) self.window.update() self.assertEqual(scale.x, 20) self.assertEqual(scale.label.cget('text'), '10.0') scale['showvalue'] = False self.window.update() self.assertEqual(scale.label.place_info(), {}) scale['orient'] = 'horizontal' self.window.update() scale['style'] = '' scale['showvalue'] = True self.window.update() scale['length'] = 200 scale['digits'] = 3 scale.style.configure('Horizontal.TScale', font='TkDefaultFont 20 italic', sliderlength=10) self.window.update() scale.set(0) self.window.update() scale.set(20) self.window.update()
class App(tk.Tk): ''' Main class, it contains all functions and objects connected to UI. ''' def __init__(self): ''' Create the structure of the UI ''' super().__init__() self.data = [] # List that contain the data to sort self.select_alg_menu = tk.StringVar() self.stop = False self.rectangles = [] self.colors = [] self.title("Array sorter") self.maxsize(MAX_WIDTH, MAX_HEIGHT) self.iconphoto(True, tk.PhotoImage(file='sorter_icon.png')) # Setup of custom style self.style = ttk.Style(self) self.tk.call('source', 'Azure-ttk-theme/azure-dark.tcl') self.style.theme_use('azure-dark') # top_menu contains all the interactive component (button, sliders...) self.top_menu = tk.Frame(self, width=MAX_WIDTH, height=MAX_HEIGHT / 4, bg=grey) self.top_menu.grid(row=0, column=0, pady=4) # drawArea is the Canvas where rectangles'll be drawn self.drawArea = tk.Canvas(self, width=MAX_WIDTH, height=MAX_HEIGHT, bg=lightBlue) self.drawArea.grid(row=1, column=0) # first ROW tk.Label(self.top_menu, text="Algorithm: ", bg=grey).grid(row=0, column=0, padx=5, pady=5, sticky='') self.algorithm_menu = ttk.Combobox( self.top_menu, textvariable=self.select_alg_menu, values=['SELECTION', 'BUBBLE', 'INSERTION', 'QUICKSORT']) self.algorithm_menu.grid(row=0, column=1, padx=15, pady=5) self.algorithm_menu.current([0]) tk.Label(self.top_menu, text="Speed [s]: ", bg=grey).grid(row=0, column=2, padx=15, pady=5, sticky='SW') self.speedScale = TickScale(self.top_menu, from_=0.005, to=2, digits=3, orient='horizontal') self.speedScale.grid(row=0, column=3, columnspan=3, padx=15, pady=0, sticky='W') # second ROW tk.Label(self.top_menu, text="Size: ", bg=grey).grid(row=1, column=0, padx=15, pady=3, sticky='SW') self.sizeEntry = TickScale(self.top_menu, from_=1, to=200, digits=0, orient='horizontal') self.sizeEntry.grid(row=1, column=1, padx=15, pady=0, sticky='W') tk.Label(self.top_menu, text="Min value: ", bg=grey).grid(row=1, column=2, padx=15, pady=3, sticky='SW') self.minEntry = TickScale(self.top_menu, from_=1, to=200, digits=0, orient='horizontal') self.minEntry.grid(row=1, column=3, padx=15, pady=3, sticky='W') tk.Label(self.top_menu, text="Max Value: ", bg=grey).grid(row=1, column=4, padx=15, pady=3, sticky='SW') self.maxEntry = TickScale(self.top_menu, from_=1, to=200, digits=0, orient='horizontal') self.maxEntry.grid(row=1, column=5, padx=15, pady=3, sticky='W') ttk.Button(self.top_menu, text="Generate", command=lambda: Generate(self), style='AccentButton').grid(row=1, column=6, padx=15) ttk.Button(self.top_menu, text="Start", command=lambda: Thread(target=Start, args=(self, )).start(), style='AccentButton').grid(row=0, column=6, padx=15) ttk.Button(self.top_menu, text="Stop", command=lambda: self.Stop(), style='AccentButton').grid(row=0, column=7, padx=15) def Draw(self, color=list, generate_flag=False): ''' Creation of rectangles, it takes a color[] list because when sorting we need to know which are the rectangles to draw ''' if self.stop: color = [rectBlue for x in range(len(self.data))] total_height = MAX_HEIGHT - (MAX_HEIGHT / 10) # left some space at top # rect_width is calculated knowing that there is # some free_space at the start and at the end rect_width = (MAX_WIDTH - MAX_WIDTH / (len(self.data))) / (len(self.data)) free_space = rect_width / 2 new_arr = [] for i in self.data: # calculating relative height for canvas coordinate new_arr.append(i / max(self.data)) if generate_flag: self.drawArea.delete("all") for i, height in enumerate(new_arr): x_left_top = i * rect_width + free_space y_left_top = total_height - height * (total_height - 20) x_right_bottom = (i + 1) * rect_width + free_space y_right_bottom = total_height if generate_flag: self.colors = [ rectBlue for x in range(int(self.sizeEntry.get())) ] self.rectangles.append( self.drawArea.create_rectangle(x_left_top, y_left_top, x_right_bottom, y_right_bottom, fill=color[i])) elif self.colors[i] != color[i] or i == len(new_arr) - 1: self.drawArea.delete(self.rectangles[i]) self.rectangles[i] = self.drawArea.create_rectangle( x_left_top, y_left_top, x_right_bottom, y_right_bottom, fill=color[i]) for i in range(len(self.colors)): self.colors[i] = color[i] # self.update_idletasks() def Stop(self): # print("stopped") self.stop = True