def adjust_fonts(self, event): new_font = Font(**self.fonts['normal'].configure()) size = orig_size = new_font['size'] desired_total_height = event.height orig_row_height = new_font.metrics('linespace') orig_row_height += self.LS_EXTRA orig_total_height = self.N_ROWS * orig_row_height if orig_total_height < desired_total_height: a, compfname, final_neg_adjust = 1, '__gt__', True elif orig_total_height > desired_total_height: a, compfname, final_neg_adjust = -1, '__lt__', False else: return prev_total_height = orig_total_height while True: if a < 0 and size <= self.MIN_FONT_SIZE: size = self.MIN_FONT_SIZE break size += a new_font.configure(size=size) new_row_height = new_font.metrics('linespace') new_row_height += self.LS_EXTRA new_total_height = self.N_ROWS * new_row_height if new_total_height == prev_total_height: size -= a break compf = getattr(new_total_height, compfname) if compf(desired_total_height): if final_neg_adjust and size > self.MIN_FONT_SIZE: size -= a break prev_total_height = new_total_height if size != orig_size: self.fonts['normal'].configure(size=size) self.fonts['bold'].configure(size=size)
def displayAutomata(self): for item in self.canvasitems: self.automataCanvas.delete(item) if self.selectedButton == 0: header = "e-NFA" automata = self.nfa if self.dotFound: image = self.nfaimg imagefile = self.nfaimagefile elif self.selectedButton == 1: header = "DFA" automata = self.dfa if self.dotFound: image = self.dfaimg imagefile = self.dfaimagefile elif self.selectedButton == 2: header = "Minimized DFA" automata = self.minDFA if self.dotFound: image = self.mindfaimg imagefile = self.mindfaimagefile font = Font(family="times", size=20) (w, h) = (font.measure(header), font.metrics("linespace")) headerheight = h + 10 itd = self.automataCanvas.create_text(10, 10, text=header, font=font, anchor=NW) self.canvasitems.append(itd) [text, linecount] = automata.getPrintText() font = Font(family="times", size=13) (w, h) = (font.measure(text), font.metrics("linespace")) textheight = headerheight + linecount * h + 20 itd = self.automataCanvas.create_text(10, headerheight + 10, text=text, font=font, anchor=NW) self.canvasitems.append(itd) if self.dotFound: itd = self.automataCanvas.create_image(10, textheight, image=image, anchor=NW) self.canvasitems.append(itd) totalwidth = imagefile.size[0] + 10 totalheight = imagefile.size[1] + textheight + 10 else: totalwidth = self.cwidth + 10 totalheight = textheight + 10 if totalheight < self.cheight: totalheight = self.cheight if totalwidth < self.cwidth: totalwidth = self.cwidth self.automataCanvas.config(scrollregion=(0, 0, totalwidth, totalheight))
def __init__( self, master: Union[tk.Frame, tk.Tk], x: int, y: int, horizontal_anchor: HorizontalAnchor, vertical_anchor: VerticalAnchor, txt: str, font: tkf.Font, txt_color: str, color: str, ): super().__init__( master=master, text=txt, font=font, padx=0, pady=0, fg=txt_color, bg=color, bd=0, ) txt_width = font.measure(txt) txt_height = font.metrics("linespace") xloc, yloc = self._get_position( horizontal_anchor, vertical_anchor, x, y, txt_width, txt_height ) self.place(x=xloc, y=yloc)
def render(self, data, node_name): # Figure out what size the text we want is. label_txt = data.get('label', None) font = Font(family="Helvetica", size=12) h = font.metrics("linespace") + 1 if label_txt: w = font.measure(label_txt) + 2 else: w = font.measure("....") + 2 self.config(width=w, height=h) marker_options = { 'fill': data.get('color', 'blue'), 'outline': 'white' } if data.get('circle', None): self.create_oval(0, 0, w, h, **marker_options) self.config(width=w, height=h) if label_txt: self.create_text(w / 2, h / 2, text=label_txt, font=font, fill="white") else: self.create_rectangle(0, 0, w, h, **marker_options) if label_txt: self.create_text(w / 2, h / 2, text=label_txt, font=font, fill="white")
def text_extents(self, style, text): """ The text extents are calculated using tkinter.Font """ with InternalWindow() as window: font_type = "" if style["bold"]: font_type += "bold" if style["italic"]: if len(font_type) > 0: font_type += " " font_type += "italic" # Create the new font object. font = Font(window, (style["font"], -int(style["size"]*FONT_SCALING), font_type)) # Query the data width = font.measure(text) metrics = font.metrics() return { "width": width / float(FONT_SCALING), "height": metrics["linespace"] / float(FONT_SCALING), "ascent": metrics["ascent"] / float(FONT_SCALING), "descent": metrics["descent"] / float(FONT_SCALING) }
def render(self, data, node_name): label_txt = data.get('label', None) font = Font(family="Helvetica", size=12) h = font.metrics("linespace") + 6 if label_txt: w = font.measure(label_txt) + 8 else: w = font.measure("....") + 2 self.config(width=w, height=h) marker_options = { 'fill': data.get('color', 'orange'), 'outline': 'orange' } if data.get('circle', False) or data.get('type', 'NOTLAN') == 'LAN': self.create_oval(0, 0, w - 1, h - 1, **marker_options) self.config(width=w, height=h) if label_txt: self.create_text((w) / 2, (h) / 2, text=label_txt, font=font, fill="black") else: self.create_rectangle(0, 0, w, h, **marker_options) if label_txt: self.create_text(w / 2, h / 2, text=label_txt, font=font, fill="black")
def label_pos(self, pos, label=None, horizontal=False): """Calculate label start position (top-right if vertical, top-center if horizontal)""" xscale = turtle.getscreen().xscale yscale = turtle.getscreen().yscale font_family, font_size = self.font font = Font(family=font_family, size=font_size) line_height = font.metrics('linespace') / yscale height = (label.count('\n') + 1) * line_height return -8 / xscale if horizontal else pos, \ pos - 0.5 * height if horizontal else -height - 6 / yscale
def label_size(self, label): """Calculate label size""" font_family, font_size = self.font font = Font(family=font_family, size=font_size) width = 0 lines = 0 for line in label.split('\n'): width = max(width, font.measure(line)) lines += 1 xscale = turtle.getscreen().xscale yscale = turtle.getscreen().yscale return width / xscale, font.metrics('linespace') * lines / yscale
class ProgressPopup(tk.Toplevel): '''Displays progress with progressbar''' def __init__(self, title, steps=100): tk.Toplevel.__init__(self) self.fixed_font = Font(size=10) self.line_height = self.fixed_font.metrics("linespace") self.title(title) tk.Label(self, text=title).grid(row=0, column=0, sticky='w', padx=5, pady=20, columnspan=2) self.progress = ttk.Progressbar(self, orient='horizontal', length=200, mode='determinate', maximum=100) self.progress.grid(row=1, column=0, sticky='ew', padx=5, columnspan=2) self.scrollbar = tk.Scrollbar(self) self.scrollbar.grid(row=2, column=1, sticky='nesw', pady=10) self.log = tk.Canvas(self, background='#FFFFFF', width=500, height=150, yscrollcommand=self.scrollbar.set) self.log.grid(row=2, column=0, sticky='nesw', pady=10) self.log.line_number = 0 self.scrollbar.config(command=self.log.yview) self.grab_set() self.step = 100.0 / steps def next(self): self.progress['value'] += self.step self.update() def log_message(self, line): self.log.create_text(0, (self.line_height * self.log.line_number), font=self.fixed_font, text=line, anchor='nw') self.log.line_number += 1 self.log.configure(scrollregion=self.log.bbox('all')) self.log.yview_moveto(1) self.update()
def taille_texte(chaine, police='Helvetica', taille='24'): """ Donne la largeur et la hauteur en pixel nécessaires pour afficher ``chaine`` dans la police et la taille données. :param str chaine: chaîne à mesurer :param police: police de caractères (défaut : `Helvetica`) :param taille: taille de police (défaut 24) :return: couple (w, h) constitué de la largeur et la hauteur de la chaîne en pixels (int), dans la police et la taille données. """ font = Font(family=police, size=taille) return font.measure(chaine), font.metrics("linespace")
class ConversationTreeview(tk.Frame): def __init__(self, parent, *args, **kwargs): super().__init__(parent) self.font = Font(font='TkDefaultFont') self.font_height = self.font.metrics('linespace') self.style = ttk.Style(self) self.style.configure('Treeview', rowheight=((self.font_height * 2) + 10)) self.scrollbar = ttk.Scrollbar(self) self.tree = ttk.Treeview(self, columns=( 'Tweet', 'Author', )) self.scrollbar.configure(command=self.tree.yview) self.tree.configure(yscrollcommand=self.scrollbar.set) self.tree.column('#1', width=140, stretch=0) self.tree.column('#2', width=100, stretch=0) self.tree.heading('#0', text='Tweet') self.tree.heading('#1', text='Author') self.tree.heading('#2', text='Sentiment') self.tree.pack(side='left', fill='both', expand=True) self.scrollbar.pack(side='right', fill='y') def __clear(self): self.tree.delete(*self.tree.get_children()) def wrap_text(self, text): text_length = len(text) if not text_length > 50: return text line1 = [] line2 = [] for word in text.split(): if len(' '.join(line1) + ' ' + word) < (text_length / 2): line1.append(word) else: line2.append(word) return ' '.join(line1) + '\n' + ' '.join(line2) def update(self, conversations): self.__clear() for convo in conversations: root_tweet_text = self.wrap_text(convo.tweets[0]) root_tw = self.tree.insert( '', 'end', text=root_tweet_text, values=[convo.authors[0], convo.conversation_sentiment]) for i in range(1, convo.number_of_turns()): if convo.sentiment_diffs[i - 1] <= 0: sent_diff = '+' + str( round(abs(convo.sentiment_diffs[i - 1]), 5)) else: sent_diff = '-' + str( round(convo.sentiment_diffs[i - 1], 5)) tweet_text = self.wrap_text(convo.tweets[i]) self.tree.insert(root_tw, 'end', text=tweet_text, values=[convo.authors[i], sent_diff])
def create_blocks_fst(self): text_height = Font.metrics(self.myFont, 'linespace') top_line = 20 # block for Expr self.canvas.create_polygon(self.command_block_coords( 50, top_line, text_height + 15, 110), fill='violet red', outline='purple', tags='expr_block') self.canvas.create_polygon(self.inside_block_coords( 55, top_line + 10, text_height, 90), fill='light pink', tags='expr_block') top_line += text_height + 25 # block for return command txt_len = Font.measure(self.myFont, 'return') self.canvas.create_polygon(self.command_block_coords( 50, top_line, text_height + 15, 10 + txt_len + 20 + txt_len), fill='violet red', outline='purple', tags='return_block') self.canvas.create_text(57, top_line + 8, anchor=NW, text='return', tags='return_block', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 52 + 1.3 * txt_len, top_line + 10, text_height, txt_len), fill='light pink', tags='return_block') top_line += text_height + 25 # block for variable assignment command txt_len = Font.measure(self.myFont, 'variable') txt2_len = Font.measure(self.myFontBold, '=') self.canvas.create_polygon(self.command_block_coords( 50, top_line, text_height + 15, 10 + txt_len + 15 + txt2_len + 15 + txt_len + 10), fill='violet red', outline='purple', tags='variable_block') self.canvas.create_polygon(self.inside_block_coords( 60, top_line + 10, text_height, txt_len + 5), fill='dodger blue', outline='steel blue', tags='variable_block') self.canvas.create_text(67, top_line + 10, anchor=NW, text="variable", tags='variable_block', font=self.myFont) self.canvas.create_text(67 + txt_len + 8, top_line + 10, anchor=NW, text="=", tags='variable_block', font=self.myFontBold) self.canvas.create_polygon(self.inside_block_coords( 67 + txt_len + 8 + txt2_len + 5, top_line + 10, text_height, txt_len), fill='light pink', tags='variable_block') top_line += text_height + 25 # block for if statement txt_len = Font.measure(self.myFont, 'if') self.canvas.create_polygon(self.control_block_coords( 50, top_line, text_height + 15, 10 + txt_len + 15 + 8 * txt_len + 15, 25)[0], fill='orange', outline='chocolate', tags='if_block') self.canvas.create_polygon(self.control_block_coords( 50, top_line, text_height + 15, 10 + txt_len + 10 + 9 * txt_len + 15, 25)[1], fill='orange', outline='chocolate', tags='if_block') self.canvas.create_text(67, top_line + 10, anchor=NW, text='if', tags='if_block', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 67 + 2 * txt_len, top_line + 9, text_height, 8 * txt_len), fill='peachpuff', tags='if_block') top_line += text_height + 75 # block for while statement txt_len = Font.measure(self.myFont, 'while') self.canvas.create_polygon(self.control_block_coords( 50, top_line, text_height + 15, 10 + txt_len + 15 + txt_len + 25, 25)[0], fill='orange', outline='chocolate', tags='while_block') self.canvas.create_polygon(self.control_block_coords( 50, top_line, text_height + 15, 10 + txt_len + 15 + txt_len + 25, 25)[1], fill='orange', outline='chocolate', tags='while_block') self.canvas.create_text(60, top_line + 10, anchor=NW, text='while', tags='while_block', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 65 + txt_len, top_line + 9, text_height, 55), fill='peachpuff', tags='while_block') top_line += text_height + 75 txt_len = Font.measure(self.myFont, 'print()') self.canvas.create_polygon(self.inside_block_coords( 50, top_line, text_height + 4, txt_len + 30 + txt_len), fill='limegreen', outline='green', tags='print') self.canvas.create_text(65, top_line + 2, anchor=NW, text="print(", tags='print', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 60 + txt_len, top_line + 2, text_height, txt_len), fill='lightgreen', tags='print') self.canvas.create_text(70 + 2 * txt_len, top_line + 2, anchor=NW, text=")", tags='print', font=self.myFont) top_line += text_height + 15 # complete print block self.canvas.create_polygon(self.command_block_coords( 50, top_line, 2 * text_height + 5, 135), fill='violet red', outline='purple', tags='print_complete') self.canvas.create_polygon(self.inside_block_coords( 55, top_line + 10, text_height + 4, txt_len + 30 + txt_len), fill='limegreen', outline='green', tags='print_complete') self.canvas.create_text(65, top_line + 12, anchor=NW, text="print(", tags='print_complete', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 60 + txt_len, top_line + 12, text_height, txt_len), fill='lightgreen', tags='print_complete') self.canvas.create_text(70 + 2 * txt_len, top_line + 12, anchor=NW, text=")", tags='print_complete', font=self.myFont) top_line += text_height + 25 # right side column # equals statement block top_line = 20 txt_len = Font.measure(self.myFontBold, ' == ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 1.2 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='equals') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='equals') self.canvas.create_text(312 + 1.2 * txt_len + 10, top_line + 2, anchor=NW, text="==", tags='equals', font=self.myFontBold) self.canvas.create_polygon(self.inside_block_coords( 310 + 1.2 * txt_len + txt_len + 10, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='equals') top_line += text_height + 15 # not equal statement block txt_len = Font.measure(self.myFontBold, ' != ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 1.5 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='not_equal') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 1.5 * txt_len), fill='sky blue', tags='not_equal') self.canvas.create_text(312 + 1.5 * txt_len + 10, top_line + 2, anchor=NW, text="!=", tags='not_equal', font=self.myFontBold) self.canvas.create_polygon(self.inside_block_coords( 310 + 1.5 * txt_len + txt_len + 10, top_line + 2, text_height, 1.5 * txt_len), fill='sky blue', tags='not_equal') top_line += text_height + 15 # grater than block txt_len = Font.measure(self.myFontBold, ' > ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 2 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='greater') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 2 * txt_len), fill='sky blue', tags='greater') self.canvas.create_text(312 + 2 * txt_len + 10, top_line + 2, anchor=NW, text=">", tags='greater', font=self.myFontBold) self.canvas.create_polygon(self.inside_block_coords( 310 + 2 * txt_len + txt_len + 10, top_line + 2, text_height, 2 * txt_len), fill='sky blue', tags='greater') top_line += text_height + 15 # smaller than block txt_len = Font.measure(self.myFontBold, ' < ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 2 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='smaller') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 2 * txt_len), fill='sky blue', tags='smaller') self.canvas.create_text(312 + 2 * txt_len + 10, top_line, anchor=NW, text="<", tags='smaller', font=self.myFontBold) self.canvas.create_polygon(self.inside_block_coords( 310 + 2 * txt_len + txt_len + 10, top_line + 2, text_height, 2 * txt_len), fill='sky blue', tags='smaller') top_line += text_height + 15 # grater or equal block txt_len = Font.measure(self.myFontBold, ' >= ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 1.2 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='greater_or_equal') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='greater_or_equal') self.canvas.create_text(312 + 1.2 * txt_len + 10, top_line, anchor=NW, text=">=", tags='greater_or_equal', font=self.myFontBold) self.canvas.create_polygon(self.inside_block_coords( 310 + 1.2 * txt_len + txt_len + 10, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='greater_or_equal') top_line += text_height + 15 # smaller or equal block txt_len = Font.measure(self.myFontBold, ' <= ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 1.2 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='smaller_or_equal') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='smaller_or_equal') self.canvas.create_text(312 + 1.2 * txt_len + 10, top_line, anchor=NW, text="<=", tags='smaller_or_equal', font=self.myFontBold) self.canvas.create_polygon(self.inside_block_coords( 310 + 1.2 * txt_len + txt_len + 10, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='smaller_or_equal') top_line += text_height + 15 # or block txt_len = Font.measure(self.myFontBold, ' or ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 1.7 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='or') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 1.7 * txt_len), fill='sky blue', tags='or') self.canvas.create_text(312 + 1.7 * txt_len + 10, top_line, anchor=NW, text="or", tags='or', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 310 + 1.7 * txt_len + txt_len + 10, top_line + 2, text_height, 1.7 * txt_len), fill='sky blue', tags='or') top_line += text_height + 15 # and block txt_len = Font.measure(self.myFont, 'and ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 15 + 2 * 1.2 * txt_len + txt_len + 15), fill='dodger blue', outline='steel blue', tags='and') self.canvas.create_polygon(self.inside_block_coords( 310, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='and') self.canvas.create_text(310 + 1.2 * txt_len + 12, top_line + 2, anchor=NW, text="and", tags='and', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 312 + 1.2 * txt_len + txt_len + 10, top_line + 2, text_height, 1.2 * txt_len), fill='sky blue', tags='and') top_line += text_height + 15 # not block txt_len = Font.measure(self.myFont, 'not ') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height + 4, 10 + txt_len + 10 + 1.6 * txt_len), fill='dodger blue', outline='steel blue', tags='not') self.canvas.create_text(312, top_line + 2, anchor=NW, text='not', tags='not', font=self.myFont) self.canvas.create_polygon(self.inside_block_coords( 312 + txt_len, top_line + 2, text_height, 1.6 * txt_len), fill='sky blue', tags='not') top_line += text_height + 15 # block for variable txt_len = Font.measure(self.myFont, 'variable') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height, txt_len + 20), fill='dodger blue', outline='steel blue', tags='variable') self.canvas.create_text(315, top_line, anchor=NW, text="variable", tags='variable', font=self.myFont) # None block txt_len = Font.measure(self.myFont, 'None') self.canvas.create_polygon(self.inside_block_coords( 400, top_line, text_height, txt_len + 20), fill='dodger blue', outline='steel blue', tags='none') self.canvas.create_text(415, top_line, anchor=NW, text="None", tags='none', font=self.myFont) top_line += text_height + 15 # block for creating number txt_len = Font.measure(self.myFont, 'number') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height, txt_len + 20), fill='dodger blue', outline='steel blue', tags='number') self.canvas.create_text(315, top_line, anchor=NW, text="number", tags='number', font=self.myFont) # True block txt_len = Font.measure(self.myFont, 'True') self.canvas.create_polygon(self.inside_block_coords( 400, top_line, text_height, txt_len + 20), fill='dodger blue', outline='steel blue', tags='true') self.canvas.create_text(415, top_line, anchor=NW, text="True", tags='true', font=self.myFont) top_line += text_height + 15 # block for creating string txt_len = Font.measure(self.myFont, 'string') self.canvas.create_polygon(self.inside_block_coords( 300, top_line, text_height, txt_len + 20), fill='dodger blue', outline='steel blue', tags='string') self.canvas.create_text(315, top_line, anchor=NW, text="string", tags='string', font=self.myFont) # False block txt_len = Font.measure(self.myFont, 'False') self.canvas.create_polygon(self.inside_block_coords( 400, top_line, text_height, txt_len + 25), fill='dodger blue', outline='steel blue', tags='false') self.canvas.create_text(415, top_line, anchor=NW, text="False", tags='false', font=self.myFont)
clue_rect_default_height = 75 x_pad = 20 # the amount to pad before starting the clue number x_num_pad = 5 # the amount to pad between the clue number and the clue y_pad = 5 # the amount to pad (top and bottom) between the end # of the clue box and the bounding box of the clue text clue_rect_top_y = 0 # zero to start. bottom_y + 1 in the loop for key, value in down_clues.items(): print(key, value) clue_number = key clue_text = value clue_text_length = rfont.measure(clue_text) clue_num_length = rfont.measure(clue_number) clue_text_height = rfont.metrics("linespace") # print("clue_text_length:", clue_text_length) # col_left_x, row_top_y, col_right_x, row_bot_y # clue_rect_bottom_y = clue_rect_top_y + clue_rect_default_height # clue_text_line_len = clue_right_x - (clue_left_x + (x_pad*2) + clue_num_length + x_num_pad + clue_top_y + y_pad) # cr = list_canvas.create_rectangle(clue_left_x, clue_top_y, clue_right_x, clue_bottom_y, fill="light grey") # cn = list_canvas.create_text(clue_left_x + x_pad, clue_top_y + y_pad, text=clue_number, font=bfont, anchor = "nw") # ct = list_canvas.create_text(clue_left_x + x_pad + clue_num_length + x_num_pad, clue_top_y + y_pad, text=clue_text, font=rfont, anchor = "nw", width=clue_text_line_len) # bb = list_canvas.bbox(ct) # x0, y0, x1, y1 = list_canvas.coords(cr) # y1 = bb[3] + y_pad # list_canvas.coords(cr, x0, y0, x1, y1) # clue_rect_top_y = clue_rect_bottom_y + 1 # if key == 1: break
alldata = view_data() for row in alldata: print(row[1:]) vocablist.insert('', 'end', values=row[1:]) header = ['Vocab JP', 'Vocab Thai', 'Description', 'Image'] vocablist = ttk.Treeview(AllVocab, columns=header, show='headings', height=10) vocablist.pack(fill=BOTH) for hd in header: vocablist.heading(hd, text=hd) font = Font(family='Angsana New', size=20) font.metrics() fontheight = font.metrics()['linespace'] style = ttk.Style() style.configure("Treeview.Heading", font=('Arial', 15)) style.configure("Treeview", font=font, rowheight=fontheight) BUpdate = ttk.Button(AllVocab, text='Update', command=update_data) BUpdate.pack() def del_item(): q = messagebox.askyesno('Confirm', 'คุณต้องการลบใช่หรือไม่?') print(q)
class SearchBox(Frame): '''A Treeview widget on top, Entry on bottom, using queues for interaction with outside functions''' def __init__(self, parent=None, db=DB, fdict={}): Frame.__init__(self, parent) self.Tests = Tests self.db = db self.fdict = dbm.open(self.db, 'c') #self.fdict['**']='' self.db_update = False #will hold the query to be processed self.query = None self.drives = self.get_drives() self.start_func = StartFunc( ) #platform dependent double-click response self.results = iter( ()) #initiating query results as an empty generator self.total_width = self.winfo_screenwidth( ) #to adjust column widths relative to screen size #Remember scorch mode self.scorch = False self.encoding = ENCODING #for scorch mode #self.keylist=self.fdict.keys() self.keylist_index = 0 self.keylist_counter = 0 #keystroke indicator self.counter = 0 #queues for passing search queries and results self.query_queue = Queue() self.result_queue = Queue() #for usage by db generating function self.dbinit_queue = Queue() #--Search Results panel at top self.panel = Treeview(columns=('Path', 'Size', 'Date')) self.panel.pack(expand=True, fill='both') self.panel.heading('#0', text='Name') self.panel.heading(0, text='Path') self.panel.heading(1, text='Size') self.panel.heading(2, text='Date Modified') #--Starting geometry of the search panel try: #start maximized self.panel.master.attributes('-zoomed', 1) except: self.panel.master.state('zoomed') self.panel_width = self.panel.winfo_width() #Name - 2/5-----Path - 2/5-----Size -1/25-----Date -4/25 # '#0' is the 'Name' column self.panel.column('#0', width=int(self.total_width * 0.4)) self.panel.column('Path', width=int(self.total_width * 0.4)) self.panel.column('Size', width=int(self.total_width * 0.06)) self.panel.column('Date', width=int(self.total_width * 0.14)) #--Panel font, style self.font = Font(family='Helvetica', size=11) '''TkDefaultFont - {'family': 'Segoe UI', 'overstrike': 0, 'size': 9, 'slant': 'roman', 'underline': 0, 'weight': normal'}''' self.style = Style() #linespace - adjust the row height to the font, doesn't happen on its own in tkinter self.style.configure('SearchBox.Treeview', font=self.font, rowheight=self.font.metrics('linespace')) self.panel.config(style='SearchBox.Treeview') #alternating background colors self.panel.tag_configure('color1', background='gray85') #, foreground='white') self.panel.tag_configure('color2', background='gray90') #, foreground='white') #'dark sea green', 'wheat3', 'black' #--App title and icon, currently transparent self.panel.master.title('Jiffy') self.icon = PhotoImage(height=16, width=16) self.icon.blank() #transparent icon, works on all but Py35/Win #loading the transparent icon. black on Py35/Win try: self.master.wm_iconphoto('True', self.icon) except: #For some reason this jammed Python 3.5 with Tk 8.6 on Windows self.tk.call('wm', 'iconphoto', self.master._w, self.icon) #--A string variable to monitor input to the Entry box self.entry_var = StringVar() #self.entry_var.set('Type to search. [F5 - Refresh Database]. [F12 - Scorch Mode]') # [Ctrl-O - Options]. [Ctrl-I - Info] self.entry_var.trace('w', self.update_query) #--Entry line on the bottom self.entry_box = Entry(textvariable=self.entry_var) #keep it as a single line on all window sizes self.entry_box.pack(side='bottom', fill='x') #--Widget Bindings #self.master.bind('<Ctrl-Z>', self.quit) #alternative to Alt-F4 self.master.bind('<Control-equal>', self.scaleup) self.master.bind('<Control-Button-4>', self.scaleup) self.master.bind('<Control-minus>', self.scaledown) self.master.bind('<Control-Button-5>', self.scaledown) self.master.bind('<Control-MouseWheel>', self.scale_mouse) self.panel.bind('<Double-1>', self.doubleclick) self.panel.bind('<Return>', self.doubleclick) #allow scrolling and typing without switching focus self.entry_box.bind('<MouseWheel>', self.scroll_from_entry) self.entry_box.bind('<Button-4>', self.scroll_from_entry) self.entry_box.bind('<Button-5>', self.scroll_from_entry) self.master.bind('<F5>', self.make_database) #self.master.bind('<F12>', self.scorch_mode) #--Starting up with entry box active self.entry_box.focus_set() #--Generating a starting message based on existence of a database if not self.fdict: self.panel.insert('', 'end', text='No cache database found', values=('Hit F5 to generate database', )) else: self.panel.insert( '', 'end', text='Type to search. [F5 - Refresh Database]', values=('[Ctrl - +/-/MouseWheel - Adjust font size]', )) # [Ctrl-O - Options]. [Ctrl-I - Info] #self.panel.insert('', 'end', text='Scorch Mode is faster but uses more memory', values=('Loads the entire database into RAM',)) self.update_searchbox() #Initializing the query managing function in a separate thread (upgrade to pool?) thread.start_new_thread( TMakeSearch, (self.fdict, self.query_queue, self.result_queue)) ##GUI functionality-------------------------------------------------------- #O change to initiation from parameter to SearchBox for more modularity def get_drives(event): return GetDrives() def scroll_from_entry(self, event): '''Scroll results without deactivating entry box, called from entry_box''' self.panel.yview_scroll(1, 'units') def scaleup(self, event): '''The Treeview widget won't auto-adjust the row height, so requires manual resetting upon font changing''' self.font['size'] += 1 self.style.configure('SearchBox.Treeview', rowheight=self.font.metrics('linespace') + 1) def scaledown(self, event): self.font['size'] -= 1 self.style.configure('SearchBox.Treeview', rowheight=self.font.metrics('linespace') + 1) def scale_mouse(self, event): self.scaleup(event) if event.delta > 0 else self.scaledown(event) def doubleclick(self, event): '''Invoke default app on double-click or Enter''' #getting file/folder name and removing '[' and ']' for folders selection = self.panel.item(self.panel.focus()) filename = selection['text'] #remove folder indicating square brackets if filename[0] == '[': filename = filename[1:-2] #SPLIT_TOKEN='\\' if 'win' in sys.platform else '/' full_path = selection['values'][0] + SPLIT_TOKEN + filename self.start_func(full_path) def quit(self, event): '''Currently Alt-F4 exits program, in case I want to add more shortcuts. Also add thread closing management here''' self.master.destroy() ##Cheese: update_database()->is_sb_generated(), trace_results(), update_query() def make_database(self, event): '''Using a thread to generate the dictionary to prevent GUI freezing''' #* dbm might not be thread safe - best might be to restart TMakeSearch self.gtime = time() # for testing self.entry_var.set('Updating Database') self.entry_box.icursor('end') #Resulting dicitionay will be passed via dbinint_queue thread.start_new_thread(RecursiveCreateDict, (self.drives, self.dbinit_queue, self.fdict)) #Wait for the dictionary to be generated self.is_db_generated() def is_db_generated(self): '''Update database if available or sleep and try again''' if not self.dbinit_queue.empty(): #A new dictionary was passed #retrieving new dict self.newdict, self.unsearched = self.dbinit_queue.get() #Messaging TMakeSearch to stop querying the dictionary self.db_update = True self.query_queue.put(None) sleep(0.11) #TMakeSearch takes 0.1s naps. Check further ''' if whichdb(self.db) == ('dbhash'): '''For dumbdbm, this jams the app, as does manual updating. it's not dumb, it's just not worthy''' self.fdict.update(self.newdict) else: for key in self.newdict: self.fdict[key] = self.newdict[key] print('fdict is created') self.db_update = False #save new database self.fdict.sync() print('fdict synced') #Open a new TMakeSearch with the updated database #thread.start_new_thread(TMakeSearch, (self.fdict, self.query_queue, self.result_queue)) #Cleaning up self.newdict.clear() self.newdict = None self.gtime = time() - self.gtime #to read about {}.format #also, a label may be simpler self.entry_var.set('Database generation time- ' + str(self.gtime) + 's. Type to search. [F5 - Refresh Database]') #Pass a signal to close TMakeSearch, then reopen it self.query_queue.put(True) thread.start_new_thread( TMakeSearch, (self.fdict, self.query_queue, self.result_queue)) self.entry_box.icursor(0) #self.loading.destroy() self.panel.delete(*self.panel.get_children()) self.panel.insert( '', 0, text='Scorch Mode is faster but uses more memory', values=('Loads database into RAM', )) #self.keylist=fdict.keys() #for scorch mode self.counter = 0 #self.IS_1ST_PRESS=True #for testing #print time()-self.start #print self.dict_size() else: self.after(100, self.is_db_generated) def update_searchbox(self): '''Update GUI with new result batches ''' self.even = True #for splitting size and date from the keys self.separator = ' * '.encode(self.encoding) while not self.result_queue.empty(): qresult = self.result_queue.get() #print ('is_batch_recieved:', qresult) #if qcounter==self.counter: #currently assuming results will arrive by querying order #break try: #if nothing in queue this will raise an error, saves a preemptive if clause self.results, self.is_new = qresult if Tests.is_batch_recieved: print('is_batch_recieved:', self.results) except: pass #no new results if Tests.is_batch_recieved: print('is_batch_recieved: no new results') else: #if self.panel.get_children()!=(): #results for a newer query, erase old results if self.is_new: self.panel.delete(*self.panel.get_children()) for key in self.results: try: name, size, date = key.decode(self.encoding).split(u'*') #name, size, date=key.split(self.separator) if Tests.is_result_parsed: print(name) except: if Tests.is_result_parsed: print('parsing issue with', key) else: path = self.fdict[key].decode(self.encoding) '''if 'win' in sys.platform and top[0] is u'/': top=u'C:\\'+top[1:] ''' color = 'color1' if self.even else 'color2' self.even = not self.even self.panel.insert('', 'end', text=name, values=(path, size, date), tags=(color, )) self.after(60, self.update_searchbox) def update_query(self, x=None, y=None, z=None): '''Invoked by StringVar().trace() method, which passes 3 arguments that are honorably ditched ''' #Deactivate while switching dictionaries if self.db_update: pass #Cleaning up for 1st keystroke or after a message in the Entry box if not self.counter: '''Entry box needs to be cleaned, the new char put in and the cursor placed after it''' #get&set the 1st search char. user may've changed cursor location b4 typing self.entry_var.set( self.entry_var.get()[self.entry_box.index(INSERT) - 1]) #move cursor after the first char self.entry_box.icursor(1) #counter goes up either way self.counter += 1 self.query = self.entry_var.get() self.query_queue.put(self.query) if Tests.is_query_sent: print(self.query) print(self.counter) ##Not in use ---------------------------------------------------------------- def trace_query(self): '''If I opt for periodically checking the StringVar''' if self.counter: #when counter=0 there's a message/notification in the entry box if self.query != self.entry_var.get(): self.query = self.entry_var.get() self.query_queue.put(self.query) self.after(100, self.trace_query) def trace_and_update(self): ''' In-GUI implementation of query searching, uses a list for iterating over the keys''' '''works smoother when results are generated quickly, but with sparse results GUI becomes unresponsive for short whiles. Relevant only if results are guaranteed to be generated swiftly''' #print self.query #if new query, resetting search parameters and GUI if self.query != self.entry_var.get(): self.keylist_counter = 0 self.query = self.entry_var.get() self.search_list = self.query.lower().split() self.panel.delete(*self.panel.get_children()) self.insertion_counter = 0 self.keylist_index = self.keylist_counter for key in self.keylist[self.keylist_index:]: filename = key.split('*')[0].lower() #If a match, parse and add to the Treeview if self.all(token in filename for token in self.search_list): name, size, date = key.split('*') self.panel.insert('', 'end', text=name, values=(self.fdict[key], size, date)) self.insertion_counter += 1 self.keylist_counter += 1 if self.insertion_counter >= self.INSERTIONS_PER_CYCLE: #50 ##or not dict_counter: break #nap time self.after(60, self.trace_and_update)
statusBar_lab.pack(side='left', padx=2, expand='yes', fill='both') if root._windowingsystem != 'aqua': foo = ttk.Sizegrip(statusBar) foo.pack(side='left', padx=2) statusBar.pack(side='bottom', fill='x', pady=2) ##set textheight 30 ##catch { ## set textheight [expr { ## ([winfo screenheight .] * 0.7) / ## [font metrics mainFont -displayof . -linespace] ## }] ##} textheight = 30 try: textheight = root.winfo_screenheight() * 0.7 / mainFont.metrics('linespace', displayof='.') except: pass ##ttk::frame .textFrame ##scrollbar .s -orient vertical -command {.t yview} -takefocus 1 ##pack .s -in .textFrame -side right -fill y ##text .t -yscrollcommand {.s set} -wrap word -width 70 -height $textheight \ ## -font mainFont -setgrid 1 -highlightthickness 0 \ ## -padx 4 -pady 2 -takefocus 0 ##pack .t -in .textFrame -expand y -fill both -padx 1 ##pack .textFrame -expand yes -fill both ##if {[tk windowingsystem] eq "aqua"} { ## pack configure .statusBar.lab -padx {10 18} -pady {4 6} ## pack configure .statusBar -pady 0 ## .t configure -padx 10 -pady 0
class CanvasElem(object): '''CanvasElem keeps track of a canvas element, possibly with text.''' STD_FONTS = [ {'family':'Bookman Old Style', 'size':14},\ {'family':'Century', 'size':14}, \ {'family':'Courier', 'size':14}] #Changes the script-name of the gate to the display-name of the gate GATE_MASKS = {'h':'H', 'x':'X', 'y':'Y', 'z':'Z', 'rx':'Rx', 'ry':'Ry',\ 'rz':'Rz', 's':'S', 'ph':'S','t':'T', 'tdag':'T^', 'measure':'M', 'prepz':'|0>'} #All the gates that have a special drawing style SPECIAL_GATES = ('cnot', 'cx', 'toffoli', 'swap', 'cphase', 'cz', 'cr','c-x','c-z','class_cx','class_cz') SPECIAL_NODES = ('circ','oplus','cross') #Determine the radius of the associated nodes of the special gates RADII = {'circ':5, 'oplus': 9, 'cross':6 } #Determine the width of the boxes around the gates BORDER_WIDTH = 3 #Determine the margins around the gates. MARGINS = (0,0) #(5,5) def __init__(self, canvas, gate=None, aspect = (-1,-1), bbox=None, font_dict=None,\ special_node = None, draw_rect = True): self.canvas = canvas self.aspect = aspect self.bbox = bbox #Keep track of the actual drawing coordinates self.draw_x = -1 self.draw_y = -1 self.draw_w = -1 self.draw_h = -1 self.text_x = -1 self.text_y = -1 #Keep track of the min dimensions of the bbox such that the contents can be displayed correctly self.min_w = -1 self.min_h = -1 #Keep track of the attachment points to which other elements can attach themselves. self.attachments = {'left':-1, 'right':-1, 'top':-1, 'bottom':-1} #Keep track of the text within the rectangle self.text = None #Set the font self.font = None if font_dict: self.font = Font(family=font_dict['family'], size=font_dict['size']) else: for font_dict in self.STD_FONTS: try: self.font = Font(family=font_dict['family'],size=font_dict['size']) if self.font: break except Exception as e: pass else: raise ValueError(f'{self.__str__()} cannot produce any font, none worked!') #Keep track of the canvas elements self.rect_canvas = None self.text_canvas = None self.special_node = special_node self.specials_canvas = [] #Keep track of whether we need to draw the rectangle self.draw_rect = draw_rect #Update the current gate, and the associated text self.set_gate(gate) def find_min_size(self): '''Finds the minimum size of the rectangle needed to contain the text''' if self.special_node: #self.min_w = max(2 * self.RADIUS, self.font.measure('x'), self.font.measure('o')) #self.min_h = max(2 * self.RADIUS, self.font.metrics('linespace')) self.min_w = self.min_h = 2 * self.RADII[self.special_node] #If we are an element with actual text inside, compute how large the text is elif self.text: min_w = self.font.measure(self.text) + self.draw_rect*self.BORDER_WIDTH*2 min_h = self.font.metrics('linespace') + self.draw_rect*self.BORDER_WIDTH*2 #Take into account the aspect ratio in self.aspect if not -1 in self.aspect: #We must have w/h = aspect[0]/aspect[1] => w = h * aspect[0]/aspect[1] #Either stretch w, or stretch h if min_h * self.aspect[0]/self.aspect[1] > min_w: self.min_w = int( min_h * self.aspect[0]/self.aspect[1] ) self.min_h = int(min_h) else: self.min_w = int(min_w) self.min_h = int( min_w * self.aspect[1]/self.aspect[0] ) else: self.min_w = int(min_w) self.min_h = int(min_h) def set_bbox(self,bbox): self.bbox = bbox def set_gate(self,gate): self.gate = gate if self.gate in self.GATE_MASKS.keys(): self.text = self.GATE_MASKS[self.gate] #If the gate is a special gate, suppress the text. Otherwise, set it. elif self.gate in self.SPECIAL_GATES: self.text = None else: self.text = self.gate self.find_min_size() def find_draw_coords(self): '''Finds the coordinates to draw with, and sets up the attachment points''' if self.bbox is None: raise ValueError(f'{self.__str__()} cannot find draw coords because bbox is None!') #Do NOT include margins if we are building a special node if self.special_node: want_width = self.min_w want_height = self.min_h else: want_width = self.min_w + 2 * self.MARGINS[0] want_height = self.min_h + 2 * self.MARGINS[1] self.draw_w = want_width if want_width < self.bbox['w'] else self.bbox['w'] self.draw_h = want_height if want_height < self.bbox['h'] else self.bbox['h'] self.draw_x = int( self.bbox['x'] + (self.bbox['w'] - self.draw_w)/2 ) self.draw_y = int( self.bbox['y'] + (self.bbox['h'] - self.draw_h)/2 ) self.text_x = int( self.draw_x + self.draw_w/2 ) self.text_y = int( self.draw_y + self.draw_h/2 ) self.attachments['left'] = self.draw_x self.attachments['right'] = self.draw_x + self.draw_w self.attachments['top'] = self.draw_y self.attachments['bottom'] = self.draw_y + self.draw_h def draw(self): '''Draws the element on the canvas''' #First, find the coords at which we should draw. self.find_draw_coords() #If we are a normal node: if self.special_node is None: #If we should draw a rectangle: if self.draw_rect: self.rect_canvas = self.canvas.create_rectangle(self.draw_x,self.draw_y,\ self.draw_x+self.draw_w,self.draw_y+self.draw_h,width=self.BORDER_WIDTH) #If we are a measurement device if self.gate == 'measure': self.specials_canvas += self.draw_measurement() #If we have text: elif self.text: self.text_canvas = self.canvas.create_text(self.text_x,self.text_y,font=self.font, justify=tk.CENTER,\ text=self.text) else: #We are special: we need to draw either a circ, an oplus or a cross def node(xy, r, circ=False, fill=False, plus=False, cross=False): out = [] if circ: out.append(self.canvas.create_oval( xy[0]-r, xy[1]-r, xy[0]+r, xy[1]+r, fill='black' if fill else '', width=1.5) ) if plus: out.append(self.canvas.create_line( xy[0], xy[1]-r, xy[0], xy[1]+r, width=2 ) ) out.append(self.canvas.create_line( xy[0]-r, xy[1], xy[0]+r, xy[1], width=2 ) ) if cross: out.append(self.canvas.create_line( xy[0]-r, xy[1]-r, xy[0]+r, xy[1]+r, width=2.5 ) ) out.append(self.canvas.create_line( xy[0]-r, xy[1]+r, xy[0]+r, xy[1]-r, width=2.5 ) ) return out mid_x = int( self.bbox['x'] + self.bbox['w']/2 ) mid_y = int( self.bbox['y'] + self.bbox['h']/2 ) if self.special_node == 'circ': self.specials_canvas += node((mid_x,mid_y), self.RADII['circ'], circ=True, fill=True ) elif self.special_node == 'oplus': self.specials_canvas += node((mid_x,mid_y), self.RADII['oplus'], circ=True, plus=True) else: self.specials_canvas += node((mid_x,mid_y), self.RADII['cross'], cross=True) def draw_measurement(self): '''Draws a measurement device''' mid_x = int(self.draw_x + self.draw_w/2) mid_y = int(self.draw_y + 3*self.draw_h/5) radius = int( (self.draw_w/2) * 7/10 ) arc = self.canvas.create_arc( mid_x-radius, mid_y-radius, mid_x+radius, mid_y+radius,\ start=0, extent=180, width=2, style=tk.ARC ) end_x = int(self.draw_x + self.draw_w * 8.5/10 ) end_y = int(self.draw_y + self.draw_h * 1.5/10 ) arrow = self.canvas.create_line(mid_x, mid_y, end_x, end_y, arrow=tk.LAST, width=2 ) return arc, arrow def __str__(self): return f'R.D. DRAW(x={self.draw_x},y={self.draw_y},w={self.draw_w},h={self.draw_h},text={self.text})' def __repr__(self): return self.__str__()
class PitchDisplay: def __init__(self, mainWindow, threshold=10): self.frame = mainWindow.right_frame self.mainWindow = mainWindow self.threshold = threshold self.font = Font(size=20) self.pitchOffset = self.font.metrics('linespace') * 0.75 self._pitchValue = '---' # default display self._centsValue = -50 self._hertzValue = 0 self._octaveValue = '' self._span = 75 # Size of tuner arc in degrees, starting at vertical self.canvas = Canvas(self.frame, bg=Colors.background, bd=0, highlightthickness=0) self.canvas.pack(fill=BOTH, expand=True) self.canvas.bind("<Configure>", self.configure) self.top_frame = Frame(self.canvas, height=35, bg=Colors.background, bd=0, highlightthickness=0) #self.top_frame.pack_propagate(0) self.top_frame.pack(side='top', fill=tk.X, anchor=tk.N) self.rec_frame = Frame(self.top_frame, width=80, height=35, bg=Colors.background, bd=0, highlightthickness=0) self.rec_frame.pack_propagate(0) self.rec_frame.pack(anchor='w', side=tk.LEFT) self.light = IndicatorLight(self.rec_frame, 35) self.light.pack(anchor='w', side='left') self.time_label = Label(self.rec_frame, text='00:00', anchor='e', justify=RIGHT, fg=Colors.text, bg=Colors.background) self.time_label.pack(side='right') self.score_label = RoundedLabel(self.top_frame, "Score: 0%", Colors.score_label, Colors.background, height=35, width=110) self.score_label.pack(side='right') #self.score_label.set_text("adfasdf") self.showsHertz = BooleanVar() style = ttk.Style() style.configure("Pitch.TCheckbutton", background=Colors.background, foreground=Colors.text) style.map('Pitch.TCheckbutton', foreground=[('active', Colors.text)], background=[('pressed', '!focus', '#232323'), ('active', Colors.aux)]) c = ttk.Checkbutton(self.canvas, text="Show Hertz", variable=self.showsHertz, takefocus=False, command=self.display_default_gui, style="Pitch.TCheckbutton") c.pack(anchor='e', side='bottom') self._last_time = 0 self._clearing = False self.display_default_gui() def pause(self): self.light.stop() self.canvas.itemconfig(self.help_text, text='Press \'space\' to accept audio input') self._clearing = True def resume(self): self.light.start_flashing() self.canvas.itemconfig(self.help_text, text='Press \'space\' to pause audio input') def cents_to_angle(self, cents): return cents / 50 * self._span def configure(self, event): self.display_default_gui() def display_score(self, score): self.score_label.set_text(f"Score: {round(score)}%") def display_current_gui(self): pitch_and_octave = self._pitchValue + self._octaveValue self.canvas.itemconfig(self.current_pitch_display, text=pitch_and_octave) if self.showsHertz.get(): self.canvas.itemconfig(self.hertzDisplay, text=self._hertzValue) self.update_line(self._centsValue) if not self._clearing and abs(self._centsValue) <= self.threshold: self.canvas.itemconfig(self.green_arc, fill=Colors.green) else: self.canvas.itemconfig(self.green_arc, fill="#ccffbf") def display_default_gui(self): self.canvas.delete("all") self.width = self.frame.winfo_width() self.height = self.frame.winfo_height() min_dimension = min(self.width, self.height) self.radius = 0.4 * min_dimension self.centerX = self.width / 2 self.centerY = self.height / 2 + 15 self.current_pitch_display = self.canvas.create_text(self.centerX, self.centerY + self.pitchOffset, font=self.font, text='---', fill=Colors.text) if self.showsHertz.get(): self.hertzDisplay = self.canvas.create_text(self.centerX, self.centerY + 2 * self.pitchOffset, font="Ubuntu 14", text='', fill=Colors.text) self.help_text = self.canvas.create_text( self.width / 2, self.height - 35, text='Press \'space\' to accept audio input', fill=Colors.text) x0 = self.centerX - self.radius y0 = self.centerY - self.radius x1 = self.centerX + self.radius y1 = self.centerY + self.radius # rect = self.canvas.create_rectangle(x0, y0, x1, y1) rStart = 90 - self._span rSpan = 2 * self._span yStart = 90 - self.cents_to_angle( self.mainWindow.controller.yellow_threshold) ySpan = 2 * (90 - yStart) gStart = 90 - self.cents_to_angle(self.threshold) gSpan = 2 * self.cents_to_angle(self.threshold) self.red_arc = self.canvas.create_arc(x0, y0, x1, y1) self.canvas.itemconfig(self.red_arc, start=rStart, fill="#ffbfbf", extent=rSpan, outline='') self.yellow_arc = self.canvas.create_arc(x0, y0, x1, y1) self.canvas.itemconfig(self.yellow_arc, start=yStart, fill="#fffeb0", extent=ySpan, outline='') self.green_arc = self.canvas.create_arc(x0, y0, x1, y1) self.canvas.itemconfig(self.green_arc, start=gStart, fill="#ccffbf", extent=gSpan, outline='') self.line = self.canvas.create_line(0, 0, 0, 0, fill=Colors.tuner_needle, width=4, arrow=FIRST, arrowshape=(self.radius, 10, 5)) self.update_line(-50) def update_line(self, cents): deg = self.cents_to_angle(cents) theta = radians(deg) dx = self.radius * sin(theta) dy = self.radius * cos(theta) self.canvas.coords(self.line, self.centerX, self.centerY, self.centerX + dx, self.centerY - dy) def set_threshold(self, thresh): self.threshold = thresh self.display_default_gui() def update_pitch(self, value): # event as parameter self._pitchValue = value def update_hertz(self, value): self._hertzValue = value def update_cents(self, value): self._centsValue = value def update_octave(self, value): self._octaveValue = value def set_time(self, total_seconds): minutes = total_seconds // 60 seconds = total_seconds % 60 display_string = '{:02}:{:02}'.format(minutes, seconds) self.time_label.config(text=display_string) def update_data(self, hz, data): #TODO: remove data parameter? if hz != 0: self._clearing = False midi = hz_to_midi(hz) if data.midi_range[0] <= midi <= data.midi_range[1]: pitch_class = midi_to_pitch_class(midi) desired_hz = closest_in_tune_frequency(hz) cent = cents(desired_hz, hz) name = data.key_signature.get_display_for(pitch_class) self.update_cents(cent) self.update_hertz(f"{round(hz)} Hz") self.update_octave(f"{get_octave(midi)}") self.update_pitch(name) self.display_current_gui() self._last_time = time.time() else: self.clear() self.set_time(data.timer.get()) # TODO move timer def clear(self): self._clearing = True if self._centsValue != -50 and time.time() - self._last_time > 1.5: self.update_cents(max(-50, self._centsValue - 3)) self.update_pitch('---') self.update_hertz('') self.update_octave('') self.display_current_gui() elif self._centsValue == -50: # clearing animation has finished self._clearing = False def needs_update(self): return self._clearing
class TextEditor(Frame, CaretObserver, TextObserver): def update_text(self, text: str): self.update_status_bar() self.redisplay() def update_caret_location(self, loc: Location): self.update_status_bar() self.redisplay() def __init__(self, text_editor_model: TextEditorModel): super().__init__() # TODO extract self._minimalWidth = 555 self._minimalHeight = 700 self._xMargin = 10 self._numberSpacing = 50 self._yMargin = 20 self._font = Font(size=12, family="Purisa") self._canvasTextColor = flat_colors.FlatUiColors.CLOUDS self._canvasBackgroundColor = flat_colors.FlatUiColors.PETER_RIVER self._canvasHighlightColor = flat_colors.FlatUiColors.EMERALD # TODO extract self._caretShownDelay = 300 self._caretHiddenDelay = 5000 self._caretColor = flat_colors.FlatUiColors.MIDNIGHT_BLUE self.__processed_selection = [] self._model = text_editor_model self._model._caretObservers.add(self) # using an anonymous class: # self._model._observers.add(type("Pero", (CaretObserver, object), # {"updateCaretLocation": lambda _, __: self.redisplay()})()) self._clipboardStack = ClipboardStack() self._pluginsDirectory = "./plugins/" self._pluginsModuleName = "plugins" self._plugins = self._load_plugins() self.init_menubar() self.init_toolbar() self.init_statusbar() self.init_canvas() def _load_plugins(self): plugins = [] for file in os.listdir(self._pluginsDirectory): if file.endswith(".py"): print(file) # try: spec = importlib.util.spec_from_file_location( self._pluginsModuleName, self._pluginsDirectory + file) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) for name, obj in inspect.getmembers( module, lambda x: inspect.isclass(x) and issubclass(x, Plugin) and not inspect.isabstract(x)): plugins.append(obj()) # except: # pass return plugins def init_menubar(self): menubar = Menu(self.master) self.master.config(menu=menubar) file_menu = Menu(menubar) file_menu.add_command(label="Open", underline=0, command=self.open_file) file_menu.add_command(label="Save", underline=0, command=self.save_file) file_menu.add_command(label="Exit", underline=0, command=self.on_exit) menubar.add_cascade(label="File", underline=0, menu=file_menu) edit_menu = Menu(menubar) edit_menu.add_command(label="Undo", command=self.undo, state=DISABLED) UndoManager.get_instance().attach_undo_observer( type("UndoManagerStackObserver1", (UndoManagerStackObserver, object), {"stack_empty": lambda _, e: edit_menu.entryconfig(1, state=DISABLED if e else NORMAL)})()) edit_menu.add_command(label="Redo", command=self.redo, state=DISABLED) UndoManager.get_instance().attach_redo_observer( type("UndoManagerStackObserver2", (UndoManagerStackObserver, object), {"stack_empty": lambda _, e: edit_menu.entryconfig(2, state=DISABLED if e else NORMAL)})()) edit_menu.add_command(label="Cut", command=self.cut, state=DISABLED) self._model.attach_selection_observer( type("SelectionObserverCut", (SelectionObserver, object), {"update_selection": lambda _, e: edit_menu.entryconfig(3, state=DISABLED if not e else NORMAL)})()) edit_menu.add_command(label="Copy", command=self.copy, state=DISABLED) self._model.attach_selection_observer( type("SelectionObserverCopy", (SelectionObserver, object), {"update_selection": lambda _, e: edit_menu.entryconfig(4, state=DISABLED if not e else NORMAL)})()) edit_menu.add_command(label="Paste", command=self.paste, state=DISABLED) self._clipboardStack.attach_clipboard_observer( type("ClipboardObserverPaste", (ClipboardObserver, object), {"update_clipboard": lambda _, e: edit_menu.entryconfig(5, state=DISABLED if not e else NORMAL)})()) edit_menu.add_command(label="Paste and Take", command=self.paste_and_take) self._clipboardStack.attach_clipboard_observer( type("ClipboardObserverPasteAndTake", (ClipboardObserver, object), {"update_clipboard": lambda _, e: edit_menu.entryconfig(6, state=DISABLED if not e else NORMAL)})()) edit_menu.add_command(label="Delete selection", command=self.delete_selection, state=DISABLED) self._model.attach_selection_observer( type("SelectionObserverDelete", (SelectionObserver, object), {"update_selection": lambda _, e: edit_menu.entryconfig(7, state=DISABLED if not e else NORMAL)})()) edit_menu.add_command(label="Clear document", command=self.clear_document) menubar.add_cascade(label="Edit", underline=0, menu=edit_menu) move_menu = Menu(menubar) move_menu.add_command(label="Caret to document start", command=self.caret_to_start) move_menu.add_command(label="Caret to document end", command=self.caret_to_end) menubar.add_cascade(label="Move", underline=0, menu=move_menu) plugins_menu = Menu(menubar) for plugin in self._plugins: plugins_menu.add_command( label=plugin.get_name(), command=lambda p=plugin: p.execute(self._model, UndoManager.get_instance(), self._clipboardStack)) menubar.add_cascade(label="Plugins", underline=0, menu=plugins_menu) def init_toolbar(self): toolbar = Frame(self.master) undo = Button(toolbar, text="Undo", command=self.undo, state=DISABLED) undo.pack(side=LEFT, padx=2, pady=2) UndoManager.get_instance().attach_undo_observer( type("UndoManagerStackObserver3", (UndoManagerStackObserver, object), {"stack_empty": lambda _, e: undo.config(state=DISABLED if e else NORMAL)})()) redo = Button(toolbar, text="Redo", command=self.redo, state=DISABLED) redo.pack(side=LEFT, padx=2, pady=2) UndoManager.get_instance().attach_redo_observer( type("UndoManagerStackObserver4", (UndoManagerStackObserver, object), {"stack_empty": lambda _, e: redo.config(state=DISABLED if e else NORMAL)})()) copy = Button(toolbar, text="Copy", command=self.copy, state=DISABLED) copy.pack(side=LEFT, padx=2, pady=2) self._model.attach_selection_observer( type("SelectionObserverCopyTool", (SelectionObserver, object), {"update_selection": lambda _, e: copy.config(state=DISABLED if not e else NORMAL)})()) cut = Button(toolbar, text="Cut", command=self.cut, state=DISABLED) cut.pack(side=LEFT, padx=2, pady=2) self._model.attach_selection_observer( type("SelectionObserverCutTool", (SelectionObserver, object), {"update_selection": lambda _, e: cut.config(state=DISABLED if not e else NORMAL)})()) paste = Button(toolbar, text="Paste", command=self.cut, state=DISABLED) paste.pack(side=LEFT, padx=2, pady=2) self._clipboardStack.attach_clipboard_observer( type("ClipboardObserverPasteTool", (ClipboardObserver, object), {"update_clipboard": lambda _, e: paste.config(state=DISABLED if not e else NORMAL)})()) toolbar.pack(side=TOP, fill=X) def init_statusbar(self): self._statusbar = Label(self.master, bd=1, relief=SUNKEN, padx=6, pady=4, anchor=E) self._statusbar.pack(side=BOTTOM, fill=X) self.update_status_bar() def init_canvas(self): self.master.title("Lab 3 example") self.pack(fill=BOTH, expand=1) self._display_caret = True self._selecting_active = False self._canvas = Canvas(self, bg=self._canvasBackgroundColor, scrollregion=(0, 0, self._minimalWidth, self._minimalHeight)) self._vbar = Scrollbar(self, orient=VERTICAL) self._vbar.pack(side=RIGHT, fill=Y) self._vbar.config(command=self._canvas.yview) self._hbar = Scrollbar(self, orient=HORIZONTAL) self._hbar.pack(side=BOTTOM, fill=X) self._hbar.config(command=self._canvas.xview) self._canvas.config(xscrollcommand=self._hbar.set, yscrollcommand=self._vbar.set) self._canvas.pack(fill=BOTH, expand=1) self.redisplay() self._canvas.bind_all("<Key>", self.on_key_pressed) self._other_dialog_open = False self.after(self._caretShownDelay, self.on_timer) def redisplay(self, update_selection=False): # print("caret loc", self._model.get_caret_location().get(), "||| current row and column", # self._model.find_caret(), "|||| sel", self._model.get_selection_range().get_start(), # self._model.get_selection_range().get_end()) row_height = self._font.metrics()["linespace"] (caret_row, caret_column) = self._model.find_caret() height = max(self._minimalHeight, len( list(self._model.all_lines())) * row_height + 2 * self._yMargin) width = max(self._minimalWidth, self._font.measure( max(list(self._model.all_lines()), key=len)) + self._numberSpacing + 2 * self._xMargin) self._canvas.delete("all") self._canvas.config(scrollregion=(0, 0, width, height)) # TODO follow caret by scrolling left/right and up/down # deltaX = # deltaY = self._render_selection(update_selection) self._render_caret(caret_row, caret_column) for (i, line) in enumerate(self._model.all_lines()): self._canvas.create_text(self._xMargin, self._yMargin + row_height * i, font=self._font, text=str(i), anchor=NW, fill=self._canvasTextColor) self._canvas.create_text(self._xMargin + self._numberSpacing, self._yMargin + row_height * i, font=self._font, text=line, anchor=NW, fill=self._canvasTextColor) def update_selection(self, right_end_moved: bool): """ Update the current selection in the TextEditorModel. If selecting is inactive, the selection will be reset to range [current_caret_location, current_caret_location]. Otherwise, the range will be updated in accordance with the new caret position. :param right_end_moved: was the caret at the right end of the selection """ # TODO move to an more appropriate place new_caret_location = self._model.get_caret_location().get() if self._selecting_active: selection_before = self._model.get_selection_range() if right_end_moved: self._model.set_selection_range(LocationRange(selection_before.get_start().get(), new_caret_location)) else: self._model.set_selection_range(LocationRange(new_caret_location, selection_before.get_end().get())) else: self._model.reset_selection() def on_timer(self): self._display_caret = not self._display_caret self.redisplay(True) self.after(self._caretShownDelay if not self._display_caret else self._caretShownDelay, self.on_timer) def on_exit(self): self.master.destroy() def get_x_y_of_line_start(self, row): return self._xMargin + self._numberSpacing, self._yMargin + self._font.metrics()["linespace"] * row def get_x_y_of_line_end(self, row): return (self._xMargin + self._numberSpacing + self._font.measure(self._model.get_line(row)), self._yMargin + self._font.metrics()["linespace"] * row) def get_x_y_at_caret_location(self, caret_location: Location): row, column = self._model.find_location(caret_location) return self.get_x_y_at_row_and_column(row, column) def get_x_y_at_row_and_column(self, row, column): xy_tuple = self.get_x_y_of_line_start(row) x, y = xy_tuple[0], xy_tuple[1] return x + self._font.measure(self._model.get_line(row)[:column]), y def row_height(self): return self._font.metrics()["linespace"] def _render_caret(self, caret_row, caret_column): color = self._caretColor if self._display_caret else self._canvasBackgroundColor x_start = self._xMargin + self._numberSpacing + self._font.measure( self._model.get_line(caret_row)[:caret_column]) y_start = self._yMargin + self.row_height() * caret_row + 3 self._canvas.create_line(x_start - 3, y_start, x_start + 3, y_start, fill=color) self._canvas.create_line(x_start, y_start, x_start, y_start + self._font.metrics()["linespace"] - 9, fill=color) self._canvas.create_line(x_start - 3, y_start + self._font.metrics()["linespace"] - 9, x_start + 3, y_start + self._font.metrics()["linespace"] - 9, fill=color) def _render_selection(self, update_selection=True): selection = self._model.get_selection_range() if selection.get_start().get() == selection.get_end().get(): return if update_selection: self.__preprocess_selection(selection) for e in self.__processed_selection: self._canvas.create_rectangle(e[0][0], e[0][1], e[1][0], e[1][1] + self.row_height(), outline=self._canvasHighlightColor, fill=self._canvasHighlightColor) def __preprocess_selection(self, selection: LocationRange): sel_start_r, sel_start_c = self._model.find_location(selection.get_start()) sel_end_r, sel_end_c = self._model.find_location(selection.get_end()) if sel_start_r > sel_end_r or sel_start_r == sel_end_r and sel_start_c > sel_end_c: sel_start_r, sel_start_c, sel_end_r, sel_end_c = sel_end_r, sel_end_c, sel_start_r, sel_start_c if sel_start_r == sel_end_r: self.__processed_selection = [(self.get_x_y_at_row_and_column(sel_start_r, sel_start_c), self.get_x_y_at_row_and_column(sel_end_r, sel_end_c))] else: self.__processed_selection = [(self.get_x_y_at_row_and_column(sel_start_r, sel_start_c), self.get_x_y_of_line_end(sel_start_r))] self.__processed_selection += [(self.get_x_y_of_line_start(row), self.get_x_y_of_line_end(row)) for row in range(sel_start_r + 1, sel_end_r)] self.__processed_selection += [(self.get_x_y_of_line_start(sel_end_r), self.get_x_y_at_row_and_column(sel_end_r, sel_end_c))] def update_status_bar(self): ln, col = self._model.find_caret() total_rows = self._model.get_lines_count() self._statusbar['text'] = "Ln: {}, Col: {}\tTotal rows: {}".format(ln, col, total_rows) def on_key_pressed(self, e): if self._other_dialog_open: return keysym = e.keysym char = e.char # https://stackoverflow.com/questions/19861689/check-if-modifier-key-is-pressed-in-tkinter ctrl = (e.state & 0x4) != 0 alt = (e.state & 0x8) != 0 or (e.state & 0x80) != 0 shift = (e.state & 0x1) != 0 self._selecting_active = shift # print(e.keysym, e.char, e.keycode, e.char.isprintable(), "%x" % e.state, ctrl, shift, alt) if keysym == "Left": self._display_caret = True caret_before = self._model.get_caret_location().get() self._model.move_caret_left() self.update_selection(caret_before == self._model.get_selection_range().get_end().get()) elif keysym == "Right": self._display_caret = True caret_before = self._model.get_caret_location().get() self._model.move_caret_right() self.update_selection(caret_before == self._model.get_selection_range().get_end().get()) elif keysym == "Up": self._display_caret = True caret_before = self._model.get_caret_location().get() self._model.move_caret_up() self.update_selection(caret_before == self._model.get_selection_range().get_end().get()) elif keysym == "Down": self._display_caret = True caret_before = self._model.get_caret_location().get() self._model.move_caret_down() self.update_selection(caret_before == self._model.get_selection_range().get_end().get()) elif keysym == "BackSpace": self._display_caret = True if self._model.get_selection_range().is_empty(): action = self._model.execute_delete_before() if action is not None: UndoManager.get_instance().push(action) else: self.delete_selection() elif keysym == "Delete": self._display_caret = True if self._model.get_selection_range().is_empty(): action = self._model.execute_delete_after() if action is not None: UndoManager.get_instance().push(action) else: self.delete_selection() elif keysym == "Return": # TODO what if None action1 = self.delete_selection(False) action2 = self._model.execute_insert_at_caret("\n") actions = [a for a in [action1, action2] if a is not None] if actions: UndoManager.get_instance().push(JumboEditAction(actions)) elif keysym == "Escape": self._display_caret = True self.master.destroy() elif ctrl and keysym.lower() == "c": self.copy() elif ctrl and keysym.lower() == "x": self.cut() elif ctrl and keysym.lower() == "v": if shift: self.paste_and_take() else: self.paste() elif ctrl and keysym.lower() == "y": self.redo() elif ctrl and keysym.lower() == "z": self.undo() elif len(char) and char.isprintable(): # TODO should LocationRange be immutable? Or how should I pass it around? action1 = self.delete_selection(False) action2 = self._model.execute_insert_at_caret(char) self._model.reset_selection() actions = [a for a in [action1, action2] if a is not None] if actions: UndoManager.get_instance().push(JumboEditAction(actions)) def open_file(self): self._other_dialog_open = True try: f = filedialog.askopenfile(mode='r', defaultextension=".txt") if f is None: return text = "\n".join(f.readlines()) self._model.set_text(text) f.close() finally: self._other_dialog_open = False def save_file(self): self._other_dialog_open = True try: f = filedialog.asksaveasfile(mode='w', defaultextension=".txt") if f is None: return text = "\n".join(self._model.all_lines()) f.write(text) f.close() finally: self._other_dialog_open = False def undo(self): um = UndoManager.get_instance() if not um.is_undo_empty(): um.undo() def redo(self): um = UndoManager.get_instance() if not um.is_redo_empty(): um.redo() def copy(self): if not self._model.get_selection_range().is_empty(): self._clipboardStack.push(self._model.get_selected_text()) def cut(self): if not self._model.get_selection_range().is_empty(): self._clipboardStack.push(self._model.get_selected_text()) self.delete_selection() def paste(self): if self._clipboardStack.has_any(): self.delete_selection() action = self._model.execute_insert_at_caret(self._clipboardStack.peek()) if action is not None: UndoManager.get_instance().push(action) def paste_and_take(self): if self._clipboardStack.has_any(): action1 = self.delete_selection(False) action2 = self._model.execute_insert_at_caret(self._clipboardStack.pop()) actions = [a for a in [action1, action2] if a is not None] if actions: UndoManager.get_instance().push(JumboEditAction(actions)) def delete_selection(self, push_action=True) -> EditAction: if self._model.get_selection_range().is_empty(): return sel_left = self._model.get_selection_range().get_left() self._model.move_caret_to(sel_left) action = self._model.execute_delete_range(self._model.get_selection_range().clone()) if push_action and action is not None: UndoManager.get_instance().push(action) self._model.set_selection_range(LocationRange(sel_left.get(), sel_left.get())) return action def clear_document(self): self._model.set_selection_range(LocationRange(0, self._model.get_caret_max())) self.delete_selection() def caret_to_start(self): self._model.move_caret_to(Location(0)) def caret_to_end(self): self._model.move_caret_to(Location(self._model.get_caret_max()))
def __init__(self, master, columns, data=None, command=None, sort=True, select_mode=None, heading_anchor=CENTER, cell_anchor=W, style=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, field_background=None, heading_font=None, heading_background=None, heading_foreground=None, cell_pady=2, cell_background=None, cell_foreground=None, cell_font=None, headers=True): self._stripped_rows = stripped_rows self._columns = columns self._number_of_rows = 0 self._number_of_columns = len(columns) self.row = self.List_Of_Rows(self) self.column = self.List_Of_Columns(self) s = Style() if style is None: style_name = "Multicolumn_Listbox%s.Treeview" % self._style_index self._style_index += 1 else: style_name = style style_map = {} if selection_background is not None: style_map["background"] = [('selected', selection_background)] if selection_foreground is not None: style_map["foeground"] = [('selected', selection_foreground)] if style_map: s.map(style_name, **style_map) style_config = {} if cell_background is not None: style_config["background"] = cell_background if cell_foreground is not None: style_config["foreground"] = cell_foreground if cell_font is None: font_name = s.lookup(style_name, "font") cell_font = nametofont(font_name) else: if not isinstance(cell_font, Font): if isinstance(cell_font, basestring): cell_font = nametofont(cell_font) else: if len(Font) == 1: cell_font = Font(family=cell_font[0]) elif len(Font) == 2: cell_font = Font(family=cell_font[0], size=cell_font[1]) elif len(Font) == 3: cell_font = Font(family=cell_font[0], size=cell_font[1], weight=cell_font[2]) else: raise ValueError( "Not possible more than 3 values for font") style_config["font"] = cell_font self._cell_font = cell_font self._rowheight = cell_font.metrics("linespace") + cell_pady style_config["rowheight"] = self._rowheight if field_background is not None: style_config["fieldbackground"] = field_background s.configure(style_name, **style_config) heading_style_config = {} if heading_font is not None: heading_style_config["font"] = heading_font if heading_background is not None: heading_style_config["background"] = heading_background if heading_foreground is not None: heading_style_config["foreground"] = heading_foreground heading_style_name = style_name + ".Heading" s.configure(heading_style_name, **heading_style_config) treeview_kwargs = {"style": style_name} if height is not None: treeview_kwargs["height"] = height if padding is not None: treeview_kwargs["padding"] = padding if headers: treeview_kwargs["show"] = "headings" else: treeview_kwargs["show"] = "" if select_mode is not None: treeview_kwargs["selectmode"] = select_mode self.interior = Treeview(master, columns=columns, **treeview_kwargs) if command is not None: self._command = command self.interior.bind("<<TreeviewSelect>>", self._on_select) for i in range(0, self._number_of_columns): if sort: self.interior.heading( i, text=columns[i], anchor=heading_anchor, command=lambda col=i: self.sort_by(col, descending=False)) else: self.interior.heading(i, text=columns[i], anchor=heading_anchor) if adjust_heading_to_content: self.interior.column(i, width=Font().measure(columns[i])) self.interior.column(i, anchor=cell_anchor) if data is not None: for row in data: self.insert_row(row)
class DNAStrandInterface: ## Valid DNA symbols. symbols = 'atcg' complSymbols = { 'a':'t', 't':'a', 'c':'g', 'g':'c' } def __init__(self, master = None, data1 = "", data2 = ""): self.master = master self.canResize = 0 self.helpIsOpen = False #text fonts self.font = Font(family="Courier", size=22) self.widthOfChar = self.font.measure("a") self.heightOfChar = self.font.metrics("linespace") self.fontHelp = Font(family="Courier", size=12) self.widthOfCharHelp = self.fontHelp.measure("a") self.heightOfCharHelp = self.fontHelp.metrics("linespace") self.canvas = Canvas(master) self.recenter() self.dnaMoving = False self.x = 0 self.y = 0 self.getData() self.movement() #initial scene to choose method of input data def getData(self): self.buttonFile = Button(self.master, text = "Get data from file", command = self.getDataFile) self.buttonType = Button(self.master, text = "Type data", command = self.getDataType) self.buttonFile.place(relx=0.5, rely=0.4, anchor=CENTER) self.buttonType.place(relx=0.5, rely=0.6, anchor=CENTER) self.canvas.pack() #destroys initial scene def destroyInitMenu(self): self.buttonFile.destroy() self.buttonType.destroy() #get input from file #generates error if the path is invalid def getEntryFileData(self): path = self.entryFile.get() try: with open(path, 'r') as f: data1 = f.readline() if(len(data1) >= 1 and data1[len(data1)-1]=='\n'): data1 = data1[:-1] data2 = f.readline() if(len(data2) >= 1 and data2[len(data2)-1]=='\n'): data2 = data2[:-1] f.close() self.destroyDataFile() self.setData(data1, data2) except IOError as e: print("Invalid path") self.canvas.destroy() exit() #destroys data file scene def destroyDataFile(self): self.entryFile.destroy() self.labelFile.destroy() self.buttonGetDataFile.destroy() #creates data file scene def getDataFile(self): self.destroyInitMenu() txt1 = "Path: " self.labelFile = Label(master, text=txt1, font = self.font) self.entryFile = Entry(self.master) self.buttonGetDataFile = Button(self.master, text = "ENTER", command=self.getEntryFileData) self.buttonGetDataFile.place(relx=0.5, rely=0.7, anchor=CENTER) self.labelFile.place(relx=0.4, rely=0.5, anchor=CENTER) self.entryFile.place(relx=0.6, rely=0.5, anchor=CENTER) #creates data type scene def getDataType(self): self.destroyInitMenu() txt1 = "DNA strand 1: " self.labelDna1 = Label(master, text=txt1, font = self.font) txt2 = "DNA strand 2: " self.labelDna2 = Label(master, text=txt2, font = self.font) self.entryDna1 = Entry(self.master) self.entryDna2 = Entry(self.master) self.buttonGetDataType = Button(self.master, text = "ENTER", command=self.getEntryData) self.labelDna1.place(relx=0.4, rely=0.2, anchor=CENTER) self.entryDna1.place(relx=0.7, rely=0.2, anchor=CENTER) self.labelDna2.place(relx=0.4, rely=0.4, anchor=CENTER) self.entryDna2.place(relx=0.7, rely=0.4, anchor=CENTER) self.buttonGetDataType.place(relx=0.5, rely=0.7, anchor=CENTER) self.canvas.pack() #destroys data type scene def destroyDataType(self): self.entryDna1.destroy() self.labelDna1.destroy() self.entryDna2.destroy() self.labelDna2.destroy() self.buttonGetDataType.destroy() #gets input from typing def getEntryData(self): data1 = self.entryDna1.get() data2 = self.entryDna2.get() self.destroyDataType() self.setData(data1, data2) #test if given data is valid def isValid(self, data): if(data == ""): return False valid = True for i in (data): flag = False for j in(self.symbols): if (i == j): flag = True break if flag == False: valid = False break return valid #generates error if given data is invalid def validateData(self, data1, data2): try: if self.isValid(data1) == False: raise ValueError('invalid givenData strand1') if self.isValid(data2) == False: raise ValueError('invalid givenData strand2') except ValueError as error: print(str(error)) self.canvas.destroy() exit() #sets data1 and data2 and creates dna moving scene def setData(self, data1, data2): self.canResize = 0 self.data1 = data1.replace('A','a').replace('T','t').replace('G','g').replace('C','c') self.data2 = data2.replace('A','a').replace('T','t').replace('G','g').replace('C','c') self.validateData(self.data1, self.data2) self.reset() #creates strands with the given data self.strand1 = self.canvas.create_text( self.center['x'], self.center['y'], text = self.data1, font = self.font ) self.strand2 = self.canvas.create_text( self.center['x'] + self.x, self.center['y'] + self.y + self.widthOfChar, text = self.data2, font = self.font) #creates help message self.helpMSG = self.canvas.create_text( 10 + len("h - help")*self.widthOfCharHelp/2.0, 10, text = "h - help", font = self.fontHelp, fill="red") self.helpList = ["Shift-L - shuffle", "Escape - exit", "m - got to position", " of maximum matches", "right key - move right", "left key - move left", "upper key - move up", "down key - move down", "Tab - reset"] self.helpPosX = [ 0, 0, 0, 0,(25*self.widthOfCharHelp), (25*self.widthOfCharHelp), (25*self.widthOfCharHelp), (25*self.widthOfCharHelp), (25*self.widthOfCharHelp)] self.helpPosY = [ 1, 2, 3, 4, 0, 1, 2, 3, 4] self.helpTexts = [] for i in range(len(self.helpList)): self.helpTexts.append(self.canvas.create_text( 10 + len(self.helpList[i])*self.widthOfCharHelp/2.0 + self.helpPosX[i], 10 + self.heightOfCharHelp*self.helpPosY[i] , text = "", font = self.fontHelp)) self.recenter() self.canvas.config(width=2560, height=1536) self.canvas.pack() self.dnaMoving = True #deals with help message def help(self, event): if(self.dnaMoving): msg ="" if(self.helpIsOpen == True): msg = "h - help" for i in range(len(self.helpList)): self.canvas.itemconfig(self.helpTexts[i], text = "") else: msg = "h - close help" for i in range(len(self.helpList)): self.canvas.itemconfig(self.helpTexts[i], text = self.helpList[i]) self.canvas.itemconfig(self.helpMSG, text = msg) self.canvas.coords(self.helpMSG, 10 + len(msg)*self.widthOfCharHelp/2.0, 10) self.helpIsOpen = not self.helpIsOpen #recenters components def recenter(self): self.canvas.pack() self.center = {'y' : self.canvas.winfo_height()/2.0, 'x' : self.canvas.winfo_width()/2.0} #resizes window def resize(self, event): self.width = event.width self.height = event.height if(self.canResize >= 1): self.canvas.config(width=self.width, height=self.height) self.canvas.pack() self.canResize = 0 else: self.canResize += 1 #sets strand2 relative position to the starting position def reset(self): self.x = (len(self.data2) - len(self.data1))*self.widthOfChar/(2.0) self.y = self.heightOfChar def callReset(self, event): if(self.dnaMoving): self.reset() #deals with the strands movements def movement(self): self.recenter() if(self.dnaMoving): self.canvas.coords(self.strand1, self.center['x'], self.center['y']) self.canvas.coords(self.strand2, self.center['x'] + self.x, self.center['y'] + self.y) x_strand2 = self.canvas.coords(self.strand2)[0] y_strand2 = self.canvas.coords(self.strand2)[1] width_strand2 = len(self.data2)*self.widthOfChar height_strand2 = self.heightOfChar if(x_strand2 + width_strand2/2 <= 0 or x_strand2 - width_strand2/2 >= self.canvas.winfo_width()): self.reset() elif(y_strand2 + height_strand2/2 <= 0 or y_strand2 - height_strand2/2 >= self.canvas.winfo_height()): self.reset() self.matches() self.canvas.after(100, self.movement) def left(self, event): self.x += -5 self.y += 0 def right(self, event): self.x += 5 self.y += 0 def up(self, event): self.x += 0 self.y += -5 def down(self, event): self.x += 0 self.y += 5 #returns if math of c1 and c2 is valid def charMatch(self, c1, c2): match = False if c1 in self.symbols: if c2 == self.complSymbols[c1]: match = True # If c1 is in the complement symbols dictionary and c2 is equal to the complement of c1 the match is valid return match def findMatchesWithRightShift(self, shift): myStrand = self.data2 otherStrand = self.data1 mat = [False]*len(myStrand) myIndex = shift # right shift the other is equal to left shift myself otherIndex = 0 # iterates through the letters of the strands testing matches i = 0 while(otherIndex + i < len(otherStrand) and myIndex + i < len(myStrand)): c1 = otherStrand[otherIndex + i] c2 = myStrand[myIndex + i] if (self.charMatch(c1, c2)== True): mat[myIndex + i] = True i+=1 # iterates through the mat array inserting each modified character at its corresponding position in the matches string matches = '' for i in range(len(mat)): if (mat[i]==False): matches += (myStrand[i]).lower() else: matches += myStrand[i].upper() return matches def findMatchesWithLeftShift(self, shift): myStrand = self.data2 otherStrand = self.data1 mat = [False]*len(myStrand) myIndex = 0 otherIndex = shift # iterates through the letters of the strands testing matches i = 0 while(otherIndex + i < len(otherStrand) and myIndex + i < len(myStrand)): c1 = otherStrand[otherIndex + i] c2 = myStrand[myIndex + i] if (self.charMatch(c1, c2) == True): mat[myIndex + i] = True i+=1 # iterates through the mat array inserting each modified character at its corresponding position in the matches string matches = '' for i in range(len(mat)): if (mat[i]==False): matches += (myStrand[i]).lower() else: matches += myStrand[i].upper() return matches def countMatchesWithRightShift(self, shift): myStrand = self.data2 otherStrand = self.data1 count = 0 myIndex = shift # right shift the other is equal to left shift myself otherIndex = 0 # iterates through the letters of the strands testing matches i = 0 while(otherIndex + i < len(otherStrand) and myIndex + i < len(myStrand)): c1 = otherStrand[otherIndex + i] c2 = myStrand[myIndex + i] if (self.charMatch(c1, c2)== True): count += 1 i+=1 return count def countMatchesWithLeftShift(self, shift): myStrand = self.data2 otherStrand = self.data1 count = 0 myIndex = 0 otherIndex = shift # iterates through the letters of the strands testing matches i = 0 while(otherIndex + i < len(otherStrand) and myIndex + i < len(myStrand)): c1 = otherStrand[otherIndex + i] c2 = myStrand[myIndex + i] if (self.charMatch(c1, c2) == True): count += 1 i+=1 return count def findMaxPossibleMatches(self, event): if(self.dnaMoving): countMax = -1 posMax = -1 limitShiftLeft = len(self.data1) - 1 limitShiftRight = len(self.data2) - 1 for shift in range(limitShiftLeft): matches = self.countMatchesWithLeftShift(shift) if (matches > countMax): countMax = matches posMax = shift for shift in range(limitShiftRight): matches = self.countMatchesWithRightShift(shift) if (matches> countMax): countMax = matches posMax = -shift self.reset() self.x += posMax*self.widthOfChar # tests matches and indicates them with uppercase letters def matches(self): self.canvas.pack() ini_x_strand2 = self.center['x'] + (len(self.data2) - len(self.data1))*self.widthOfChar/(2.0) x_strand2 = self.canvas.coords(self.strand2)[0] if(x_strand2 >= ini_x_strand2): shift = (int) (x_strand2 - ini_x_strand2) // self.widthOfChar self.canvas.itemconfig(self.strand2, text = self.findMatchesWithLeftShift(shift)) else: shift = (int) (ini_x_strand2 - x_strand2) // self.widthOfChar self.canvas.itemconfig(self.strand2, text = self.findMatchesWithRightShift(shift)) #shuffles letters of strand2 def shuffle(self, event): if(self.dnaMoving): newData = list(self.data2) for i in range(0,len(newData) - 1): j = randint(i+1, len(newData)-1) newData[i], newData[j] = newData[j], newData[i] self.data2 = ''.join(newData) self.canvas.itemconfig(self.strand2, text = self.data2) #exits application def quit(self, event): self.canvas.destroy() exit()
class Tree(Canvas): # do we have enough possible arguments?!?!?! def __init__(self, master, root_id, root_label='', get_contents_callback=None, dist_x=15, dist_y=15, font=None, text_offset=10, line_flag=1, expanded_icon=None, collapsed_icon=None, regular_icon=None, plus_icon=None, minus_icon=None, node_class=Node, drop_callback=None, *args, **kw_args): # pass args to superclass (new idiom from Python 2.2) Canvas.__init__(self, master, *args, **kw_args) #BNV Create the font and calc height self._active = None try: if font is None: self.font = Font(name="tree", exists=True) else: self.font = Font(name="tree", font=font, exists=True) except: if font is None: font = _DEFAULT_FONT try: self.font = Font(name="tree", font=font) except: self.font = font metrics = self.font.metrics() self.height = metrics["ascent"] + metrics["descent"] if dist_y < self.height: dist_y = self.height + 2 # this allows to subclass Node and pass our class in self.node_class = node_class # keep track of node bindings self.bindings = {} # cheap mutex spinlock self.spinlock = 0 # flag to see if there's been any d&d dragging self.drag = 0 # default images (BASE64-encoded GIF files) if expanded_icon == None: self.expanded_icon=PhotoImage( data='R0lGODlhEAANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAQAA0AAAM6GCrM+jCIQamIbw6ybXNSx3GVB' \ 'YRiygnA534Eq5UlO8jUqLYsquuy0+SXap1CxBHr+HoBjoGndDpNAAA7') else: self.expanded_icon = expanded_icon if collapsed_icon == None: self.collapsed_icon=PhotoImage( data='R0lGODlhDwANAKIAAAAAAMDAwICAgP//////ADAwMAAAAAAA' \ 'ACH5BAEAAAEALAAAAAAPAA0AAAMyGCHM+lAMMoeAT9Jtm5NDKI4Wo' \ 'FXcJphhipanq7Kvu8b1dLc5tcuom2foAQQAyKRSmQAAOw==') else: self.collapsed_icon = collapsed_icon if regular_icon == None: self.regular_icon=PhotoImage( data='R0lGODlhCwAOAJEAAAAAAICAgP///8DAwCH5BAEAAAMALAAA' \ 'AAALAA4AAAIphA+jA+JuVgtUtMQePJlWCgSN9oSTV5lkKQpo2q5W+' \ 'wbzuJrIHgw1WgAAOw==') else: self.regular_icon = regular_icon if plus_icon == None: self.plus_icon=PhotoImage( data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ 'FIyPoiu2sJyCyoF7W3hxz850CFIA\nADs=') else: self.plus_icon = plus_icon if minus_icon == None: self.minus_icon=PhotoImage( data='R0lGODdhCQAJAPEAAAAAAH9/f////wAAACwAAAAACQAJAAAC' \ 'EYyPoivG614LAlg7ZZbxoR8UADs=') else: self.minus_icon = minus_icon # horizontal distance that subtrees are indented self.dist_x = dist_x # vertical distance between rows self.dist_y = dist_y # how far to offset text label self.text_offset = text_offset # flag controlling connecting line display self.line_flag = line_flag # called just before subtree expand/collapse self.get_contents_callback = get_contents_callback # called after drag'n'drop self.drop_callback = drop_callback # create root node to get the ball rolling self.root = node_class(parent_node=None, label=root_label, id=root_id, expandable_flag=1, collapsed_icon=self.collapsed_icon, expanded_icon=self.expanded_icon, x=dist_x, y=dist_y, parent_widget=self) # configure for scrollbar(s) x1, y1, x2, y2 = self.bbox('all') self.configure(scrollregion=(x1, y1, x2 + 5, y2 + 5)) # add a cursor self.cursor_box = self.create_rectangle(0, 0, 0, 0) self.move_cursor(self.root) # make it easy to point to control #BNV #self.bind('<Enter>', self.PVT_mousefocus) self.bind('<Button-1>', self.PVT_mousefocus) # totally arbitrary yet hopefully intuitive default keybindings # stole 'em from ones used by microsoft tree control # page-up/page-down self.bind('<Next>', self.pagedown) self.bind('<Prior>', self.pageup) # arrow-up/arrow-down self.bind('<Down>', self.__next__) self.bind('<Up>', self.prev) # arrow-left/arrow-right self.bind('<Left>', self.ascendAndClose) # (hold this down and you expand the entire tree) self.bind('<Right>', self.descend) # home/end self.bind('<Home>', self.first) self.bind('<End>', self.last) # space bar self.bind('<Key-space>', self.toggle) self.bind('<Return>', self.select) # mouse wheel self.bind('<Button-4>', self.lineUp) self.bind('<Button-5>', self.lineDown) # middle button self.bind('<B2-Motion>', self.scanDrag) self.bind('<ButtonRelease-2>', self.scanRelease) self.scandrag = False # ----- PRIVATE METHODS (prefixed with "PVT_") ----- # these methods are subject to change, so please try not to use them def PVT_mousefocus(self, event): """Soak up event argument when moused-over""" self.focus_set() # ----- PUBLIC METHODS ----- def tag_bind(self, tag, seq, *args, **kw_args): """Keep track of callback bindings so we can delete them later. I shouldn't have to do this!!!!""" # pass args to superclass func_id = Canvas.tag_bind(self, tag, seq, *args, **kw_args) # save references self.bindings[tag] = self.bindings.get(tag, []) + [(seq, func_id)] def add_list(self, list=None, name=None, id=None, flag=0, expanded_icon=None, collapsed_icon=None): """Add node construction info to list""" n = Struct() n.name = name n.id = id n.flag = flag if collapsed_icon: n.collapsed_icon = collapsed_icon else: if flag: # it's expandable, use closed folder icon n.collapsed_icon = self.collapsed_icon else: # it's not expandable, use regular file icon n.collapsed_icon = self.regular_icon if flag: if expanded_icon: n.expanded_icon = expanded_icon else: n.expanded_icon = self.expanded_icon else: # not expandable, don't need an icon n.expanded_icon = None if list == None: list = [] list.append(n) return list def add_node(self, name=None, id=None, flag=0, expanded_icon=None, collapsed_icon=None): """Add a node during get_contents_callback()""" self.add_list(self.new_nodes, name, id, flag, expanded_icon, collapsed_icon) def find_full_id(self, search): """Search for a node""" return self.root.PVT_find(search) # BNV def find_full_id_expand(self, search): node = self.root prev = None for id in search: node.expand() if isinstance(id, int): node = node.child_nodes[id] else: if prev is None: pid = [id] else: pid = [prev, id] node = node.PVT_find(pid) if node is None: break prev = id return node def cursor_node(self): """Return node under cursor""" return self.pos def see(self, *items): """Scroll (in a series of nudges) so items are visible""" x1, y1, x2, y2 = self.bbox(*items) while x2 > self.canvasx(0) + self.winfo_width(): old = self.canvasx(0) self.xview('scroll', 1, 'units') # avoid endless loop if we can't scroll if old == self.canvasx(0): break while y2 > self.canvasy(0) + self.winfo_height(): old = self.canvasy(0) self.yview('scroll', 1, 'units') if old == self.canvasy(0): break # done in this order to ensure upper-left of object is visible while x1 < self.canvasx(0): old = self.canvasx(0) self.xview('scroll', -1, 'units') if old == self.canvasx(0): break while y1 < self.canvasy(0): old = self.canvasy(0) self.yview('scroll', -1, 'units') if old == self.canvasy(0): break def move_cursor(self, node): """Move cursor to node""" self.pos = node x1, y1, x2, y2 = self.bbox(node.symbol, node.label) self.coords(self.cursor_box, x1 - 1, y1 - 1, x2 + 1, y2 + 1) self.see(node.symbol, node.label) def toggle(self, event=None): """Expand/collapse subtree""" self.pos.toggle_state() # BNV override routine for selection def select(self, event=None): """Select node""" self.pos.select() def next(self, event=None): """Move to next lower visible node""" self.move_cursor(self.pos.next_visible()) def prev(self, event=None): """Move to next higher visible node""" self.move_cursor(self.pos.prev_visible()) def ascend(self, event=None): """Move to immediate parent""" if self.pos.parent_node: # move to parent self.move_cursor(self.pos.parent_node) # BNV def ascendAndClose(self, event=None): """Close present if open and then move to immediate parent""" if self.pos.expandable_flag and self.pos.expanded_flag: self.pos.toggle_state() elif self.pos.parent_node: # move to parent self.move_cursor(self.pos.parent_node) def descend(self, event=None): """Move right, expanding as we go""" if self.pos.expandable_flag: self.pos.expand() if self.pos.child_nodes: # move to first subnode self.move_cursor(self.pos.child_nodes[0]) return # if no subnodes, move to next sibling next(self) def first(self, event=None): """Go to root node""" # move to root node self.move_cursor(self.root) def last(self, event=None): """Go to last visible node""" # move to bottom-most node self.move_cursor(self.root.PVT_last()) def pageup(self, event=None): """Previous page""" n = self.pos j = self.winfo_height() / self.dist_y for i in range(j - 3): n = n.prev_visible() self.yview('scroll', -1, 'pages') self.move_cursor(n) def pagedown(self, event=None): """Next page""" n = self.pos j = self.winfo_height() / self.dist_y for i in range(j - 3): n = n.next_visible() self.yview('scroll', 1, 'pages') self.move_cursor(n) # BNV def refresh(self): full_id = self.pos.full_id() self.root.refresh() node = self.find_full_id(full_id) if node is not None: self.move_cursor(node) Node.select(node) # BNV def lineUp(self, event=None): """Previous line""" self.yview('scroll', -1, 'units') # BNV def lineDown(self, event=None): """Next line""" self.yview('scroll', 1, 'units') # BNV def scanDrag(self, event): if self.scandrag: self.scan_dragto(event.x, event.y, 3) else: self.config(cursor="hand2") self.scan_mark(event.x, event.y) self.scandrag = True # BNV def scanRelease(self, event): self.scandrag = False self.config(cursor="") # ----- functions for drag'n'drop support ----- def where(self, event): """Determine drag location in canvas coordinates. event.x & event.y don't seem to be what we want.""" # where the corner of the canvas is relative to the screen: x_org = self.winfo_rootx() y_org = self.winfo_rooty() # where the pointer is relative to the canvas widget, # including scrolling x = self.canvasx(event.x_root - x_org) y = self.canvasy(event.y_root - y_org) return x, y def dnd_accept(self, source, event): """Accept dnd messages, i.e. we're a legit drop target, and we do implement d&d functions.""" if not isinstance(source, Tree): return None self.target = None return self def dnd_enter(self, source, event): """Get ready to drag or drag has entered widget (create drag object)""" # this flag lets us know there's been drag motion self.drag = 1 x, y = self.where(event) x1, y1, x2, y2 = source.widget.bbox(source.symbol, source.label) dx, dy = x2 - x1, y2 - y1 # create dragging icon if source.expanded_flag: self.dnd_symbol = self.create_image(x, y, image=source.expanded_icon) else: self.dnd_symbol = self.create_image(x, y, image=source.collapsed_icon) self.dnd_label = self.create_text(x + self.text_offset, y, text=source.get_label(), justify='left', anchor='w') def dnd_motion(self, source, event): """Move drag icon""" self.drag = 1 x, y = self.where(event) x1, y1, x2, y2 = self.bbox(self.dnd_symbol, self.dnd_label) self.move(self.dnd_symbol, x - x1 + source.x_off, y - y1 + source.y_off) self.move(self.dnd_label, x - x1 + source.x_off, y - y1 + source.y_off) def dnd_leave(self, source, event): """Finish dragging or drag has left widget (destroy drag object)""" self.delete(self.dnd_symbol) self.delete(self.dnd_label) def dnd_commit(self, source, event): """Object has been dropped here""" # call our own dnd_leave() to clean up self.dnd_leave(source, event) # process pending events to detect target node # update_idletasks() doesn't do the trick if source & target are # on different widgets self.update() if not self.target: # no target node return # we must update data structures based on the drop if self.drop_callback: try: # called with dragged node and target node # this is where a file manager would move the actual file # it must also move the nodes around as it wishes self.drop_callback(source, self.target) except: report_callback_exception()