import py from py.__.test.tkinter import backend from py.__.test.tkinter import util from py.__.test.tkinter import event Null = util.Null Event = event.Event import ScrolledText from Tkinter import PhotoImage import Tkinter import re import os myfont = ('Helvetica', 12, 'normal') class StatusBar(Tkinter.Frame): font = ('Helvetica', 14, 'normal') def __init__(self, master=None, **kw): if master is None: master = Tk() Tkinter.Frame.__init__(self, master, **kw) self.labels = {} self.callback = Null() self.tkvariable_dict = {'failed': Tkinter.BooleanVar(), 'skipped': Tkinter.BooleanVar(), 'passed_item': Tkinter.BooleanVar()} self.init_widgets() def init_widgets(self): def get_button(text, var): b = Tkinter.Checkbutton(self, text=text, variable=var, onvalue=True, offvalue=False, anchor=Tkinter.W, font=self.font, indicatoron = False, command = self.do_callback) b.select() return b self.add_widget('Failed', get_button('Failed', self.tkvariable_dict['failed'])) self._get_widget('Failed').configure(bg = 'Red3', fg = 'White', highlightcolor = 'Red', selectcolor = 'Red', activebackground= 'Red', activeforeground = 'White') self.add_widget('Skipped', get_button('Skipped', self.tkvariable_dict['skipped'])) self._get_widget('Skipped').configure(bg = 'Yellow3', highlightcolor = 'Yellow', selectcolor = 'Yellow', activebackground= 'Yellow') b = get_button('Passed', self.tkvariable_dict['passed_item']) b.deselect() self.add_widget('Passed', b) self._get_widget('Passed').configure(bg = 'Green3', fg = 'Black', highlightcolor = 'Black', selectcolor = 'Green', activeforeground = 'Black', activebackground= 'Green') def set_filter_args_callback(self, callback): self.callback = callback def do_callback(self,): kwargs = dict([(key, var.get()) for key, var in self.tkvariable_dict.items()]) self.callback(**kwargs) def set_label(self, name, text='', side=Tkinter.LEFT): if not self.labels.has_key(name): label = Tkinter.Label(self, bd=1, relief=Tkinter.SUNKEN, anchor=Tkinter.W, font=self.font) self.add_widget(name, label, side) else: label = self.labels[name] label.config(text='%s:\t%s' % (name,text)) def add_widget(self, name, widget, side=Tkinter.LEFT): widget.pack(side=side) self.labels[name] = widget def _get_widget(self, name): if not self.labels.has_key(name): self.set_label(name, 'NoText') return self.labels[name] def update_all(self, reportstore = Null(), ids = []): self.set_label('Failed', str(len(reportstore.get(failed = True)))) self.set_label('Skipped', str(len(reportstore.get(skipped = True)))) self.set_label('Passed', str(len(reportstore.get(passed_item = True)))) root_report = reportstore.root if root_report.time < 7*24*60*60: self.set_label('Time', '%0.2f seconds' % root_report.time) else: self.set_label('Time', '%0.2f seconds' % 0.0) class ReportListBox(Tkinter.LabelFrame): font = myfont def __init__(self, *args, **kwargs): Tkinter.LabelFrame.__init__(self, *args, **kwargs) self.callback = Null() self.data = {} self.filter_kwargs = {'skipped': True, 'failed': True} self.label = Tkinter.Label(self) self.label.configure(font = self.font, width = 80, anchor = Tkinter.W) self.configure(labelwidget=self.label) self.createwidgets() self.label.configure(text = 'Idle') self.configure(font = myfont) def createwidgets(self): self.listbox = Tkinter.Listbox(self, foreground='red', selectmode=Tkinter.SINGLE, font = self.font) self.scrollbar = Tkinter.Scrollbar(self, command=self.listbox.yview) self.scrollbar.pack(side = Tkinter.RIGHT, fill = Tkinter.Y, anchor = Tkinter.N) self.listbox.pack(side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = Tkinter.YES, anchor = Tkinter.NW) self.listbox.configure(yscrollcommand = self.scrollbar.set, bg = 'White',selectbackground= 'Red', takefocus= Tkinter.YES) def set_callback(self, callback): self.callback = callback self.listbox.bind('', self.do_callback) def do_callback(self, *args): report_ids = [self.data[self.listbox.get(int(item))] for item in self.listbox.curselection()] for report_id in report_ids: self.callback(report_id) def set_filter_kwargs(self, **kwargs): self.filter_kwargs = kwargs def update_label(self, report): label = report.path if not label: label = 'Idle' self.label.configure(text = label) def update_list(self, reportstore): reports = reportstore.get(**self.filter_kwargs) old_selections = [self.listbox.get(int(sel)) for sel in self.listbox.curselection()] self.listbox.delete(0, Tkinter.END) self.data = {} for report in reports: label = '%s: %s' % (report.status, report.label) self.data[label] = report.full_id self.listbox.insert(Tkinter.END, label) if label in old_selections: self.listbox.select_set(Tkinter.END) self.listbox.see(Tkinter.END) class LabelEntry(Tkinter.Frame): font = myfont def __init__(self, *args, **kwargs): Tkinter.Frame.__init__(self, *args, **kwargs) self.label = Tkinter.Label(self) self.label.configure(font = self.font) self.label.pack(side = Tkinter.LEFT) self.entry = Tkinter.Entry(self) self.entry.configure(font = self.font) self.entry.pack(side = Tkinter.LEFT, expand = Tkinter.YES, fill = Tkinter.X) def update(self, reportstore = Null(), ids = []): bgcolor = 'White' fgcolor = 'Black' root_status = reportstore.root.status if root_status == reportstore.ReportClass.Status.Passed(): bgcolor = 'Green' elif root_status == reportstore.ReportClass.Status.Failed(): bgcolor = 'Red' fgcolor = 'White' elif root_status == reportstore.ReportClass.Status.Skipped() : bgcolor = 'Yellow' self.entry.configure(bg = bgcolor, fg = fgcolor) class ReportFrame(Tkinter.LabelFrame): font = myfont def __init__(self, *args, **kwargs): Tkinter.LabelFrame.__init__(self, *args, **kwargs) self.report = Null() self.label = Tkinter.Label(self,foreground="red", justify=Tkinter.LEFT) self.label.pack(anchor=Tkinter.W) self.label.configure(font = self.font) self.configure(labelwidget = self.label) self.text = ScrolledText.ScrolledText(self) self.text.configure(bg = 'White', font = ('Helvetica', 11, 'normal')) self.text.tag_config('sel', relief=Tkinter.FLAT) self.text.pack(expand=Tkinter.YES, fill=Tkinter.BOTH, side = Tkinter.TOP) def set_report(self, report): self.report = report self.label.configure(text ='%s: %s' % (self.report.status, self.report.label), foreground="red", justify=Tkinter.LEFT) self.text['state'] = Tkinter.NORMAL self.text.delete(1.0, Tkinter.END) self.text.insert(Tkinter.END, self.report.error_report) self.text.yview_pickplace(Tkinter.END) self.text['state'] = Tkinter.DISABLED self.attacheditorhotspots(self.text) def clear(self): self.label.configure(text = '') self.text['state'] = Tkinter.NORMAL self.text.delete(1.0, Tkinter.END) self.text['state'] = Tkinter.DISABLED def launch_editor(self, file, line): editor = (py.std.os.environ.get('PYTHON_EDITOR', None) or py.std.os.environ.get('EDITOR_REMOTE', None) or os.environ.get('EDITOR', None) or "emacsclient --no-wait ") if editor: print "%s +%s %s" % (editor, line, file) #py.process.cmdexec('%s +%s %s' % (editor, line, file)) os.system('%s +%s %s' % (editor, line, file)) def attacheditorhotspots(self, text): # Attach clickable regions to a Text widget. filelink = re.compile(r"""\[(?:testcode\s*:)?\s*(.+):(\d+)\]""") skippedlink = re.compile(r"""in\s+(/.*):(\d+)\s+""") lines = text.get('1.0', Tkinter.END).splitlines(1) if not lines: return tagname = '' start, end = 0,0 for index, line in enumerate(lines): match = filelink.search(line) if match is None: match = skippedlink.search(line) if match is None: continue file, line = match.group(1, 2) start, end = match.span() tagname = "ref%d" % index text.tag_add(tagname, "%d.%d" % (index + 1, start), "%d.%d" % (index + 1, end)) text.tag_bind(tagname, "", lambda e, n=tagname: e.widget.tag_config(n, underline=1)) text.tag_bind(tagname, "", lambda e, n=tagname: e.widget.tag_config(n, underline=0)) text.tag_bind(tagname, "", lambda e, self=self, f=file, l=line: self.launch_editor(f, l)) class MinimalButton(Tkinter.Button): format_string = '%dF / %dS / %dP' def __init__(self, *args, **kwargs): Tkinter.Button.__init__(self, *args, **kwargs) self.configure(text = 'Start/Stop') #self.format_string % (0,0,0)) def update_label(self, reportstore): failed = len(reportstore.get(failed = True)) skipped = len(reportstore.get(skipped = True)) passed = len(reportstore.get(passed_item = True)) #self.configure(text = self.format_string % (failed, skipped, passed)) bgcolor = 'Green' fgcolor = 'Black' highlightcolor = 'Green', activebackground= 'Green', activeforeground = 'Black' if skipped > 0: bgcolor = 'Yellow' activebackground = 'Yellow' if failed > 0: bgcolor = 'Red' fgcolor = 'White' activebackground= 'Red', activeforeground = 'White' self.configure(bg = bgcolor, fg = fgcolor, highlightcolor = highlightcolor, activeforeground = activeforeground, activebackground = activebackground) class LayoutManager: def __init__(self): self.current_layout = 0 self.layout_dict = {} def add(self, widget, layout_ids, **kwargs): for id in layout_ids: list = self.layout_dict.setdefault(id, []) list.append((widget, kwargs)) def set_layout(self, layout = 0): if self.current_layout != layout: widget_list = self.layout_dict.get(self.current_layout, []) for widget, kwargs in widget_list: widget.pack_forget() self.current_layout = layout widget_list = self.layout_dict.get(self.current_layout, []) for widget, kwargs in widget_list: widget.pack(**kwargs) class TkGui: font = ('Helvetica', 9, 'normal') def __init__(self, parent, config): self._parent = parent self._config = config self._should_stop = False #XXX needed? self._paths = [] self.backend = backend.ReportBackend(config) self._layoutmanager = LayoutManager() self._layout_toggle_var = Tkinter.StringVar() self._layout_toggle_var.set('<') # events self.on_report_updated = Event() self.on_report_store_updated = Event() self.on_show_report = Event() self.on_run_tests = Event() self.createwidgets() self.timer_update() self.report_window = Null() self.report_frame = Null() def createwidgets(self): add = self._layoutmanager.add self._buttonframe = Tkinter.Frame(self._parent) self._layoutbutton = Tkinter.Button(self._buttonframe, padx=0, relief=Tkinter.GROOVE, textvariable=self._layout_toggle_var, command=self.toggle_layout) add(self._layoutbutton, [0,1], side= Tkinter.LEFT) self._entry = LabelEntry(self._buttonframe) self._entry.label.configure(text = 'Enter test name:') self._entry.entry.bind('', self.start_tests) add(self._entry, [0], side = Tkinter.LEFT, fill = Tkinter.X, expand = Tkinter.YES) self._run_stop_button = Tkinter.Button(self._buttonframe, text = 'Run', command = self.toggle_action, font = self.font) add(self._run_stop_button, [0], side = Tkinter.RIGHT) self._minimalbutton = MinimalButton(self._buttonframe, font = self.font, command = self.toggle_action) add(self._minimalbutton, [1], side = Tkinter.RIGHT, fill = Tkinter.X, expand = Tkinter.YES) add(self._buttonframe, [0,1], side = Tkinter.TOP, fill = Tkinter.X) self._statusbar = StatusBar(self._parent) self._statusbar.set_filter_args_callback(self.filter_args_changed) add(self._statusbar, [0,1], side= Tkinter.BOTTOM, fill=Tkinter.X) self._reportlist = ReportListBox(self._parent) add(self._reportlist, [0], side = Tkinter.TOP, fill = Tkinter.BOTH, expand = Tkinter.YES) self._reportlist.set_callback(self.show_report) self._report_frame = ReportFrame(self._parent) add(self._report_frame, [0], side = Tkinter.TOP, fill = Tkinter.BOTH, expand = Tkinter.YES) #self.on_show_report.subscribe(self.show_report) self.on_report_updated.subscribe(self._reportlist.update_label) self.on_report_store_updated.subscribe(self._reportlist.update_list) self.on_report_store_updated.subscribe(self._minimalbutton.update_label) self.on_report_store_updated.subscribe(self.update_status) self._layoutmanager.set_layout(0) self.update_status(self.backend.get_store()) self.messages_callback(None) def toggle_layout(self): if self._layout_toggle_var.get() == '>': self._layout_toggle_var.set('<') self._layoutmanager.set_layout(0) else: self._layout_toggle_var.set('>') self._layoutmanager.set_layout(1) self._parent.geometry('') def filter_args_changed(self, **kwargs): self._reportlist.set_filter_kwargs(**kwargs) self._reportlist.update_list(self.backend.get_store()) def show_report(self, report_id): if report_id is None: self._report_frame.clear() else: report = self.backend.get_store().get(id = report_id)[0] self._report_frame.set_report(report) def show_error_window(self, report_id): report = self.backend.get_store().get(id = report_id)[0] if not self.report_window: self.report_window = Tkinter.Toplevel(self._parent) self.report_window.protocol('WM_DELETE_WINDOW', self.report_window.withdraw) b = Tkinter.Button(self.report_window, text="Close", command=self.report_window.withdraw) b.pack(side=Tkinter.BOTTOM) #b.focus_set() self.report_frame = ReportFrame(self.report_window) self.report_frame.pack() elif self.report_window.state() != Tkinter.NORMAL: self.report_window.deiconify() self.report_window.title(report.label) self.report_frame.set_report(report) def timer_update(self): self.backend.update() if self.backend.running: action = 'Stop' else: action = 'Run' self._run_stop_button.configure(text = action) self._minimalbutton.configure(text = action) self._parent.after(200, self.timer_update) def start_tests(self, dont_care_event=None): if self.backend.running: return paths = [path.strip() for path in self._entry.entry.get().split(',')] self.backend.set_messages_callback(self.messages_callback) self.backend.set_message_callback(self.message_callback) self.backend.start_tests(args = paths) self.show_report(None) def update_status(self, reportstore): self._statusbar.update_all(reportstore = reportstore, ids = []) self._entry.update(reportstore = reportstore, ids = []) def messages_callback(self, report_ids): if not report_ids: return self.on_report_store_updated(self.backend.get_store()) def message_callback(self, report_id): if report_id is None: report = Null() else: report = self.backend.get_store().get(id = report_id)[0] self.on_report_updated(report) def set_paths(self, paths): self._paths = paths self._entry.entry.insert(Tkinter.END, ', '.join(paths)) def toggle_action(self): if self.backend.running: self.stop() else: self.start_tests() def stop(self): self.backend.shutdown() self.backend.update() self.messages_callback([None]) self.message_callback(None) def shutdown(self): self.should_stop = True self.backend.shutdown() py.std.sys.exit()