def __init__(self, parent, app=None): tk.Frame.__init__(self, parent) background = self.cget("background") self.app = app self.pages = [] self.nodelist = [] self.current_page = None # within the frame are two panes; the left has a tree, # the right shows the current page. We need a splitter self.pw = tk.PanedWindow(self, orient="horizontal", background="#f58735", borderwidth=1,relief='solid', sashwidth=3) self.pw.pack(side="top", fill="both", expand=True, pady = (4,1), padx=4) self.left = tk.Frame(self.pw, background=background, borderwidth=0, highlightthickness=0) self.right = tk.Frame(self.pw, background="white", width=600, height=600, borderwidth=0, highlightthickness=0) self.pw.add(self.left) self.pw.add(self.right) self.list = TabList(self.left) vsb = AutoScrollbar(self.left, command=self.list.yview, orient="vertical") hsb = AutoScrollbar(self.left, command=self.list.xview, orient="horizontal") self.list.configure(xscrollcommand=hsb.set, yscrollcommand=vsb.set) self.list.grid(row=0, column=1, sticky="nsew", padx=0, pady=0) vsb.grid(row=0, column=0, sticky="ns") hsb.grid(row=1, column=1, sticky="ew") self.left.grid_rowconfigure(0, weight=1) self.left.grid_columnconfigure(1, weight=1) self.list.bind("<<ListboxSelect>>", self.on_list_selection) # start with them invisible; they will reappear when needed vsb.grid_remove() hsb.grid_remove()
def __init__(self, parent, app=None): tk.Frame.__init__(self, parent) background = self.cget("background") self.app = app self.pages = [] self.nodelist = [] self.current_page = None # within the frame are two panes; the left has a tree, # the right shows the current page. We need a splitter self.pw = tk.PanedWindow(self, orient="horizontal", background="#f58735", borderwidth=1, relief='solid', sashwidth=3) self.pw.pack(side="top", fill="both", expand=True, pady=(4, 1), padx=4) self.left = tk.Frame(self.pw, background=background, borderwidth=0, highlightthickness=0) self.right = tk.Frame(self.pw, background="white", width=600, height=600, borderwidth=0, highlightthickness=0) self.pw.add(self.left) self.pw.add(self.right) self.list = TabList(self.left) vsb = AutoScrollbar(self.left, command=self.list.yview, orient="vertical") hsb = AutoScrollbar(self.left, command=self.list.xview, orient="horizontal") self.list.configure(xscrollcommand=hsb.set, yscrollcommand=vsb.set) self.list.grid(row=0, column=1, sticky="nsew", padx=0, pady=0) vsb.grid(row=0, column=0, sticky="ns") hsb.grid(row=1, column=1, sticky="ew") self.left.grid_rowconfigure(0, weight=1) self.left.grid_columnconfigure(1, weight=1) self.list.bind("<<ListboxSelect>>", self.on_list_selection) # start with them invisible; they will reappear when needed vsb.grid_remove() hsb.grid_remove()
class KwBrowser(ttk.Frame): '''A widget designed to view keyword documentation''' def __init__(self, app, *args, **kwargs): self.app = app ttk.Frame.__init__(self, app, *args, **kwargs) self.kwdb = KeywordTable() self._create_ui() self.reset() self.bind("<Visibility>", self._on_visibility) self.keyword_tree.bind("<<TreeviewSelect>>", self._on_list_select) self.lib_tree.bind("<<TreeviewSelect>>", self._on_lib_select) item = self.lib_tree.insert("", "end", text="All", values=[ALL_ID]) self.lib_tree.selection_set(item) def reset(self): '''Reset the widgets and database to their startup state''' self.text.delete("1.0", "end") children = self.keyword_tree.get_children("") if len(children) > 0: self.keyword_tree.delete(*children) children = self.lib_tree.get_children("") if len(children) > 0: self.lib_tree.delete(*children) self.kwdb.reset() def search(self): '''Perform a search based on the criteria in the filter widget I should move this into the keywordtable object... ''' type = self.filter.get_type() string = self.filter.get_string().lower() children = self.keyword_tree.get_children("") self.keyword_tree.delete(*children) library_selection = self.lib_tree.selection() library_id = ALL_ID if len(library_selection) > 0: item = library_selection[0] library = self.lib_tree.item(item, "text") library_id = self.lib_tree.item(item, "values")[0] # UGH. This is klunky. I should refactor this. pattern = self._glob_to_sql(string) parameters = [] SQL = ["SELECT kw.name, kw.id, kw.doc, c.name", "FROM keyword_table as kw", "JOIN collection_table as c", ] if type == "name": SQL.append("WHERE kw.name LIKE ?") parameters.append(pattern) else: SQL.append("WHERE (kw.name like ? OR kw.doc like ?)") parameters.append(pattern) parameters.append(pattern) if library_id != ALL_ID: SQL.append("AND c.id = ?") parameters.append(str(library_id)) SQL.append("AND kw.collection_id == c.id") sql_result = self.kwdb.execute("\n".join(SQL), parameters) keywords = sql_result.fetchall() first = None for (name, kw_id, doc, source) in sorted(keywords, key=lambda t: t[0].lower()): description = doc.split("\n")[0] item = self.keyword_tree.insert("", "end", text=name,values=(kw_id, source, description)) if first is None: first = item if first is None: self.text.delete(1.0, "end") else: self.keyword_tree.selection_set(first) def add_built_in_libraries(self): self.kwdb.add_built_in_libraries() self._update_lib_tree() def add_library(self, name, *args): '''Add all the keywords for the given library This will load the library using the given args. ''' self.kwdb.add_library(name, *args) self._update_lib_tree() def add_file(self, filename): '''Add documentation for all keywords in the given file''' self.kwdb.add_file(filename) self._update_lib_tree() def _create_list(self, parent): listframe = ttk.Frame(parent, width=200, borderwidth=0) self.lib_tree = ttk.Treeview(listframe, selectmode="browse", columns=["id"], displaycolumns=[]) self.lib_tree.heading("#0", text="Libraries", anchor="w") self.lib_tree_vsb = AutoScrollbar(listframe, orient="vertical", command=self.lib_tree.yview) self.lib_tree.configure(yscrollcommand=self.lib_tree_vsb.set) self.keyword_tree = ttk.Treeview(listframe, columns=["kw_id", "source","description"], displaycolumns=["source", "description"]) self.keyword_tree.column("source", width=120, stretch=False) self.keyword_tree.heading("#0", text="Keyword Name", anchor="w") self.keyword_tree.heading("source", text="Source", anchor="w") self.keyword_tree.heading("description", text="Description", anchor="w") self.keyword_tree_ysb = AutoScrollbar(listframe, orient="vertical", command=self.keyword_tree.yview) self.keyword_tree.configure(yscrollcommand=self.keyword_tree_ysb.set) self.lib_tree.grid(row=0, column=0, sticky="nsew") self.lib_tree_vsb.grid(row=0, column=1, sticky="ns") self.keyword_tree.grid(row=0, column=2, sticky="nsew") self.keyword_tree_ysb.grid(row=0, column=3, sticky="ns") listframe.grid_rowconfigure(0, weight=1) listframe.grid_columnconfigure(2, weight=1) return listframe def _glob_to_sql(self, string): '''Convert glob-like wildcards to SQL wildcards * becomes % ? becomes _ % becomes \% \\ remains \\ This also adds a leading and trailing %, unless the pattern begins with ^ or ends with $ ''' # What's with the chr(1) and chr(2) nonsense? It's a trick to # hide \* and \? from the * and ? substitutions. This trick # depends on the substitutiones being done in order. chr(1) # and chr(2) were picked because I know those characters # almost certainly won't be in the input string table = ( (r'\\', chr(1)), (r'\*', chr(2)), (r'\?', chr(3)), (r'%', r'\%'), (r'?', '_'), (r'*', '%'), (chr(1), r'\\'), (chr(2), r'\*'), (chr(3), r'\?') ) for (a, b) in table: string = string.replace(a,b) if string.startswith("^"): string = string[1:] else: string = "%" + string if string.endswith("$"): string = string[:-1] else: string = string + "%" return string def _update_lib_tree(self): children = self.lib_tree.get_children("") self.lib_tree.delete(*children) item = self.lib_tree.insert("", "end", text="All", values=[ALL_ID]) self.lib_tree.selection_set((item,)) maxwidth = 0 for (collection_name, collection_id) in sorted(self.kwdb.get_collections(), key=lambda x: x[0].lower()): item = self.lib_tree.insert("", "end", text=collection_name, values=[collection_id]) maxwidth = max(maxwidth, len(collection_name)) # the +16 is for the space reserved for the image in the TreeView width = self.app.fonts.default.measure("M"*maxwidth) + 16 self.lib_tree.column("#0", width=width) def _create_ui(self): self.filter = FilterBox(self) self.filter.bind("<<Search>>", self._on_search) self.filter.entry.bind("<Down>", self._on_down) self.filter.entry.bind("<Up>", self._on_up) pw = tk.PanedWindow(self, orient="vertical", borderwidth=0, background=self.app.colors.accent, sashwidth=4, sashpad=0) listframe = self._create_list(pw) dataframe = self._create_data(pw) pw.add(listframe) pw.add(dataframe) self.filter.pack(side="top", fill="x", pady=2) pw.pack(side="top", fill="both", expand="true", padx=4, pady=4) def _create_data(self, parent): dataframe = ttk.Frame(parent, borderwidth=1, relief="sunken") self.text = CustomText(dataframe, wrap="word", borderwidth=0, width=120, font=self.app.fonts.default, highlightthickness=0) self.text_ysb = AutoScrollbar(dataframe, orient="vertical", command=self.text.yview) self.text.configure(yscrollcommand=self.text_ysb.set) self.text.grid(row=0, column=0, sticky="nsew", padx=4) self.text_ysb.grid(row=0, column=1, sticky="ns") dataframe.grid_rowconfigure(0, weight=1) dataframe.grid_columnconfigure(0, weight=1) self.text.tag_configure("name", font=self.app.fonts.heading) self.text.tag_configure("args", font=self.app.fonts.italic) self.text.tag_configure("example", background="lightgray", font=self.app.fonts.fixed) self.text.tag_configure("search_string", background="yellow", borderwidth=1, relief="raised") self.text.tag_raise("sel") return dataframe def _get_keywords(self, library, search_string,type): '''Return a list of all keywords that match the criteria''' keywords = [] search_string = search_string.lower() for kw in self.kwdb.get_keywords(): keywords.append(kw) return keywords def _on_lib_select(self, event): '''Callback for when user selects a library''' if not self.winfo_viewable(): return self.search() def _on_up(self, event): '''Callback for when user presses up arrow in search box''' if len(self.keyword_tree.selection()) == 0: return current = self.keyword_tree.selection()[0] prev_item = self.keyword_tree.prev(current) if prev_item: self.keyword_tree.selection_clear() self.keyword_tree.selection_set(prev_item) self.keyword_tree.see(prev_item) def _on_down(self, event): '''Callback for when user presses down arrow in search box''' if len(self.keyword_tree.selection()) == 0: return current = self.keyword_tree.selection()[0] next_item = self.keyword_tree.next(current) if next_item: self.keyword_tree.selection_clear() self.keyword_tree.selection_set(next_item) self.keyword_tree.see(next_item) def _on_visibility(self, event): '''Callback for when the UI is first realized''' # this causes the list to be updated with all the loaded files self.search() self.bind("<Visibility>", None) self.filter.set_focus() def _on_list_select(self, event): '''Callback for when user clicks on a keyword''' selection = self.keyword_tree.selection() item = selection[0] (kwid, library, description) = self.keyword_tree.item(item, "values") self.text.configure(state="normal") self.text.delete(1.0, "end") sql_result = self.kwdb.execute(''' SELECT kw.name, kw.id, kw.doc, kw.args, c.name, c.type FROM keyword_table as kw JOIN collection_table as c WHERE kw.id == ? AND c.id == kw.collection_id ''', (kwid,)) (kw_name, kw_id, kw_doc, kw_args, collection_name, collection_type) = sql_result.fetchone() self.text.insert("end", kw_name + "\n", "name", "\n") if collection_type == "library" and collection_name != "BuiltIn": self.text.insert("end", "| Library | %s \n\n" % collection_name) if len(kw_args) > 0: self.text.insert("end", "Arguments: %s\n\n" % kw_args, "args") self.text.insert("end", self._cleanup_whitespace(kw_doc)) self.text.highlight_pattern("^\|.*?$", self._on_highlight_example) self.text.highlight_pattern("(?iq)"+self.filter.get_string(), self._on_highlight_search_string) self.text.configure(state="disabled") def _cleanup_whitespace(self, s): '''Clean up whitespace artifacts due to robot parsing This replaces the literal string \n with a newline, and removes a single space immediately after a newline ''' s = s.replace(r'\n', "\n") s = s.replace("\n ", "\n") return s def _on_highlight_search_string(self): '''Callback to apply highlighting of the search string''' self.text.tag_add("search_string", "matchStart", "matchEnd") def _on_highlight_example(self): '''Callback to apply highlighting on an example''' self.text.tag_add("example", "matchStart", "matchEnd+1c") def _on_search(self, *args): '''Callback for when user changes the search string''' self.search()