class Select_Py_Version(_Dialog): """ Select_Py_Version is a tkinter pop-up dialog used to select a python interpreter for use with Tk_Nosy. """ def body(self, master): dialogframe = Frame(master, width=300, height=300) dialogframe.pack() self.Label_1 = Label(dialogframe, text="Select Python Version") self.Label_1.pack() if self.dialogOptions: rbL = self.dialogOptions.get('rbL', ['No Options','No Options']) else: rbL = ['No Options', 'No Options'] self.RadioGroup1_StringVar = StringVar() self.RadioGroup1_StringVar.set(rbL[0]) self.RadioGroup1_StringVar_traceName = \ self.RadioGroup1_StringVar.trace_variable("w", self.RadioGroup1_StringVar_Callback) for rb in rbL: self.Radiobutton_1 = Radiobutton(dialogframe, text=rb, value=rb) self.Radiobutton_1.pack(anchor=W) self.Radiobutton_1.configure( variable=self.RadioGroup1_StringVar ) self.resizable(0, 0) # Linux may not respect this def RadioGroup1_StringVar_Callback(self, varName, index, mode): """When radio group selection changes, print message to CLI.""" print( "RadioGroup1_StringVar_Callback varName, index, mode", varName, index, mode ) print( " new StringVar value =", self.RadioGroup1_StringVar.get() ) def validate(self): """Validates and packages dialog selections prior to return to calling routine. set values in "self.result" dictionary for return """ self.result = {} # return a dictionary of results self.result["selection"] = self.RadioGroup1_StringVar.get() return 1 def apply(self): print( 'apply called')
class Select_Py_Version(_Dialog): """ Select_Py_Version is a tkinter pop-up dialog used to select a python interpreter for use with Tk_Nosy. """ def body(self, master): dialogframe = Frame(master, width=300, height=300) dialogframe.pack() self.Label_1 = Label(dialogframe, text="Select Python Version") self.Label_1.pack() if self.dialogOptions: rbL = self.dialogOptions.get('rbL', ['No Options', 'No Options']) else: rbL = ['No Options', 'No Options'] self.RadioGroup1_StringVar = StringVar() self.RadioGroup1_StringVar.set(rbL[0]) self.RadioGroup1_StringVar_traceName = \ self.RadioGroup1_StringVar.trace_variable("w", self.RadioGroup1_StringVar_Callback) for rb in rbL: self.Radiobutton_1 = Radiobutton(dialogframe, text=rb, value=rb) self.Radiobutton_1.pack(anchor=W) self.Radiobutton_1.configure(variable=self.RadioGroup1_StringVar) self.resizable(0, 0) # Linux may not respect this def RadioGroup1_StringVar_Callback(self, varName, index, mode): """When radio group selection changes, print message to CLI.""" print("RadioGroup1_StringVar_Callback varName, index, mode", varName, index, mode) print(" new StringVar value =", self.RadioGroup1_StringVar.get()) def validate(self): """Validates and packages dialog selections prior to return to calling routine. set values in "self.result" dictionary for return """ self.result = {} # return a dictionary of results self.result["selection"] = self.RadioGroup1_StringVar.get() return 1 def apply(self): print('apply called')
class ResolveIdleTime(Dialog): def body(self, master): """ !!! copy completions filter/listbox setup Assign time period [ time period entry ] to [ keyword combo box ] need: options['idle_delta'] options['keywords'] file to append idle time assigned actions options['idle_file'] file to append new keywords options['keywords_file'] """ self.idle_delta = self.options['idle_delta'] self.completions = self.options['keywords'] self.currfile = self.options['currfile'] self.tz = self.options['tz'] period_frame = Frame(master, background=BGCOLOR) period_frame.pack(side="top", fill="x", padx=4, pady=2) period_label = Label(period_frame, text=_("Assign"), bg=BGCOLOR) period_label.pack(side="left") self.time_period = StringVar(self) self.period_entry = Entry(period_frame, textvariable=self.time_period, highlightbackground=BGCOLOR) self.idletime = StringVar(self) self.idletime.set(fmt_period(self.idle_delta)) self.idle_label = Label(period_frame, textvariable=self.idletime, bg=BGCOLOR, takefocus=0) self.idle_label.pack(side="right", padx=2) of_label = Label(period_frame, text=_("of"), bg=BGCOLOR, takefocus=0) of_label.pack(side="right") self.period_entry.pack(side="left", fill="x", expand=1, padx=4) self.keyword_frame = keyword_frame = Frame(master, background=BGCOLOR) keyword_frame.pack(side="top", fill="both", padx=4, expand=1) self.outcome = StringVar(self) self.outcome.set("") self.outcome_label = Label(keyword_frame, textvariable=self.outcome, bg=BGCOLOR, takefocus=0) self.outcome_label.pack(side="bottom") self.filterValue = StringVar(self) self.filterValue.set("") self.filterValue.trace_variable("w", self.setCompletions) self.fltr = Entry(self.keyword_frame, textvariable=self.filterValue, highlightbackground=BGCOLOR) self.fltr.pack(fill="x") self.fltr.icursor(END) self.listbox = listbox = Listbox( self.keyword_frame, exportselection=False, width=self.parent.options['completions_width']) listbox.pack(fill="both", expand=True, padx=2, pady=2) self.keyword_frame.bind("<Double-1>", self.apply) self.keyword_frame.bind("<Return>", self.apply) self.listbox.bind("<Up>", self.cursorUp) self.listbox.bind("<Down>", self.cursorDown) self.fltr.bind("<Up>", self.cursorUp) self.fltr.bind("<Down>", self.cursorDown) self.setCompletions() return self.period_entry def setCompletions(self, *args): match = self.filterValue.get() self.matches = matches = [ x for x in self.completions if x and x.lower().startswith(match.lower()) ] self.listbox.delete(0, END) for item in matches: self.listbox.insert(END, item) self.listbox.select_set(0) self.listbox.see(0) def cursorUp(self, event=None): cursel = int(self.listbox.curselection()[0]) newsel = max(0, cursel - 1) self.listbox.select_clear(cursel) self.listbox.select_set(newsel) self.listbox.see(newsel) return "break" def cursorDown(self, event=None): cursel = int(self.listbox.curselection()[0]) newsel = min(len(self.matches) - 1, cursel + 1) self.listbox.select_clear(cursel) self.listbox.select_set(newsel) self.listbox.see(newsel) return "break" def apply(self): """ Make sure values are ok, write action and update idle time """ period_str = self.period_entry.get() keyword_str = self.matches[int(self.listbox.curselection()[0])] if not (period_str and keyword_str): return try: period = parse_period(period_str) except: self.outcome.set( _("Could not parse period: {0}").format(period_str)) return hsh = { 'itemtype': '~', '_summary': 'idle time', 's': get_current_time(), 'e': period, 'k': keyword_str, 'z': self.tz } self.parent.loop.append_item(hsh, self.currfile) self.outcome.set( _("assigned {0} to {1}").format(fmt_period(period), keyword_str)) self.time_period.set("") self.idle_delta -= period self.idletime.set(fmt_period(self.idle_delta)) if self.idle_delta < ONEMINUTE: self.cancel() def ok(self, event=None): self.apply()
class FileChoice(object): def __init__(self, master=None, title=None, prefix=None, list=[], start='', ext="txt", new=False): self.master = master self.value = None self.prefix = prefix self.list = list if prefix and start: self.start = relpath(start, prefix) else: self.start = start self.ext = ext self.new = new self.modalPane = Toplevel(self.master, highlightbackground=BGCOLOR, background=BGCOLOR) if master: logger.debug('winfo: {0}, {1}; {2}, {3}'.format( master.winfo_rootx(), type(master.winfo_rootx()), master.winfo_rooty(), type(master.winfo_rooty()))) self.modalPane.geometry( "+%d+%d" % (master.winfo_rootx() + 50, master.winfo_rooty() + 50)) self.modalPane.transient(self.master) self.modalPane.grab_set() self.modalPane.bind("<Return>", self._choose) self.modalPane.bind("<Escape>", self._cancel) if title: self.modalPane.title(title) if new: nameFrame = Frame(self.modalPane, highlightbackground=BGCOLOR, background=BGCOLOR) nameFrame.pack(side="top", padx=18, pady=2, fill="x") nameLabel = Label(nameFrame, text=_("file:"), bd=1, relief="flat", anchor="w", padx=0, pady=0, highlightbackground=BGCOLOR, background=BGCOLOR) nameLabel.pack(side="left") self.fileName = StringVar(self.modalPane) self.fileName.set("untitled.{0}".format(ext)) self.fileName.trace_variable("w", self.onSelect) self.fname = Entry(nameFrame, textvariable=self.fileName, bd=1, highlightbackground=BGCOLOR) self.fname.pack(side="left", fill="x", expand=1, padx=0, pady=0) self.fname.icursor(END) self.fname.bind("<Up>", self.cursorUp) self.fname.bind("<Down>", self.cursorDown) filterFrame = Frame(self.modalPane, highlightbackground=BGCOLOR, background=BGCOLOR) filterFrame.pack(side="top", padx=18, pady=4, fill="x") filterLabel = Label(filterFrame, text=_("filter:"), bd=1, relief="flat", anchor="w", padx=0, pady=0, highlightbackground=BGCOLOR, background=BGCOLOR) filterLabel.pack(side="left") self.filterValue = StringVar(self.modalPane) self.filterValue.set("") self.filterValue.trace_variable("w", self.setMatching) self.fltr = Entry(filterFrame, textvariable=self.filterValue, bd=1, highlightbackground=BGCOLOR) self.fltr.pack(side="left", fill="x", expand=1, padx=0, pady=0) self.fltr.icursor(END) prefixFrame = Frame(self.modalPane, highlightbackground=BGCOLOR, background=BGCOLOR) prefixFrame.pack(side="top", padx=8, pady=2, fill="x") self.prefixLabel = Label(prefixFrame, text=_("{0}:").format(prefix), bd=1, highlightbackground=BGCOLOR, background=BGCOLOR) self.prefixLabel.pack(side="left", expand=0, padx=0, pady=0) buttonFrame = Frame(self.modalPane, highlightbackground=BGCOLOR, background=BGCOLOR) buttonFrame.pack(side="bottom", padx=10, pady=2) chooseButton = Button(buttonFrame, text="Choose", command=self._choose, highlightbackground=BGCOLOR, background=BGCOLOR, pady=2) chooseButton.pack(side="right", padx=10) cancelButton = Button(buttonFrame, text="Cancel", command=self._cancel, highlightbackground=BGCOLOR, background=BGCOLOR, pady=2) cancelButton.pack(side="left") selectionFrame = Frame(self.modalPane, highlightbackground=BGCOLOR, background=BGCOLOR) selectionFrame.pack(side="bottom", padx=8, pady=2, fill="x") self.selectionValue = StringVar(self.modalPane) self.selectionValue.set("") self.selection = Label(selectionFrame, textvariable=self.selectionValue, bd=1, highlightbackground=BGCOLOR, background=BGCOLOR) self.selection.pack(side="left", fill="x", expand=1, padx=0, pady=0) listFrame = Frame(self.modalPane, highlightbackground=BGCOLOR, background=BGCOLOR, width=40) listFrame.pack(side="top", fill="both", expand=1, padx=5, pady=2) scrollBar = Scrollbar(listFrame, width=8) scrollBar.pack(side="right", fill="y") self.listBox = Listbox(listFrame, selectmode=BROWSE, width=36) self.listBox.pack(side="left", fill="both", expand=1, ipadx=4, padx=2, pady=0) self.listBox.bind('<<ListboxSelect>>', self.onSelect) self.listBox.bind("<Double-1>", self._choose) self.modalPane.bind("<Return>", self._choose) self.modalPane.bind("<Escape>", self._cancel) # self.modalPane.bind("<Up>", self.cursorUp) # self.modalPane.bind("<Down>", self.cursorDown) self.fltr.bind("<Up>", self.cursorUp) self.fltr.bind("<Down>", self.cursorDown) scrollBar.config(command=self.listBox.yview) self.listBox.config(yscrollcommand=scrollBar.set) self.setMatching() def ignore(self, e=None): return "break" def onSelect(self, *args): # Note here that Tkinter passes an event object to onselect() if self.listBox.curselection(): firstIndex = self.listBox.curselection()[0] value = self.matches[int(firstIndex)] r = value[1] p = os.path.join(self.prefix, r) if self.new: if os.path.isfile(p): p = os.path.split(p)[0] r = os.path.split(r)[0] f = self.fileName.get() r = os.path.join(r, f) p = os.path.join(p, f) self.selectionValue.set(r) self.value = p return "break" def cursorUp(self, event=None): cursel = int(self.listBox.curselection()[0]) newsel = max(0, cursel - 1) self.listBox.select_clear(cursel) self.listBox.select_set(newsel) self.listBox.see(newsel) self.onSelect() return "break" def cursorDown(self, event=None): cursel = int(self.listBox.curselection()[0]) newsel = min(len(self.list) - 1, cursel + 1) self.listBox.select_clear(cursel) self.listBox.select_set(newsel) self.listBox.see(newsel) self.onSelect() return "break" def setMatching(self, *args): # disabled = "#BADEC3" # disabled = "#91CC9E" disabled = "#62B374" match = self.filterValue.get() if match: self.matches = matches = [ x for x in self.list if x and match.lower() in x[1].lower() ] else: self.matches = matches = self.list self.listBox.delete(0, END) index = 0 init_index = 0 for item in matches: if type(item) is tuple: # only show the label # (label, value, disabled)FF self.listBox.insert(END, item[0]) if self.new: if not item[-1]: self.listBox.itemconfig(index, fg=disabled) else: self.listBox.itemconfig(index, fg="blue") if self.start and item[1] == self.start: init_index = index else: if item[-1]: self.listBox.itemconfig(index, fg=disabled) else: self.listBox.itemconfig(index, fg="blue") if self.start and item[1] == self.start: init_index = index # elif files: else: self.listBox.insert(END, item) index += 1 self.listBox.select_set(init_index) self.listBox.see(init_index) self.fltr.focus_set() self.onSelect() def _choose(self, event=None): try: if self.listBox.curselection(): firstIndex = self.listBox.curselection()[0] if self.new: if not self.value or os.path.isfile(self.value): return else: tup = self.matches[int(firstIndex)] if tup[-1]: return self.value = os.path.join(self.prefix, tup[1]) else: return except IndexError: self.value = None self.modalPane.destroy() def _cancel(self, event=None): self.value = None self.modalPane.destroy() def returnValue(self): self.master.wait_window(self.modalPane) return self.value
from guis import settings import utils # Affectation de la langue par défaut si non présente if utils.isWindows(): if locale.getlocale(locale.LC_ALL) == (None, None): locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()[0][:2]) else: if locale.getlocale(locale.LC_CTYPE) == (None, None): locale.setlocale(locale.LC_CTYPE, locale.getdefaultlocale()[0][:2]) # Affectation de la langue d'utilisation en fonction des paramètres ou du système d'exploitation (français ou anglais) LOCALE = StringVar(value=locale.getlocale()[0][:2]) if LOCALE.get() != "fr" and LOCALE.get() != "en": LOCALE.set("en") LOCALE.trace_variable("w", lambda *args: settings.saveSettings()) class MSG(StringVar): def __init__(self, english_message: str, french_message: str): """ Constructeur d'un message localisé Le premier argument est le message en anglais, le second est en français """ super().__init__() self.en = english_message self.fr = french_message # Si un des messages est vide, l'autre est choisi if len(self.fr) == 0: self.fr = self.en
class App(Tk): STEP_MODE: IntVar # Пошаговое проигрывание анимации. SHOW_INFLECTION_POINTS: IntVar started = False # Мы начали искать корни, в это время нельзя менять менять уравнение. paused = False # Пауза для анимации. done: bool = False st: float en: float div: int eps: float lin_x: any # Множество точек для построения графика. lin_space_size: int = 400 # Кол-во точек для построения графика solver: HordEquationSolver expr: StringVar # Введенное пользователем выражение # График ax: plt.Axes plots: List[Line2D] = [] h_lines: List[Line2D] = [] # Горизонтальные линии main_plot: any = None deriv_plot: any = None inflection_points: any = None # Список решений solutions: List[Solution] = [] solution_ids: List[str] = [] tree: Treeview # Таблица результатов b_start: Button # Кнопка начала/остановки lin_space_label: Label # Надпись о точности графика cached_function: any # Декодированная функция, что бы каждый раз не вызывать eval after_job_id: any = None # id отложенного вызова функции для её отмены def f(self, x): return self.cached_function(x) def __init__(self, st: float, en: float, div: int, eps: float): super().__init__() self.st = st self.en = en self.div = div self.eps = eps self.lin_x = np.linspace( st, en, self.lin_space_size) # Множество точек для построения графика. fig = plt.Figure(tight_layout=True) self.ax = fig.add_subplot() self.ax.axhline(0, color='black') self.grid_columnconfigure(0, weight=2) self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(0, weight=1) self.fagg = FigureCanvasTkAgg(fig, self) self.fagg.get_tk_widget().grid(row=0, column=0, sticky='WNSE') self.frame = Frame(self) self.frame.grid(row=0, column=1, sticky='WNSE', rowspan=2) self.init_sidebar() self.solver = HordEquationSolver(st, en, div, eps, self.f) self.prepare() self.fagg.draw() button_frame = Frame(self) button_frame.grid(row=1, column=0, sticky='WE') self.b_start = Button(button_frame, text='start') self.b_start.pack(side='left', anchor='center') self.b_start.bind('<Button>', self.start) Button(button_frame, text='reset', command=self.reset).pack(side='left', anchor='center') def init_sidebar(self): self.cached_function = eval('lambda x: ' + DEFAULT_EXPRESSION) self.expr = StringVar(self) self.expr.set(DEFAULT_EXPRESSION) self.expr.trace_variable('w', self.var_debounce(self.expression_input)) # Динамические переменные для входных полей start_v = DoubleVar(self, value=self.st) end = DoubleVar(self, value=self.en) epsilon = DoubleVar(self, value=self.eps) divs = IntVar(self, value=self.div) lin_space_var = DoubleVar(self, value=math.log(self.lin_space_size, LOG_BASE)) variables = ((start_v, 'st'), (end, 'en'), (epsilon, 'eps'), (divs, 'div')) # Функция обертка для сигнализирования о смене переменной. def outer(var, var_name): def inner(*_args): try: self.params_input(var.get(), var_name) except Exception: pass return inner for (v, name) in variables: v.trace('w', self.debounce(outer(v, name), 250)) lin_debouncer = self.debounce(self.modify_lin_space_size, 150) lin_space_var.trace( 'w', lambda *_args: self.modify_lin_space_size_callback( lin_space_var.get(), lin_debouncer)) self.frame.columnconfigure(1, weight=2) Label(self.frame, text='Выражение:').grid(column=0, row=0, columnspan=2, sticky='EW') Entry(self.frame, textvariable=self.expr).grid(column=0, row=1, columnspan=2, sticky='EW') self.frame.rowconfigure(2, minsize=25) Label(self.frame, text='Начало').grid(column=0, row=3, sticky='W') Entry(self.frame, textvariable=start_v).grid(column=1, row=3, sticky='EW') Label(self.frame, text='Конец').grid(column=0, row=4, sticky='W') Entry(self.frame, textvariable=end).grid(column=1, row=4, sticky='EW') Label(self.frame, text='Точность').grid(column=0, row=5, sticky='W') Entry(self.frame, textvariable=epsilon).grid(column=1, row=5, sticky='EW') Label(self.frame, text='Разделение').grid(column=0, row=6, sticky='W') Entry(self.frame, textvariable=divs).grid(column=1, row=6, sticky='EW') self.frame.rowconfigure(7, minsize=25) self.lin_space_label = Label( self.frame, text=f'Количество точек графика: {self.lin_space_size}') self.lin_space_label.grid(column=0, row=8, columnspan=2, sticky='W') w = Scale(self.frame, from_=math.log(5, LOG_BASE), to=math.log(MAX_LINE_SPACE_SIZE, LOG_BASE), resolution=0.1 / LOG_BASE, orient=HORIZONTAL, variable=lin_space_var) w.grid(column=0, row=9, columnspan=2, sticky='EW') self.tree = Treeview(self.frame) self.tree['columns'] = (1, 2, 3, 4, 5) self.tree.column('#0', width=35) self.tree.column(1, width=130, anchor='center') self.tree.column(2, width=80, anchor='center') self.tree.column(3, width=80, anchor='center') self.tree.column(4, width=80, anchor='center') self.tree.column(5, width=80, anchor='center') self.tree.heading('#0', text='№') self.tree.heading(1, text='Интервал') self.tree.heading(2, text='Корень') self.tree.heading(3, text='Значение') self.tree.heading(4, text='Итераций') self.tree.heading(5, text='Ошибка') self.tree.grid(column=0, row=10, columnspan=2, sticky='EWSN') self.STEP_MODE = IntVar(self, value=0) self.SHOW_INFLECTION_POINTS = IntVar(self, value=1) self.SHOW_INFLECTION_POINTS.trace( 'w', lambda *_args: self.redraw_main_plot(True)) Checkbutton(self.frame, text='Пошаговый режим', variable=self.STEP_MODE).grid(column=0, row=11, sticky='WS') Checkbutton(self.frame, text='Показывать точка перегиба', variable=self.SHOW_INFLECTION_POINTS)\ .grid(column=0, row=12, sticky='WS') def modify_lin_space_size_callback(self, value, callback): self.lin_space_label.configure( text=f'Количество точек графика: {round(LOG_BASE**value)}') callback(value) def modify_lin_space_size(self, size): self.lin_space_size = round(LOG_BASE**size) self.redraw_main_plot(True) def redraw_main_plot(self, draw=False): if self.st != self.lin_x.min() or self.en != self.lin_x.max( ) or self.lin_space_size != len(self.lin_x): self.lin_x = np.linspace(self.st, self.en, self.lin_space_size) if self.main_plot: self.main_plot.remove() if self.deriv_plot: self.deriv_plot.remove() self.deriv_plot = None if self.inflection_points: self.inflection_points.remove() self.inflection_points = None v = self.f(self.lin_x) self.main_plot = self.ax.plot(self.lin_x, v, label='f(x)', color='tab:blue')[0] if self.SHOW_INFLECTION_POINTS.get(): v2 = np.diff(v) v2 = np.insert(v2, 0, v2[0] - (v2[1] - v2[0])) v2 /= ((self.en - self.st) / self.lin_space_size) v2 = np.diff(v2) second_derivative_is_zero = all([x < self.eps for x in v2]) if not second_derivative_is_zero: v2 = np.append(v2, v2[-1]) v2 /= ((self.en - self.st) / self.lin_space_size) inflection_points_x = [] inflection_points_y = [] for i in range(1, len(v2)): if v2[i - 1] * v2[i] <= 0: if v[i - 1] == 0: continue n = i - 1 if v2[i - 1] < v2[i] else i x = self.st + (self.en - self.st) / self.lin_space_size * n y = self.f(x) inflection_points_x.append(x) inflection_points_y.append(y) self.inflection_points = self.ax.scatter(inflection_points_x, inflection_points_y, 80, marker='x', color='violet') self.deriv_plot = self.ax.plot(self.lin_x, v2, label="f''(x)", color='tab:green')[0] mx_y = max(v) mn_y = min(v) m = (mx_y - mn_y) / 50 dx = abs(self.en - self.st) * 0.05 self.ax.set_ylim(mn_y - m, mx_y + m) self.ax.set_xlim(self.st - dx, self.en + dx) if draw: self.fagg.draw() # Первичное отображение, подготавливает все данные для него. def prepare(self): step = (self.en - self.st) / self.div self.redraw_main_plot() for plt in self.plots: plt.remove() self.plots.clear() for line in self.h_lines: line.remove() self.h_lines.clear() for i in range(self.div + 1): x = (self.st + step * i) self.h_lines.append(self.ax.axvline(x, color='black')) self.plots = [ self.ax.plot(self.lin_x, self.solver.hord.k * self.lin_x + self.solver.hord.b, label='lin', color='tab:orange')[0], self.ax.scatter([self.solver.p1.x], [self.solver.p1.y], marker='o', color='red'), self.ax.scatter([self.solver.p_fixed.x], [self.solver.p_fixed.y], marker='o', color='blue') ] self.fagg.draw() def var_debounce(self, func: Callable[[str], None], t: int = 500) -> Callable: def inner(*args): func(self.tk.globalgetvar(args[0])) return self.debounce(inner, t) def debounce(self, func: Callable[..., None], t: int = 500) -> Callable: return Debouncer(self, func, t) def expression_input(self, value): try: self.cached_function = eval('lambda x: ' + value) self.reset() except Exception as e: # traceback.print_exc() # print(e) pass def params_input(self, value, var_name): self.__setattr__(var_name, value) self.reset() def reset(self): if self.after_job_id: self.after_cancel(self.after_job_id) self.started = False self.done = False self.tree.delete(*self.solution_ids) self.solutions.clear() self.solution_ids.clear() self.solver = HordEquationSolver(self.st, self.en, self.div, self.eps, self.f) self.b_start.configure(text='start') self.b_start.bind('<Button>', self.start) self.prepare() def start(self, event): if self.STEP_MODE.get(): self.step_solve() else: if not self.started or self.paused: self.started = True self.paused = False self.step_solve() event.widget.configure(text='stop') event.widget.bind('<Button>', self.stop) def stop(self, event): self.paused = True if self.after_job_id: self.after_cancel(self.after_job_id) event.widget.configure(text='start') event.widget.bind('<Button>', self.start) # Перерисовка хорды и точек пересечения. def redraw_solution(self): for pt in self.plots: pt.remove() self.plots = [ self.ax.plot(self.lin_x, self.solver.hord.k * self.lin_x + self.solver.hord.b, label='lin', color='tab:orange')[0], self.ax.scatter([self.solver.p1.x], [self.solver.p1.y], marker='o', color='red'), self.ax.scatter([self.solver.p_fixed.x], [self.solver.p_fixed.y], marker='o', color='blue') ] self.fagg.draw() self.ax.relim() def step_solve(self): if self.started and not self.solver.done or self.STEP_MODE.get(): status = self.solver.next_step() self.redraw_solution() if not status: if self.started and not self.paused and not self.STEP_MODE.get( ): self.after_job_id = self.after(100, self.step_solve) else: self.add_solution(self.solver.get_solution()) if self.solver.next_segment(): print('DONE!!!!') self.b_start.configure(text='DONE!!!!') self.b_start.unbind('<Button>') else: self.redraw_solution() if not self.STEP_MODE.get(): self.after_job_id = self.after(200, self.step_solve) def add_solution(self, sol): if sol.err == 0: interval = f'({sol.interval[0]:.5} : {sol.interval[1]:.5})' iid = self.tree.insert('', 'end', text=str(len(self.solutions) + 1), values=(interval, f'{sol.x:.7}', f'{sol.y:.5}', sol.iter, sol.err)) else: iid = self.tree.insert('', 'end', text=str(len(self.solutions) + 1), values=('', '', '', '', sol.err)) self.solutions.append(sol) self.solution_ids.append(iid)
class Model: def __init__(self, config): self.config = config self.db_file = self.config['filepaths']['tinydb'] self.packs = [] self.current_pack = None self.standard_sets = config['sets_standard'] self.wild_sets = config['sets_wild'] self.acronyms = dict(config['sets_wild'].items()) self.acronyms.update(config['sets_standard'].items()) self.image = None # ~~ Image saving view ~~ # StringVar will hold the name of the current subpage self.current_subpage = StringVar() # ~~ Card pack subpage ~~ self.quantities = {} self.notes = StringVar() # defaults to empty string self.card_set = StringVar() # ~~ Arena subpage ~~ # Todo: may want to group rewards up so call call model.rewards.gold for example self.reward_quantities = {} self.reward_dust = IntVar() self.reward_gold = IntVar() self.reward_packs = IntVar() self.arena_wins = IntVar() self.arena_losses = IntVar() # ~~ End of Season subpage ~~ self.end_rank = IntVar() self.max_rank = IntVar() # ~~ Other subpage ~~ self.output_names = { self.config.get(item[0], 'name', fallback=item[0]): item[0] for item in self.config.items('output') } self.other_pages = [ self.config.get(item[0], 'name', fallback=item[0]) for item in self.config.items('output') if item[0] not in [ 'packs', 'arena', 'rewards', ] ] self.selected_folder = StringVar() # ~~ Stats view ~~ self.viewed_total_quantities = { } # quantities for each rarity, for stats view self.viewed_mean_quantities = {} self.view_card_set = StringVar() # card set, as deemed by stats view self.viewed_total_packs = StringVar() # packs for stats view card set self.quantities = {rarity: IntVar() for rarity in Hearthstone.rarities} self.viewed_total_quantities = { rarity: IntVar() for rarity in Hearthstone.rarities } self.viewed_mean_quantities = { rarity: StringVar() for rarity in Hearthstone.rarities } self.enchant_value = StringVar() self.disenchant_value = StringVar() # ~~ Pity view ~~ # NOTE: hard coding pity values in here # G.Epics and G.Legendaries are conservative best estimates self.pity_max = dict( zip(Hearthstone.rarities[2:], [10, 40, 25, 30, 150, 350])) self.pity_card_set = StringVar() self.pity_total_packs = StringVar() # We ignore common and rare from our pity timers, they are guaranteed almost every pack self.pity_current_timers = { rarity: StringVar() for rarity in Hearthstone.rarities[2:] } self.pity_card_set.trace_variable('w', self.extract_timers) self.pity_advice = StringVar() self.find_images() self.next_pack() def bind_graph_update_function(self, function): self.view_card_set.trace_variable('w', function) def find_images(self): """Finds all the screenshots on the desktop that might be packs, and stores them""" desktop_path = self.config['filepaths']['desktop'] for file in os.listdir(desktop_path): if file.endswith(".png") and file.startswith("Hearthstone"): # Todo: signature of CardPack might change self.packs.append(CardPack(file, desktop_path)) # reverse so pop can be used to get in order self.packs.sort(key=lambda pack: pack.sortkey, reverse=True) def next_pack(self): # closing images once we're done with them if self.current_pack: self.image.close() # moves onto the next pack in the list, updating image as well try: self.current_pack = self.packs.pop() self.image = Image.open(self.current_pack.full_path) except IndexError: # no more packs to process self.current_pack = None # Todo: handle the no-more pack case more deftly # could use a BooleanVar flag for 'model has finished' self.reset_variables() def reset_variables(self): # Resetting some variables back to default values for rarity in Hearthstone.rarities: default = Hearthstone.default_pack[rarity] self.quantities[rarity].set(default) self.notes.set('') # Note: self.card_set is not reset, assumes multiple packs of same set most likely # TODO: this model-level validation is the last bastion # --> need not be the only one, if the Controller can do it, might be worth def is_valid_pack(self): # Todo: ideally we'd throw the user some information as to why it fails # --> Not core requirement, I will know what's up if (self.card_set.get() not in self.standard_sets) and \ (self.card_set.get() not in self.wild_sets): # CARD SET NOT SELECTED return False total_cards = sum([val.get() for val in self.quantities.values()]) if total_cards != 5: # NOT ENOUGH CARDS return False if self.quantities['common'].get() == 5: # ALL COMMONS NOT VALID return False if (self.quantities['legendary'].get() + self.quantities['golden_epic'].get() + self.quantities['golden_legendary'].get() > 0) and (self.notes.get() == ''): # NOTEWORTHY CARD NOT NOTED return False return True # TODO: test # TinyDB file should be updated with the pack data # TinyDB should be using the correct table for the data # image should be moved to the correct folder, according to the word of the config # directories should be made if not present # TODO: storing the data to the database, and moving the file # should be separate both from each other and from the main 'submit' method # imagine we were moving both into web-based databasing # Should the correct submission process be the responsibility of Model or Controller? def submit(self): # commits pack data to tinydb file # makes no attempt to validate, assumes you have sorted this # Todo: check the current subpage, and modify behaviour accordingly current_page = self.current_subpage.get() if current_page == "Packs": self.submit_cardpack() elif current_page == "Arena2": self.submit_arena() elif current_page == "Season End5": self.submit_eos() elif current_page == "Other": self.submit_other() else: # TODO raise some form of error, unrecognised subpage pass def submit_cardpack(self): with TinyDB(self.db_file) as db: card_table = db.table('card_data') card_table.insert({ 'date': self.current_pack.sortkey, **{ rarity: self.quantities[rarity].get() for rarity in Hearthstone.rarities }, 'notes': self.notes.get(), 'set': self.acronyms[self.card_set.get()], 'filename': self.current_pack.image_name }) dest_folder = self.fetch_destination('packs') destination = os.path.join(dest_folder, self.current_pack.image_name) print('\nFile to move: {}'.format(self.current_pack.full_path)) print('\nDestination set to {}. Continue?'.format(destination)) input() # REMOVE, testing hold self.image.close() shutil.move(self.current_pack.full_path, destination) # TODO: check this is needed # TODO: move to end of main submit function self.extract_data() self.next_pack() def submit_arena(self): pass def submit_eos(self): pass def submit_other(self): folder_selection = self.selected_folder.get() if folder_selection not in self.output_names: # TODO: proper handling print('Unknown output folder "{}"'.format(folder_selection)) return folder_name = self.output_names[folder_selection] dest_folder = self.fetch_destination(folder_name) destination = os.path.join(dest_folder, self.current_pack.image_name) print('\nFile to move: {}'.format(self.current_pack.full_path)) print('\nDestination set to {}. Continue?'.format(destination)) input() # REMOVE, testing hold self.image.close() shutil.move(self.current_pack.full_path, destination) # TODO: check this is needed # TODO: move to end of main submit function self.extract_data() self.next_pack() def not_pack(self): # "Not pack" will likely be removed and made into "skip". # Images for things other than packs will have a sub page of the storage screen self.next_pack() def fetch_destination(self, out_name): folder = self.config['output'][out_name] path_parts = [] if self.config.has_section(out_name): if self.config.getboolean(out_name, 'yearseperated', fallback=False): path_parts.append(str(self.current_pack.date.year)) if self.config.getboolean(out_name, 'monthseperated', fallback=False): path_parts.append(month_name[self.current_pack.date.month]) dest_folder = os.path.join(folder, *path_parts) os.makedirs(dest_folder, exist_ok=True ) # create folder, and all intermediary folders, if needed return dest_folder # Another alias. Can be cleaned up when refactoring if needed skip_image = next_pack def extract_data(self): # called when the view_card_set variable changes # looks at the new value, and determines if it is a set, or 'All Sets' # performs requires queries on the tinyDB, and updates any variables to these value # -->view will then change, as variables are updated set_name = self.view_card_set.get() if not set_name: return if set_name == 'Card Set': return elif set_name == 'All Sets': # case looking for all packs handled separately with TinyDB(self.db_file) as db: card_table = db.table('card_data') results = card_table.all() else: acronym = self.acronyms[set_name] pack = Query() with TinyDB(self.db_file) as db: card_table = db.table('card_data') results = card_table.search(pack['set'] == acronym) total_packs = len(results) dates = [ datetime(*[int(val) for val in result['date'].split('/')]) for result in results ] packs = list(range(1, total_packs + 1)) self.viewed_total_packs.set('{} Packs'.format(total_packs)) count = Counter() for pack_line in results: count.update({k: pack_line[k] for k in Hearthstone.rarities}) for rarity in Hearthstone.rarities: self.viewed_total_quantities[rarity].set(count[rarity]) if total_packs == 0: self.viewed_mean_quantities[rarity].set('###') else: self.viewed_mean_quantities[rarity].set('{:.3f}'.format( float(count[rarity]) / total_packs)) if total_packs == 0: self.enchant_value.set('Average Enchant\n') self.disenchant_value.set('Average Disenchant\n') else: self.enchant_value.set('Average Enchant\n{:.1f}'.format( float(value_enchant(count) / total_packs))) self.disenchant_value.set('Average Disenchant\n{:.1f}'.format( float(value_disenchant(count) / total_packs))) return dates, packs def extract_timers(self, *callback): # called when the pity_card_set variable changes # looks at new value, and gets new set # performs required db queries and updates variables set_name = self.pity_card_set.get() if set_name == 'Card Set': return else: acronym = self.acronyms[set_name] pack = Query() with TinyDB(self.db_file) as db: card_table = db.table('card_data') results = card_table.search(pack['set'] == acronym) self.pity_total_packs.set('{} Packs'.format(len(results))) # Using stringvars, so we can return "<current>/<max>" results.sort(key=lambda entry: entry['date'], reverse=True) for rarity in Hearthstone.rarities[2:]: # ignore <epics # Generator that enumerates the packs, and selects only those that contain rarity # then we take the first item with next which produces timer + pack (should we need that) # Using len(results) if no pack is found, ie all packs count timer, pack = next( (i for i in enumerate(results) if i[1][rarity] > 0), (len(results), None)) self.pity_current_timers[rarity].set('{}/{}'.format( timer, self.pity_max[rarity]))
class Timer: def __init__(self, parent): root.geometry("280x100") root.title("Таймер выключения пк") root.resizable(width=False, height=False) self.displaying_counting = Label(root, text="осталось", font=("Arial bold", 14)) self.displaying_counting.place(x=0, y=60) self.lbl_sec = Label(root, text="сек", font=("Arial bold", 14)) self.lbl_sec.place(x=135, y=0) self.lbl_min = Label(root, text="мин", font=("Arial bold", 14)) self.lbl_min.place(x=80, y=0) self.lbl_hour = Label(root, text="час", font=("Arial bold", 14)) self.lbl_hour.place(x=30, y=0) self.entry_seconds = Entry(parent, width=2) self.entry_seconds.place(x=120, y=5) self.entry_minutes = Entry(parent, width=2) self.entry_minutes.place(x=65, y=5) self.entry_hours = Entry(parent, width=4) self.entry_hours.place(x=5, y=5) self.imput_str_sec = StringVar() self.imput_str_min = StringVar() self.imput_str_hour = StringVar() callback = root.register(self.only_numeric_input_less_than_60) callback_hours = root.register(self.only_numeric_input) self.entry_seconds.configure(validate="key", validatecommand=(callback, "%P"), textvariable=self.imput_str_sec) self.entry_minutes.configure(validate="key", validatecommand=(callback, "%P"), textvariable=self.imput_str_min) self.entry_hours.configure(validate="key", validatecommand=(callback_hours, "%P"), textvariable=self.imput_str_hour) self.imput_str_sec.trace_variable("w", self.entry_set_call) self.imput_str_min.trace_variable("w", self.entry_set_call) self.imput_str_hour.trace_variable("w", self.entry_set_call) btn = Button(root, text="начать отсчет", command=self.clicked_start, relief=RAISED, overrelief=FLAT) btn.place(x=10, y=30) btn2 = Button(root, text="stop", command=self.stop_countdown, relief=RAISED, overrelief=FLAT) btn2.place(x=100, y=30) def logic(self, seconds, minutes, hours): "responsible for the countdown and turns off the computer" seconds = int(seconds) minutes = int(minutes) hours = int(hours) s = time.strftime("%S", time.localtime()) m = time.strftime("%M", time.localtime()) h = time.strftime("%H", time.localtime()) day = time.strftime("%d", time.localtime()) sec_at_the_end = seconds + int(s) min_at_the_end = minutes + int(m) hour_at_the_end = hours + int(h) day_at_the_end = day day_at_the_end = int(day_at_the_end) while True: if sec_at_the_end > 60: sec_at_the_end -= 60 min_at_the_end += 1 if min_at_the_end > 60: min_at_the_end -= 60 hour_at_the_end += 1 if hour_at_the_end > 24: hour_at_the_end -= 24 day_at_the_end += 1 else: break old_s = int(s) self.stop_flag = True self.off_pc_flag = False if self.stop_flag == True: while True: if seconds <= 0 and minutes > 0: seconds = 60 minutes -= 1 if minutes <= 0 and hours > 0: minutes = 60 hours -= 1 s = time.strftime("%S", time.localtime()) m = time.strftime("%M", time.localtime()) h = time.strftime("%H", time.localtime()) day = time.strftime("%d", time.localtime()) s = int(s) m = int(m) h = int(h) day = int(day) if s > old_s: seconds -= 1 old_s = s res = "осталось {}".format(hours) + "час {}".format( minutes) + "мин {}".format(int(seconds)) + "сек" self.displaying_counting.after( 100, self.displaying_counting.configure(text=res)) if sec_at_the_end <= s and min_at_the_end <= m and hour_at_the_end <= h and day_at_the_end <= day and self.off_pc_flag == True: self.displaying_counting.configure(text="конец") os.system('shutdown -s') break if self.stop_flag == False: self.displaying_counting.configure(text="осталось") break self.off_pc_flag = True root.update() def clicked_start(self): "logic buttom and displaying a countdown" seconds = self.entry_seconds.get() minutes = self.entry_minutes.get() hours = self.entry_hours.get() if seconds == "": seconds = 0 if minutes == "": minutes = 0 if hours == "": hours = 0 self.logic(seconds, minutes, hours) root.update() def entry_set_call(self, name, index, mode): "prohibits entering more than 2 digits, for hours 4" sec = self.imput_str_sec.get() min = self.imput_str_min.get() hour = self.imput_str_hour.get() if len(sec) > 2: self.imput_str_sec.set(sec[:-1]) if len(min) > 2: self.imput_str_min.set(min[:-1]) if len(hour) > 4: self.imput_str_hour.set(hour[:-1]) def only_numeric_input(self, P): "prohibits entering not numbers" if P.isdigit() or P == "": return True return False def only_numeric_input_less_than_60(self, P): "prohibits entering letters and numbers greater than 59. for seconds and minutes" if P.isdigit() and int(P) < 60 or P == "": return True return False def stop_countdown(self): "no explanation is required" self.stop_flag = False
""" Code illustration: 10.01 Tkinter Trace Variable Demo Tkinter GUI Application Development Blueprints """ from tkinter import Tk, Label, Entry, StringVar root = Tk() my_variable = StringVar() def trace_when_my_variable_written(var, indx, mode): print("Traced variable {}".format(my_variable.get())) my_variable.trace_variable("w", trace_when_my_variable_written) Label(root, textvariable=my_variable).pack(padx=5, pady=5) Entry(root, textvariable=my_variable).pack(padx=5, pady=5) root.mainloop()
class PopupFrame(Toplevel): # ОПРЕДЕЛЯЕМ ГЛАВНОЕ ОКНО для РАБОТЫ С ДАННЫМИ; def __init__(self): super().__init__(root) self.init_frame() self.view = appl def init_frame(self): self.resizable(False, False) # СТИЛЬ ШРИФТА; font_style = ('Consolas', '12') # НИЖНЯЯ ПАНЕЛЬ ОШИБОК; self.TB_M_ = Frame(self, bg='#EDF0F5', bd=1, relief=FLAT) self.LBL_1 = Label(self.TB_M_) self.TBbtn = Frame(self.TB_M_) self.LBL_2 = Label(self.TB_M_) self.TB_M_.pack(fill=X) self.LBL_1.pack(side=BOTTOM) self.TBbtn.pack(side=BOTTOM) self.LBL_2.pack(side=BOTTOM) # МЕТКИ, ПОЛЕ и РАСКРЫВАЮЩИЕСЯ СПИСКИ; kino = [ [ 'Кино', 'Сериал', 'Мультфильм' ], [ 'Боевик', 'Драма', 'Комедия', 'Приключения', 'Триллер', 'Ужасы', 'Фантастика', 'Эротика' ], [ 'Просмотрено', 'Непросмотрено' ] ] self.L_name = Label(self.TB_M_, text='Название') self.L_mors = Label(self.TB_M_, text='Кино/Сериал/Мультфильм') self.L_janr = Label(self.TB_M_, text='Жанр') self.L_view = Label(self.TB_M_, text='Просмотр') self.count_chars = Label(self.TB_M_, font=('Consolas', '7')) self.len_mx = StringVar() # Максимальная длина символов в поле; self.name__ = Entry(self.TB_M_, width=41, font=font_style, textvariable=self.len_mx) self.len_mx.trace_variable('w', self.def_max_count_chars) self.mors__ = Combobox(self.TB_M_, values=kino[0], width=39, font=font_style, state='readonly') self.janr__ = Combobox(self.TB_M_, values=kino[1], width=39, font=font_style, state='readonly') self.view__ = Combobox(self.TB_M_, values=kino[2], width=39, font=font_style, state='readonly') self.L_name.pack(side=TOP) self.name__.pack(side=TOP) self.L_mors.pack(side=TOP) self.mors__.pack(side=TOP) self.L_janr.pack(side=TOP) self.janr__.pack(side=TOP) self.L_view.pack(side=TOP) self.view__.pack(side=TOP) self.name__.focus() self.mors__.current(0) self.janr__.current(0) self.view__.current(0) # КНОПКА для ЗАКРЫТИЯ ОКНА; self.btn_can = Button(self.TBbtn, text='Закрыть', width=25, command=self.destroy) self.btn_can.pack(side=RIGHT) # УДЕРЖИВАЕМ НАШЕ ДИАЛОГОВОЕ ОКНО 'НА ВЕРХУ'; self.grab_set() self.focus_set() def def_max_count_chars(self, name, index, mode): msg = self.len_mx.get() self.count_chars['text'] = '%d/%d' % (len(self.name__.get()), 40) if len(msg) > 40: self.len_mx.set(msg[:-1]) #self.len_mx.set(msg[0:39]) print(self.len_mx.set(msg[0:39]))
class _Hatch_GUI(object): """ create a Hatch object from hatch_supt and to then create a skeleton python project. """ def __init__(self, master): self.initComplete = 0 self.master = master self.x, self.y, self.w, self.h = -1, -1, -1, -1 # bind master to <Configure> in order to handle any resizing, etc. # postpone self.master.bind("<Configure>", self.Master_Configure) self.master.bind('<Enter>', self.bindConfigure) self.master.title("PyHatch GUI") self.master.resizable(0, 0) # Linux may not respect this dialogframe = Frame(master, width=810, height=630) dialogframe.pack() self.Shortdesc_Labelframe = LabelFrame( dialogframe, text="Short Description (1-liner)", height="90", width="718") self.Shortdesc_Labelframe.place(x=60, y=127) helv20 = tkinter.font.Font(family='Helvetica', size=20, weight='bold') self.Buildproject_Button = Button(dialogframe, text="Build Project", width="15", font=helv20) self.Buildproject_Button.place(x=492, y=10, width=263, height=68) self.Buildproject_Button.bind("<ButtonRelease-1>", self.Buildproject_Button_Click) self.Selectdir_Button = Button(dialogframe, text="<Select Directory>", width="15") self.Selectdir_Button.place(x=72, y=585, width=672, height=31) self.Selectdir_Button.bind("<ButtonRelease-1>", self.Selectdir_Button_Click) self.Author_Entry = Entry(dialogframe, width="15") self.Author_Entry.place(x=228, y=424, width=187, height=21) self.Author_Entry_StringVar = StringVar() self.Author_Entry.configure(textvariable=self.Author_Entry_StringVar) self.Author_Entry_StringVar.set("John Doe") self.Classname_Entry = Entry(dialogframe, width="15") self.Classname_Entry.place(x=192, y=73, width=165, height=21) self.Classname_Entry_StringVar = StringVar() self.Classname_Entry.configure( textvariable=self.Classname_Entry_StringVar) self.Classname_Entry_StringVar.set("MyClass") self.Copyright_Entry = Entry(dialogframe, width="15") self.Copyright_Entry.place(x=228, y=478, width=461, height=21) self.Copyright_Entry_StringVar = StringVar() self.Copyright_Entry.configure( textvariable=self.Copyright_Entry_StringVar) self.Copyright_Entry_StringVar.set("Copyright (c) 2013 John Doe") self.Email_Entry = Entry(dialogframe, relief="sunken", width="15") self.Email_Entry.place(x=228, y=505, width=458, height=21) self.Email_Entry_StringVar = StringVar() self.Email_Entry.configure(textvariable=self.Email_Entry_StringVar) self.Email_Entry_StringVar.set("*****@*****.**") self.GithubUserName_Entry = Entry(dialogframe, relief="sunken", width="15") self.GithubUserName_Entry.place(x=228, y=539, width=458, height=21) self.GithubUserName_Entry_StringVar = StringVar() self.GithubUserName_Entry.configure( textvariable=self.GithubUserName_Entry_StringVar) self.GithubUserName_Entry_StringVar.set("github_user_name") self.Funcname_Entry = Entry(dialogframe, width="15") self.Funcname_Entry.place(x=192, y=100, width=157, height=21) self.Funcname_Entry_StringVar = StringVar() self.Funcname_Entry.configure( textvariable=self.Funcname_Entry_StringVar) self.Funcname_Entry_StringVar.set("my_function") # License values should be correct format LICENSE_OPTIONS = tuple(sorted(CLASSIFIER_D.keys())) self.License_Entry_StringVar = StringVar() self.License_Entry = OptionMenu(dialogframe, self.License_Entry_StringVar, *LICENSE_OPTIONS) self.License_Entry.place(x=552, y=424, width=184, height=21) self.License_Entry_StringVar.set(LICENSE_OPTIONS[0]) self.Mainpyname_Entry = Entry(dialogframe, width="15") self.Mainpyname_Entry.place(x=168, y=37, width=196, height=21) self.Mainpyname_Entry_StringVar = StringVar() self.Mainpyname_Entry.configure( textvariable=self.Mainpyname_Entry_StringVar) self.Mainpyname_Entry_StringVar.set("main.py") self.Projname_Entry = Entry(dialogframe, width="15") self.Projname_Entry.place(x=168, y=10, width=194, height=21) self.Projname_Entry_StringVar = StringVar() self.Projname_Entry.configure( textvariable=self.Projname_Entry_StringVar) self.Projname_Entry_StringVar.set("MyProject") self.Shortdesc_Entry = Entry(dialogframe, width="15") self.Shortdesc_Entry.place(x=72, y=150, width=688, height=48) self.Shortdesc_Entry_StringVar = StringVar() self.Shortdesc_Entry.configure( textvariable=self.Shortdesc_Entry_StringVar) self.Shortdesc_Entry_StringVar.set("My project does this") # Status must be correct format self.Status_Entry_StringVar = StringVar() self.Status_Entry = OptionMenu(dialogframe, self.Status_Entry_StringVar, *DEV_STATUS_OPTIONS) self.Status_Entry.place(x=228, y=451, width=183, height=21) self.Status_Entry_StringVar.set(DEV_STATUS_OPTIONS[0]) self.Version_Entry = Entry(dialogframe, width="15") self.Version_Entry.place(x=552, y=451, width=184, height=21) self.Version_Entry_StringVar = StringVar() self.Version_Entry.configure(textvariable=self.Version_Entry_StringVar) self.Version_Entry_StringVar.set("0.1.1") self.Author_Label = Label(dialogframe, text="Author", width="15") self.Author_Label.place(x=96, y=424, width=112, height=22) self.Classname_Label = Label(dialogframe, text="Class Name", width="15") self.Classname_Label.place(x=60, y=73, width=112, height=22) self.Copyright_Label = Label(dialogframe, text="Copyright", width="15") self.Copyright_Label.place(x=96, y=478, width=113, height=23) self.Email_Label = Label(dialogframe, text="Email", width="15") self.Email_Label.place(x=96, y=505, width=113, height=23) self.GithubUserName_Label = Label(dialogframe, text="GithubUserName", width="15") self.GithubUserName_Label.place(x=96, y=539, width=113, height=23) self.Funcname_Label = Label(dialogframe, text="Function Name", width="15") self.Funcname_Label.place(x=60, y=100, width=112, height=22) self.License_Label = Label(dialogframe, text="License", width="15") self.License_Label.place(x=432, y=424, width=113, height=23) self.Longdesc_Label = Label(dialogframe, text="Paragraph Description", width="15") self.Longdesc_Label.place(x=216, y=220, width=376, height=22) self.Mainpyname_Label = Label(dialogframe, text="Main Python File", width="15") self.Mainpyname_Label.place(x=48, y=37, width=112, height=22) self.Projname_Label = Label(dialogframe, text="Project Name", width="15") self.Projname_Label.place(x=48, y=10, width=112, height=22) self.Selectdir_Label = Label( dialogframe, text="Select the Directory Below Which to Place Your Project", width="15") self.Selectdir_Label.place(x=156, y=567, width=536, height=24) self.Status_Label = Label(dialogframe, text="Status", width="15") self.Status_Label.place(x=96, y=451, width=114, height=24) self.Version_Label = Label(dialogframe, text="Version", width="15") self.Version_Label.place(x=432, y=451, width=113, height=23) self.Isclass_Radiobutton = Radiobutton(dialogframe, text="Class Project", value="Class Project", width="15", anchor=W) self.Isclass_Radiobutton.place(x=320, y=73, width=135, height=27) self.RadioGroup1_StringVar = StringVar() self.RadioGroup1_StringVar.set("Class Project") self.RadioGroup1_StringVar_traceName = \ self.RadioGroup1_StringVar.trace_variable("w", self.RadioGroup1_StringVar_Callback) self.Isclass_Radiobutton.configure(variable=self.RadioGroup1_StringVar) self.Isfunction_Radiobutton = Radiobutton(dialogframe, text="Function Project", value="Function Project", width="15", anchor=W) self.Isfunction_Radiobutton.place(x=320, y=100, width=135, height=27) self.Isfunction_Radiobutton.configure( variable=self.RadioGroup1_StringVar) lbframe = Frame(dialogframe) self.Text_1_frame = lbframe scrollbar = Scrollbar(lbframe, orient=VERTICAL) self.Text_1 = Text(lbframe, width="40", height="6", yscrollcommand=scrollbar.set) scrollbar.config(command=self.Text_1.yview) scrollbar.pack(side=RIGHT, fill=Y) self.Text_1.pack(side=LEFT, fill=BOTH, expand=1) self.Text_1_frame.place(x=72, y=250, width=665, height=160) # >>>>>>insert any user code below this comment for section "top_of_init" self.dirname = '<Select Directory>' self.Funcname_Entry.config(state=DISABLED) h = Hatch(projName='MyProject', mainDefinesClass='N') if h.author: self.Author_Entry_StringVar.set(h.author) if h.proj_license: self.License_Entry_StringVar.set(h.proj_license) if h.proj_copyright: self.Copyright_Entry_StringVar.set(h.proj_copyright) if h.email: self.Email_Entry_StringVar.set(h.email) if h.github_user_name: self.GithubUserName_Entry_StringVar.set(h.github_user_name) del h def build_result_dict(self): """Takes user inputs from GUI and builds a dictionary of results""" # pylint: disable=W0201 self.result = {} # return a dictionary of results self.result["author"] = self.Author_Entry_StringVar.get() self.result["status"] = self.Status_Entry_StringVar.get() self.result["proj_license"] = self.License_Entry_StringVar.get() self.result["version"] = self.Version_Entry_StringVar.get() self.result["proj_copyright"] = self.Copyright_Entry_StringVar.get() self.result["email"] = self.Email_Entry_StringVar.get() self.result[ "github_user_name"] = self.GithubUserName_Entry_StringVar.get() self.result["main_py_name"] = self.Mainpyname_Entry_StringVar.get() self.result["proj_name"] = self.Projname_Entry_StringVar.get() self.result["class_name"] = self.Classname_Entry_StringVar.get() self.result["func_name"] = self.Funcname_Entry_StringVar.get() self.result["short_desc"] = self.Shortdesc_Entry_StringVar.get() self.result["para_desc"] = self.Text_1.get(1.0, END) self.result["parent_dir"] = self.dirname if self.RadioGroup1_StringVar.get() == "Class Project": self.result["is_class_project"] = 'Y' else: self.result["is_class_project"] = 'N' def Buildproject_Button_Click(self, event): """When clicked, this method gathers all the user inputs and builds the project skeleton in the directory specified. """ # tkinter requires arguments, but I don't use them # pylint: disable=W0613 # >>>>>>insert any user code below this comment for section "compID=29" # replace, delete, or comment-out the following #print "executed method Buildproject_Button_Click" if not os.path.isdir(self.dirname): ShowError( title='Need Parent Directory', message= 'You need to choose a directory in which to place your project.' ) else: self.build_result_dict() # builds self.result dict r = self.result h = Hatch(projName=r["proj_name"], mainDefinesClass=r["is_class_project"], mainPyFileName=r["main_py_name"], mainClassName=r["class_name"], mainFunctionName=r["func_name"], author=r["author"], proj_copyright=r["proj_copyright"], proj_license=r["proj_license"], version=r["version"], email=r["email"], status=r["status"], github_user_name=r["github_user_name"], simpleDesc=r["short_desc"], longDesc=r["para_desc"]) call_result = h.save_project_below_this_dir(self.dirname) if call_result == 'Success': if AskYesNo(title='Exit Dialog', message='Do you want to leave this GUI?'): self.master.destroy() else: ShowWarning( title='Project Build Error', message='Project did NOT build properly.\n'+\ 'Warning Message = (%s)'%call_result) # return a string containing directory name def AskDirectory(self, title='Choose Directory', initialdir="."): """Simply wraps the tkinter function of the "same" name.""" dirname = tkinter.filedialog.askdirectory(parent=self.master, initialdir=initialdir, title=title) return dirname # <-- string def Selectdir_Button_Click(self, event): # I don't care what the exception is, if there's a problem, bail # Also, I want to redefine the dirname attribute here # pylint: disable=W0702, W0201 # tkinter requires arguments, but I don't use them # pylint: disable=W0613 """Selects the directory in which to build project.""" dirname = self.AskDirectory(title='Choose Directory For Nose Tests', initialdir='.') if dirname: try: dirname = os.path.abspath(dirname) except: self.dirname = '<Select Directory>' return # let Alarm force dir selection self.dirname = dirname self.Selectdir_Button.config(text=self.dirname) def RadioGroup1_StringVar_Callback(self, varName, index, mode): # tkinter requires arguments, but I don't use them # pylint: disable=W0613 """Responds to changes in RadioGroup1_StringVar.""" if self.RadioGroup1_StringVar.get() == "Class Project": self.Funcname_Entry.config(state=DISABLED) self.Classname_Entry.config(state=NORMAL) else: self.Classname_Entry.config(state=DISABLED) self.Funcname_Entry.config(state=NORMAL) # tk_happy generated code. DO NOT EDIT THE FOLLOWING. section "Master_Configure" def bindConfigure(self, event): """bindConfigure and Master_Configure help stabilize GUI""" # tkinter requires arguments, but I don't use them # pylint: disable=W0613 if not self.initComplete: self.master.bind("<Configure>", self.Master_Configure) self.initComplete = 1 def Master_Configure(self, event): """bindConfigure and Master_Configure help stabilize GUI""" # >>>>>>insert any user code below this comment for section "Master_Configure" # replace, delete, or comment-out the following if event.widget != self.master: if self.w != -1: return x = int(self.master.winfo_x()) y = int(self.master.winfo_y()) w = int(self.master.winfo_width()) h = int(self.master.winfo_height()) if (self.x, self.y, self.w, self.h) == (-1, -1, -1, -1): self.x, self.y, self.w, self.h = x, y, w, h if self.w != w or self.h != h: print("Master reconfigured... make resize adjustments") self.w = w self.h = h
class CurrencyEditor(Frame): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.bvar_modified = BooleanVar() self.create_currency_ui() def create_currency_ui(self): self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.frm_currency = Frame(self) self.frm_currency.rowconfigure(2, weight=1) self.frm_currency.columnconfigure(0, weight=1) self.frm_currency.grid(padx=10, pady=10, sticky='nsew') # Tree Frame self.frm_tree = Frame(self.frm_currency) self.frm_tree.grid(row=2, sticky='nsew') self.frm_tree.rowconfigure(0, weight=1) self.frm_tree.columnconfigure(0, weight=1) self.tree = EditableTreeview(self.frm_tree, on_cell_update=self.onCellUpdate) scrly = AutoScrollbar(self.frm_tree, command=self.tree.yview) scrlx = AutoScrollbar(self.frm_tree, command=self.tree.xview, orient=HORIZONTAL) self.tree.config(yscrollcommand=scrly.set, xscrollcommand=scrlx.set) self.tree.grid(row=0, column=0, sticky='nsew') scrly.grid(row=0, column=1, sticky='nsew') scrlx.grid(row=1, column=0, sticky='nsew') self.tree.insert('', 0, text='Exalted Orb', values=('90', '85')) frm = Frame(self.frm_currency, relief=SOLID, borderwidth=2) frm.columnconfigure(10, weight=1) frm.grid(row=1, column=0, pady=(0, 5), sticky='nsew') # self.entry_currency = \ # Combobox_Autocomplete(frm, list_of_items=['one of many currencies'], startswith_match=False) self.search_var = StringVar() self.entry_search = PlaceholderEntry(frm, 'Search..', style='Default.TEntry', textvariable=self.search_var) self.search_var.trace_variable( 'w', lambda a, b, c: self.tree.search(self.entry_search.get_value())) self.entry_search.bind( '<Return>', lambda event: self.tree.search(self.entry_search.get_value(), find_next=True)) # self.btn_currency_search = Button(frm, text='Search', command=lambda event: self.tree_currency.search(self.entry_currency_search.get_value(), find_next=True)) self.btn_apply = Button(frm, text='Apply', command=self.applyChanges) self.btn_reload = Button( frm, text='Reload', command=lambda: self.loadCurrency(force_reload=True)) self.entry_search.grid(row=2, column=0, pady=5, padx=5) # self.btn_currency_search.grid(row=2, column=1, pady=5) self.btn_apply.grid(row=2, column=2, pady=5) # frm.columnconfigure(3, weight=1) self.btn_reload.grid(row=2, column=3, sticky='e', pady=5) # Confidence Level lbl = Label(frm, text="Confidence level:") lbl.grid(row=2, column=10, padx=5, sticky='nse', pady=(3, 5)) self.lbl_confidence_lvl = lbl self.var_confidence_lvl = IntVar() self.entry_confidence_lvl = ConfidenceScale( frm, variable=self.var_confidence_lvl) self.entry_confidence_lvl.grid(row=2, column=11, padx=5, pady=5) self.var_confidence_lvl.trace( 'w', lambda a, b, c: self.on_entry_change(self.entry_confidence_lvl)) # Tree Config tree = self.tree tree['columns'] = currencyColumns[1:] tree.register_column( 'Override', ColEntry(TooltipEntry(tree), func_validate=_validate_price_override)) def init_tree_column(col): col_name = currencyColumns[0] if col == '#0' else col tree.heading(col, text=CurrencyColumn[col_name].value, anchor=W, command=lambda col=col: tree.sort_col(col)) tree.column(col, width=140, stretch=False) for col in ('#0', ) + tree['columns']: init_tree_column(col) tree.heading('#0', anchor=CENTER) tree.column('#0', width=250, stretch=False) tree.column(CurrencyColumn.Filler.name, stretch=True) tree.heading(CurrencyColumn.Rate.name, command=lambda col=CurrencyColumn.Rate.name: tree. sort_col(col, key=float, default=0)) tree.heading(CurrencyColumn.EffectiveRate.name, command=lambda col=CurrencyColumn.EffectiveRate.name: tree .sort_col(col, key=float, default=0)) tree.heading(CurrencyColumn.Override.name, command=lambda col=CurrencyColumn.Override.name: tree. sort_col(col, key=self._price_key)) self.bvar_modified.trace('w', lambda a, b, c: self._updateApplyState()) def _price_key(self, key): if key == '': return None # this means it will be ignored while sorting try: return cm.compilePrice(key, base_price=0) except Exception: return 0 def _updateApplyState(self): if self.bvar_modified.get(): self.btn_apply.config(state=NORMAL) else: self.btn_apply.config(state=DISABLED) def loadCurrency(self, force_reload=False): if not cm.initialized: return if not force_reload and self.bvar_modified.get(): return self.var_confidence_lvl.set(cm.confidence_level) tree = self.tree tree.clear() table = {} for curr in cm.shorts: effective_rate = cm.crates.get(curr, '0') table[curr] = (_to_display_rate(cm.rates.get(curr, '')), cm.overrides.get(curr, ''), _to_display_rate(effective_rate)) for curr in table: tree.insert('', END, '', text=curr, values=table[curr]) tree.sort_col(CurrencyColumn.EffectiveRate.name, key=float, default=0) self.bvar_modified.set(False) def applyChanges(self, event=None): if not self.bvar_modified.get() or not cm.initialized: return overrides = {} for iid in self.tree.get_children(): #TODO: hide #0 col and move names to a value column currency_name_col = '#0' # CurrencyColumn.Currency.name # id = self.tree.set(iid, currency_name_col) id = self.tree.item(iid, 'text') override = self.tree.set(iid, CurrencyColumn.Override.name) if override: overrides[id] = override # ids = set([self.tree.set(iid, currency_name_col) for iid in self.tree.get_children()]) ids = set( [self.tree.item(iid, 'text') for iid in self.tree.get_children()]) # preserve unhandled ids configuration for key in (set(cm.overrides) - ids): overrides[key] = cm.overrides[key] cm.confidence_level = self.entry_confidence_lvl.get() try: cm.compile(overrides=overrides) if fm.initialized: threading.Thread(target=fm.compileFilters).start() self.bvar_modified.set(False) except AppException as e: messagebox.showerror('Update error', e, parent=self.winfo_toplevel()) except Exception as e: logexception() messagebox.showerror( 'Update error', 'Failed to apply changes, unexpected error:\n{}'.format(e), parent=self.winfo_toplevel()) def onCellUpdate(self, iid, col, old, new): if not self.bvar_modified.get() and old != new: self.bvar_modified.set(True) def on_entry_change(self, entry): self.bvar_modified.set(True)
class PricesEditor(Frame): def __init__(self, master, **kwargs): super().__init__(master, **kwargs) self.create_prices_ui() self.initial_values = {} self.table_modified = False def create_prices_ui(self): self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) self.frm_prices = Frame(self) self.frm_prices.rowconfigure(2, weight=1) self.frm_prices.columnconfigure(0, weight=1) self.frm_prices.grid(padx=10, pady=10, sticky='nsew') # Button Frame frm_btns = Frame(self.frm_prices, relief=SOLID, borderwidth=2) frm_btns.grid(row=0, column=0, pady=(0, 5), sticky='nsew') frm_btns.columnconfigure(10, weight=1) # self.entry_currency = \ # Combobox_Autocomplete(frm, list_of_items=['one of many currencies'], startswith_match=False) self.search_var = StringVar() self.entry_search = PlaceholderEntry(frm_btns, 'Search..', style='Default.TEntry', textvariable=self.search_var) self.search_var.trace_variable( 'w', lambda a, b, c: self.tree.search(self.entry_search.get_value())) self.entry_search.bind( '<Return>', lambda event: self.tree.search(self.entry_search.get_value(), find_next=True)) self.btn_apply = Button(frm_btns, text='Apply', command=self.applyChanges) self.btn_reload = Button( frm_btns, text='Reload', command=lambda: self.loadPrices(force_reload=True)) self.entry_search.grid(row=2, column=0, pady=5, padx=5) self.btn_apply.grid(row=2, column=2, pady=5) # frm.columnconfigure(3, weight=1) self.btn_reload.grid(row=2, column=3, sticky='e', pady=5) self.var_advanced = BooleanVar(False) self.var_advanced.trace_variable( 'w', lambda a, b, c: self._on_view_option_change()) self.cb_advanced = Checkbutton(frm_btns, text='Advanced', variable=self.var_advanced) self.cb_advanced.grid(row=2, column=10, sticky='e', padx=10) frm_border = Frame(self.frm_prices, relief=SOLID, borderwidth=2) frm_border.grid(row=2, column=0, sticky='nsew') frm_border.rowconfigure(2, weight=1) frm_border.columnconfigure(0, weight=1) # Tree Frame self.frm_tree = Frame(frm_border) self.frm_tree.grid(row=2, column=0, sticky='nsew', padx=5, pady=(0, 0)) self.frm_tree.rowconfigure(0, weight=1) self.frm_tree.columnconfigure(0, weight=1) self.tree = EditableTreeview(self.frm_tree, on_cell_update=self.onCellUpdate) scrly = AutoScrollbar(self.frm_tree, command=self.tree.yview) scrlx = AutoScrollbar(self.frm_tree, command=self.tree.xview, orient=HORIZONTAL) self.tree.config(yscrollcommand=scrly.set, xscrollcommand=scrlx.set) self.tree.grid(row=0, column=0, sticky='nsew') scrly.grid(row=0, column=1, sticky='nsew') scrlx.grid(row=1, column=0, sticky='nsew') # Button Frame frm = Frame(frm_border) #, relief=SOLID, borderwidth=1) # frm = Frame(self.frm_prices) frm.grid(row=0, column=0, sticky='nsew') # self.entry_currency = \ # Combobox_Autocomplete(frm, list_of_items=['one of many currencies'], startswith_match=False) lbl = Label(frm, text='Item value threshold:') lbl.grid(row=0, column=0, padx=5, pady=5, sticky='w') self.var_threshold = StringVar() self.entry_threshold = TooltipEntry(frm, textvariable=self.var_threshold) self.entry_threshold.bind( '<FocusOut>', lambda event: self._validate_threshold_entry()) self.entry_threshold.grid(row=0, column=1, padx=5, pady=5) self.var_threshold.trace( 'w', lambda a, b, c: self.on_entry_change(self.entry_threshold)) lbl = Label(frm, text='Budget:') lbl.grid(row=0, column=2, padx=5, pady=5) self.var_budget = StringVar() self.entry_budget = TooltipEntry(frm, textvariable=self.var_budget) self.entry_budget.bind('<FocusOut>', lambda event: self._validate_budget_entry()) self.entry_budget.grid(row=0, column=3, padx=5, pady=5) self.var_budget.trace( 'w', lambda a, b, c: self.on_entry_change(self.entry_budget)) lbl = Label(frm, text='Minimum price:') lbl.grid(row=0, column=4, padx=5, pady=5) self.var_min_price = StringVar() self.entry_min_price = TooltipEntry(frm, textvariable=self.var_min_price) self.entry_min_price.bind( '<FocusOut>', lambda event: self._validate_min_price_entry()) self.entry_min_price.grid(row=0, column=5, padx=5, pady=5) self.var_min_price.trace( 'w', lambda a, b, c: self.on_entry_change(self.entry_min_price)) lbl = Label(frm, text='Default filter override:') lbl.grid(row=0, column=6, padx=5, pady=5) self.lbl_fprice_override = lbl self.var_fprice_override = StringVar() self.entry_fprice_override = TooltipEntry( frm, textvariable=self.var_fprice_override) self.entry_fprice_override.bind( '<FocusOut>', lambda event: self._validate_fprice_override_entry()) self.entry_fprice_override.grid(row=0, column=7, padx=5, pady=5) self.var_fprice_override.trace( 'w', lambda a, b, c: self.on_entry_change(self.entry_fprice_override)) # Advanced lbl = Label(frm, text='Default item value override:') lbl.grid(row=1, column=0, padx=5, pady=(2, 5), sticky='w') self.lbl_price_override = lbl self.var_price_override = StringVar() self.entry_price_override = TooltipEntry( frm, textvariable=self.var_price_override) self.entry_price_override.bind( '<FocusOut>', lambda event: self._validate_price_override_entry()) self.entry_price_override.grid(row=1, column=1, padx=5, pady=(2, 5)) self.var_price_override.trace( 'w', lambda a, b, c: self.on_entry_change(self.entry_price_override)) # Confidence Level lbl = Label(frm, text="Confidence level:") lbl.grid(row=1, column=2, padx=5, pady=(2, 5), sticky='w') self.lbl_confidence_lvl = lbl self.var_confidence_lvl = IntVar() self.entry_confidence_lvl = ConfidenceScale( frm, variable=self.var_confidence_lvl) self.entry_confidence_lvl.grid(row=1, column=3, padx=5, pady=(2, 5)) self.var_confidence_lvl.trace( 'w', lambda a, b, c: self.on_entry_change(self.entry_confidence_lvl)) self.var_5l_filters = BooleanVar(False) self.cb_5l_filters = VarCheckbutton(frm, text='Enable 5L filters', variable=self.var_5l_filters) self.cb_5l_filters.var = self.var_5l_filters self.cb_5l_filters.grid(row=1, column=4, padx=5, pady=(2, 5), columnspan=1) self.var_5l_filters.trace_variable( 'w', lambda a, b, c: self.on_entry_change(self.cb_5l_filters)) # Tree Config tree = self.tree def init_tree_column(col): col_name = pricesColumns[0] if col == '#0' else col tree.heading(col, text=PricesColumn[col_name].value, anchor=W, command=lambda col=col: tree.sort_col(col)) tree.column(col, width=140, stretch=False) # self.tree['columns'] = ('ID', 'Item Price', 'Override', 'Filter Price', 'Filter Override', 'Effective Filter Price', 'Filter State Override', '') self.tree['columns'] = pricesColumns[1:] self.tree.register_column( PricesColumn.Override.name, ColEntry(TooltipEntry(self.tree), func_validate=_validate_price_override)) self.tree.register_column( PricesColumn.FilterOverride.name, ColEntry(TooltipEntry(self.tree), func_validate=_validate_price_override)) self.tree.register_column( PricesColumn.FilterStateOverride.name, ColEntry(Combobox(self.tree, values=filterStateOptions, state=READONLY), accept_events=('<<ComboboxSelected>>', '<Return>'))) for col in (('#0', ) + tree['columns']): init_tree_column(col) tree.heading('#0', anchor=CENTER) tree.column('#0', width=200, stretch=False) tree.column(PricesColumn.Filler.name, stretch=True) tree.heading(PricesColumn.ItemPrice.name, command=lambda col=PricesColumn.ItemPrice.name: tree. sort_col(col, key=self._price_key)) tree.heading(PricesColumn.Override.name, command=lambda col=PricesColumn.Override.name: tree. sort_col(col, key=self._price_key)) tree.heading(PricesColumn.FilterOverride.name, command=lambda col=PricesColumn.FilterOverride.name: tree. sort_col(col, key=self._price_key)) tree.heading(PricesColumn.FilterPrice.name, command=lambda col=PricesColumn.FilterPrice.name: tree. sort_col(col, key=self._rate_key, default=0)) tree.heading(PricesColumn.EffectiveFilterPrice.name, command=lambda col=PricesColumn.EffectiveFilterPrice.name: tree.sort_col(col, key=self._rate_key, default=0)) self.bvar_modified = BooleanVar() self.bvar_modified.trace('w', lambda a, b, c: self._updateApplyState()) self.bvar_modified.set(False) self.var_advanced.set(False) def _rate_key(self, key): if key == 'N/A': return 0 return float(key) def _price_key(self, key): if key == '': return None # this means it will be ignored while sorting try: return cm.compilePrice(key, base_price=0) except Exception: return 0 def on_entry_change(self, entry): val = entry.get() if self.initial_values[entry] != val: self.bvar_modified.set(True) # def on_price_entry_focusout(self, widget): # valid = _validate_price(widget, accept_empty=False) # if valid and not self.bvar_modified.get() and self.initial_values[widget] != widget.get(): # self.bvar_modified.set(True) # return valid # # def on_override_entry_focusout(self, widget): # valid = _validate_price_override(widget, accept_empty=False) # if valid and not self.bvar_modified.get() and self.initial_values[widget] != widget.get(): # self.bvar_modified.set(True) # return valid def _validate_threshold_entry(self): return _validate_price(self.entry_threshold, accept_empty=False) def _validate_budget_entry(self): return _validate_price(self.entry_budget, accept_empty=True) def _validate_min_price_entry(self): return _validate_price(self.entry_min_price, accept_empty=True) def _validate_price_override_entry(self): return _validate_price_override(self.entry_price_override, accept_empty=False) def _validate_fprice_override_entry(self): return _validate_price_override(self.entry_fprice_override, accept_empty=False) def _update_modified(self): modified = any(entry.get() != self.initial_values[entry] for entry in self.initial_values) or self.table_modified self.bvar_modified.set(modified) def _updateApplyState(self): if self.bvar_modified.get(): self.btn_apply.config(state=NORMAL) else: self.btn_apply.config(state=DISABLED) def _validateForm(self): if not self._validate_threshold_entry(): return False if not self._validate_budget_entry(): return False if not self._validate_min_price_entry(): return False if not self._validate_price_override_entry(): return False if not self._validate_fprice_override_entry(): return False return True def applyChanges(self, event=None): if not self.bvar_modified.get() or not fm.initialized: return if not self._validateForm(): return price_threshold = self.entry_threshold.get() default_price_override = self.entry_price_override.get() default_fprice_override = self.entry_fprice_override.get() budget = self.entry_budget.get() min_price = self.entry_min_price.get() confidence_lvl = self.entry_confidence_lvl.get( ) or fm.DEFAULT_CONFIDENCE_LEVEL enable_5l_filters = self.var_5l_filters.get() price_overrides = {} filter_price_overrides = {} filter_state_overrides = {} for iid in self.tree.get_children(): id = self.tree.set(iid, PricesColumn.ID.name) iprice = self.tree.set(iid, PricesColumn.Override.name) if iprice: price_overrides[id] = iprice fprice = self.tree.set(iid, PricesColumn.FilterOverride.name) if fprice: filter_price_overrides[id] = fprice fstate = self.tree.set(iid, PricesColumn.FilterStateOverride.name) try: filter_state_overrides[id] = FilterStateOption[fstate].value except KeyError: pass ids = set([ self.tree.set(iid, PricesColumn.ID.name) for iid in self.tree.get_children() ]) # preserve unhandled ids configuration for key in (set(fm.price_overrides) - ids): price_overrides[key] = fm.price_overrides[key] for key in (set(fm.filter_price_overrides) - ids): filter_price_overrides[key] = fm.filter_price_overrides[key] for key in (set(fm.filter_state_overrides) - ids): filter_state_overrides[key] = fm.filter_state_overrides[key] try: fm.updateConfig(default_price_override, default_fprice_override, price_threshold, budget, min_price, price_overrides, filter_price_overrides, filter_state_overrides, int(confidence_lvl), enable_5l_filters) except AppException as e: messagebox.showerror( 'Validation error', 'Failed to update configuration:\n{}'.format(e), parent=self.winfo_toplevel()) except Exception as e: logexception() messagebox.showerror( 'Update error', 'Failed to apply changes, unexpected error:\n{}'.format(e), parent=self.winfo_toplevel()) else: # SHOULD always work since config is valid, main console will report any failures # background thread because schema validating takes a bit of time threading.Thread(target=fm.compileFilters).start() self._initFormState() def loadPrices(self, force_reload=False): if not cm.initialized or not fm.initialized: return if not force_reload: self._update_modified() # in case of reverted changes if self.bvar_modified.get(): # dont interrupt user changes return tree = self.tree tree.clear() table = {} for fltr in fm.autoFilters: # effective_rate = cm.crates.get(curr, '') # if effective_rate != '': # effective_rate = round(effective_rate, 3) fid = fltr.id fstate_override = fm.filter_state_overrides.get(fid, '') try: fstate_override = FilterStateOption(fstate_override).name except ValueError: fstate_override = '' table[fid] = (fltr.title, fid, fm.item_prices[fid], fm.price_overrides.get(fid, ''), _to_display_rate( fm.compiled_item_prices.get(fid, 'N/A')), fm.filter_price_overrides.get(fid, ''), _to_display_rate( fm.compiled_filter_prices.get(fid, 'N/A')), fstate_override) for fid in table: tree.insert('', END, '', text=table[fid][0], values=table[fid][1:]) # tree.sort_by('#0', descending=True) tree.sort_col('#0', reverse=False) self._initFormState() # def onItemPriceUpdate(self, iid, col, old, new): # print('IPrice update: iid {}, col {}'.format(iid, col)) def onCellUpdate(self, iid, col, old, new): if old != new: self.table_modified = True self.bvar_modified.set(True) # self._update_modified() def _initFormState(self): self.table_modified = False self.initial_values[self.entry_threshold] = fm.price_threshold self.initial_values[self.entry_budget] = fm.budget self.initial_values[self.entry_min_price] = fm.default_min_price self.initial_values[ self.entry_price_override] = fm.default_price_override self.initial_values[ self.entry_fprice_override] = fm.default_fprice_override self.initial_values[self.entry_confidence_lvl] = fm.confidence_level self.initial_values[self.cb_5l_filters] = fm.enable_5l_filters self.var_threshold.set(fm.price_threshold) self.var_budget.set(fm.budget) self.var_min_price.set(fm.default_min_price) self.var_price_override.set(fm.default_price_override) self.var_fprice_override.set(fm.default_fprice_override) self.var_confidence_lvl.set(fm.confidence_level) self.var_5l_filters.set(fm.enable_5l_filters) self.bvar_modified.set(False) def _on_view_option_change(self): advanced_widgets = [ self.entry_price_override, self.lbl_price_override, self.lbl_confidence_lvl, self.entry_confidence_lvl, self.cb_5l_filters ] if not self.var_advanced.get(): for w in advanced_widgets: w.grid_remove() self.tree.config(displaycolumn=[ PricesColumn.FilterPrice.name, PricesColumn.FilterOverride. name, PricesColumn.EffectiveFilterPrice.name, PricesColumn.Filler.name ]) else: for w in advanced_widgets: w.grid() self.tree.config(displaycolumn='#all') self.tree.on_entry_close()
class SimpleEditor(Toplevel): def __init__(self, parent=None, master=None, file=None, line=None, newhsh=None, rephsh=None, options=None, title=None, start=None, modified=False): """ If file is given, open file for editing. Otherwise, we are creating a new item and/or replacing an item mode: 1: new: edit newhsh, replace none 2: replace: edit and replace rephsh 3: new and replace: edit newhsh, replace rephsh :param parent: :param file: path to file to be edited """ # self.frame = frame = Frame(parent) if master is None: master = parent self.master = master Toplevel.__init__(self, master) self.minsize(400, 300) self.geometry('500x200') self.transient(parent) self.configure(background=BGCOLOR, highlightbackground=BGCOLOR) self.parent = parent self.loop = parent.loop self.messages = self.loop.messages self.messages = [] self.mode = None self.changed = False self.scrollbar = None self.listbox = None self.autocompletewindow = None self.line = None self.match = None self.file = file self.initfile = None self.fileinfo = None self.repinfo = None self.title = title self.edithsh = {} self.newhsh = newhsh self.rephsh = rephsh self.value = '' self.options = options self.tkfixedfont = tkFont.nametofont("TkFixedFont") self.tkfixedfont.configure(size=self.options['fontsize_fixed']) # self.text_value.trace_variable("w", self.setSaveStatus) frame = Frame(self, bd=0, relief=FLAT) frame.pack(side="bottom", fill=X, padx=4, pady=0) frame.configure(background=BGCOLOR, highlightbackground=BGCOLOR) # quit with a warning prompt if modified Button(frame, text=_("Cancel"), highlightbackground=BGCOLOR, pady=2, command=self.quit).pack(side=LEFT, padx=4) self.bind("<Escape>", self.quit) l, c = commandShortcut('q') self.bind(c, self.quit) self.bind("<Escape>", self.cancel) # finish will evaluate the item entry and, if repeating, show reps finish = Button(frame, text=FINISH, highlightbackground=BGCOLOR, command=self.onFinish, pady=2) # self.bind("<Control-w>", self.onCheck) self.bind("<Control-w>", self.onFinish) finish.pack(side=RIGHT, padx=4) # find Button(frame, text='x', command=self.clearFind, highlightbackground=BGCOLOR, padx=8, pady=2).pack(side=LEFT, padx=0) self.find_text = StringVar(frame) self.e = Entry(frame, textvariable=self.find_text, width=10, highlightbackground=BGCOLOR) self.e.pack(side=LEFT, padx=0, expand=1, fill=X) self.e.bind("<Return>", self.onFind) Button(frame, text='>', command=self.onFind, highlightbackground=BGCOLOR, padx=8, pady=2).pack(side=LEFT, padx=0) text = Text(self, wrap="word", bd=2, relief="sunken", padx=3, pady=2, font=self.tkfixedfont, undo=True, width=70) text.configure(highlightthickness=0) text.tag_configure(FOUND, background="lightskyblue") text.pack(side="bottom", padx=4, pady=3, expand=1, fill=BOTH) self.text = text self.completions = self.loop.options['completions'] if start is not None: # we have the starting text but will need a new uid text = start if self.rephsh is None: self.edithsh = {} self.mode = 1 self.title = CREATENEW else: self.edithsh = self.rephsh self.mode = 2 self.title = EDITEXISTING elif file is not None: # we're editing a file - if it's a data file we will add uid's # as necessary when saving self.mode = 'file' if not os.path.isfile(file): logger.warn('could not open: {0}'.format(file)) text = "" else: with codecs.open(file, 'r', self.options['encoding']['file']) as f: text = f.read() else: # we are creating a new item and/or replacing an item # mode: # 1: new # 2: replace # 3: new and replace initfile = ensureMonthly(options=self.options, date=datetime.now()) # set the mode if newhsh is None and rephsh is None: # we are creating a new item from scratch and will need # a new uid self.mode = 1 self.title = CREATENEW self.edithsh = {} self.edithsh['i'] = uniqueId() text = '' elif rephsh is None: # newhsh is not None # we are creating a new item as a copy and will need # a new uid self.mode = 1 self.title = CREATENEW self.edithsh = self.newhsh self.edithsh['i'] = uniqueId() if ('fileinfo' in newhsh and newhsh['fileinfo']): initfile = newhsh['fileinfo'][0] text, msg = hsh2str(self.edithsh, self.options) elif newhsh is None: # we are editing and replacing rephsh - no file prompt # using existing uid self.title = EDITEXISTING self.mode = 2 # self.repinfo = rephsh['fileinfo'] self.edithsh = self.rephsh text, msg = hsh2str(self.edithsh, self.options) else: # neither is None # we are changing some instances of a repeating item # we will be writing but not editing rephsh using its fileinfo # and its existing uid # we will be editing and saving newhsh using self.initfile # we will need a new uid for newhsh self.mode = 3 self.title = CREATENEW self.edithsh = self.newhsh self.edithsh['i'] = uniqueId() if 'fileinfo' in newhsh and newhsh['fileinfo'][0]: initfile = self.newhsh['fileinfo'][0] text, msg = hsh2str(self.edithsh, self.options) self.initfile = initfile logger.debug('mode: {0}; initfile: {1}; edit: {2}'.format(self.mode, self.initfile, self.edithsh)) if self.title is not None: self.wm_title(self.title) self.settext(text) # clear the undo buffer if not modified: self.text.edit_reset() self.setmodified(False) self.text.bind('<<Modified>>', self.updateSaveStatus) self.text.focus_set() self.protocol("WM_DELETE_WINDOW", self.quit) if parent: self.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 50)) self.configure(background=BGCOLOR) l, c = commandShortcut('f') self.bind(c, lambda e: self.e.focus_set()) l, c = commandShortcut('g') self.bind(c, lambda e: self.onFind()) if start: self.text.tag_add("sel", "1.1", "1.{0}".format(len(start))) self.text.mark_set(INSERT, END) elif line: self.text.mark_set(INSERT, "{0}.0".format(line)) else: self.text.mark_set(INSERT, END) self.text.see(INSERT) # l, c = commandShortcut('/') logger.debug("/: {0}, {1}".format(l, c)) self.text.bind("<Control-space>", self.showCompletions) self.grab_set() self.wait_window(self) def settext(self, text=''): self.text.delete('1.0', END) self.text.insert(INSERT, text) self.text.mark_set(INSERT, '1.0') self.text.focus() logger.debug("modified: {0}".format(self.checkmodified())) def gettext(self): return self.text.get('1.0', END + '-1c') def setCompletions(self, *args): match = self.filterValue.get() self.matches = matches = [x for x in self.completions if x and x.lower().startswith(match.lower())] self.listbox.delete(0, END) for item in matches: self.listbox.insert(END, item) self.listbox.select_set(0) self.listbox.see(0) self.fltr.focus_set() def showCompletions(self, e=None): if not self.completions: return "break" if self.autocompletewindow: return "break" line = self.text.get("insert linestart", INSERT) m = completion_regex.search(line) if not m: logger.debug("no match in {0}".format(line)) return "break" # set self.match here since it determines the characters to be replaced self.match = match = m.groups()[0] logger.debug("found match '{0}' in line '{1}'".format(match, line)) self.autocompletewindow = acw = Toplevel(master=self.text) acw.geometry("+%d+%d" % (self.text.winfo_rootx() + 50, self.text.winfo_rooty() + 50)) self.autocompletewindow.wm_attributes("-topmost", 1) self.filterValue = StringVar(self) self.filterValue.set(match) self.filterValue.trace_variable("w", self.setCompletions) self.fltr = Entry(acw, textvariable=self.filterValue) self.fltr.pack(side="top", fill="x") self.fltr.icursor(END) self.listbox = listbox = Listbox(acw, exportselection=False, width=self.loop.options['completions_width']) listbox.pack(side="bottom", fill=BOTH, expand=True) self.autocompletewindow.bind("<Double-1>", self.completionSelected) self.autocompletewindow.bind("<Return>", self.completionSelected) self.autocompletewindow.bind("<Escape>", self.hideCompletions) self.autocompletewindow.bind("<Up>", self.cursorUp) self.autocompletewindow.bind("<Down>", self.cursorDown) self.fltr.bind("<Up>", self.cursorUp) self.fltr.bind("<Down>", self.cursorDown) self.setCompletions() def is_active(self): return self.autocompletewindow is not None def hideCompletions(self, e=None): if not self.is_active(): return # destroy widgets self.listbox.destroy() self.listbox = None self.autocompletewindow.destroy() self.autocompletewindow = None def completionSelected(self, event): # Put the selected completion in the text, and close the list modified = False if self.matches: cursel = self.matches[int(self.listbox.curselection()[0])] else: cursel = self.filterValue.get() modified = True start = "insert-{0}c".format(len(self.match)) end = "insert-1c wordend" logger.debug("cursel: {0}; match: {1}; start: {2}; insert: {3}".format( cursel, self.match, start, INSERT)) self.text.delete(start, end) self.text.insert(INSERT, cursel) self.hideCompletions() if modified: file = FileChoice(self, "append completion to file", prefix=self.loop.options['etmdir'], list=self.loop.options['completion_files']).returnValue() if (file and os.path.isfile(file)): with codecs.open(file, 'r', self.loop.options['encoding']['file']) as fo: lines = fo.readlines() lines.append(cursel) lines.sort() content = "\n".join([x.strip() for x in lines if x.strip()]) with codecs.open(file, 'w', self.loop.options['encoding']['file']) as fo: fo.write(content) self.completions.append(cursel) self.completions.sort() def cursorUp(self, event=None): cursel = int(self.listbox.curselection()[0]) # newsel = max(0, cursel=1) newsel = max(0, cursel - 1) self.listbox.select_clear(cursel) self.listbox.select_set(newsel) self.listbox.see(newsel) return "break" def cursorDown(self, event=None): cursel = int(self.listbox.curselection()[0]) newsel = min(len(self.matches) - 1, cursel + 1) self.listbox.select_clear(cursel) self.listbox.select_set(newsel) self.listbox.see(newsel) return "break" def setmodified(self, bool): if bool is not None: self.text.edit_modified(bool) def checkmodified(self): return self.text.edit_modified() def updateSaveStatus(self, event=None): # Called by <<Modified>> if self.checkmodified(): self.wm_title("{0} (modified)".format(self.title)) else: self.wm_title("{0}".format(self.title)) def onFinish(self, e=None): if self.mode == 'file': self.onSave() else: self.onCheck() def onSave(self, e=None, v=0): if not self.checkmodified(): self.quit() elif self.file is not None: # we are editing a file alltext = self.gettext() self.loop.safe_save(self.file, alltext) self.setmodified(False) self.changed = True self.quit() else: # we are editing an item if self.mode in [1, 3]: # new dir = self.options['datadir'] if 's' in self.edithsh and self.edithsh['s']: dt = self.edithsh['s'] file = ensureMonthly(self.options, dt.date()) else: dt = None file = ensureMonthly(self.options) dir, initfile = os.path.split(file) # we need a filename for the new item # make datadir the root prefix, tuples = getFileTuples(self.options['datadir'], include=r'*.txt') if v == 2: filename = file else: ret = FileChoice(self, "etm data files", prefix=prefix, list=tuples, start=file).returnValue() if not ret: return False filename = os.path.join(prefix, ret) if not os.path.isfile(filename): return False filename = os.path.normpath(filename) logger.debug('saving to: {0}'.format(filename)) self.text.focus_set() logger.debug('edithsh: {0}'.format(self.edithsh)) if self.mode == 1: if self.loop.append_item(self.edithsh, filename): logger.debug('append mode: {0}'.format(self.mode)) elif self.mode == 2: if self.loop.replace_item(self.edithsh): logger.debug('replace mode: {0}'.format(self.mode)) else: # self.mode == 3 if self.loop.append_item(self.edithsh, filename): logger.debug('append mode: {0}'.format(self.mode)) if self.loop.replace_item(self.rephsh): logger.debug('replace mode: {0}'.format(self.mode)) # update the return value so that when it is not null then modified # is false and when modified is true then it is null self.setmodified(False) self.changed = True self.quit() return "break" def onCheck(self, event=None, showreps=True, showres=True): # only called when editing an item and finish is pressed self.loop.messages = [] text = self.gettext() msg = [] reps = [] if text.startswith("BEGIN:VCALENDAR"): text = import_ical(vcal=text) logger.debug("text: {0} '{01}'".format(type(text), text)) if self.edithsh and 'i' in self.edithsh: uid = self.edithsh['i'] else: uid = None hsh, msg = str2hsh(text, options=self.options, uid=uid) if not msg: # we have a good hsh pre = post = "" if 'r' in hsh: pre = _("Repeating ") elif 's' in hsh: dt = hsh['s'] if not dt.hour and not dt.minute: dtfmt = fmt_date(dt, short=True) else: dtfmt = fmt_shortdatetime(hsh['s'], self.options) post = _(" scheduled for {0}").format(dtfmt) else: # unscheduled pre = _("Unscheduled ") prompt = "{0}{1}{2}".format(pre, type2Text[hsh['itemtype']], post) if self.edithsh and 'fileinfo' in self.edithsh: fileinfo = self.edithsh['fileinfo'] self.edithsh = hsh self.edithsh['fileinfo'] = fileinfo else: # we have a new item without fileinfo self.edithsh = hsh # update missing fields logger.debug('calling hsh2str with {0}'.format(hsh)) str, msg = hsh2str(hsh, options=self.options) self.loop.messages.extend(msg) if self.loop.messages: messages = "{0}".format("\n".join(self.loop.messages)) logger.debug("messages: {0}".format(messages)) self.messageWindow(MESSAGES, messages, opts=self.options) return False logger.debug("back from hsh2str with: {0}".format(str)) if 'r' in hsh: showing_all, reps = get_reps(self.loop.options['bef'], hsh) if reps: if showreps: try: repsfmt = [unicode(x.strftime(rrulefmt)) for x in reps] except: repsfmt = [unicode(x.strftime("%X %x")) for x in reps] logger.debug("{0}: {1}".format(showing_all, repsfmt)) if showing_all: reps = ALLREPS else: reps = SOMEREPS prompt = "{0}, {1}:\n {2}".format(prompt, reps, "\n ".join(repsfmt)) # self.messageWindow(VALID, repetitions, opts=self.options) else: repetitions = "No repetitions were generated." self.loop.messages.append(repetitions) if self.loop.messages: messages = "{0}".format("\n".join(self.loop.messages)) logger.debug("messages: {0}".format(messages)) self.messageWindow(MESSAGES, messages, opts=self.options) return False if self.checkmodified(): prompt += "\n\n{0}".format(SAVEANDEXIT) else: prompt += "\n\n{0}".format(UNCHANGEDEXIT) if str != text: self.settext(str) ans, value = OptionsDialog(parent=self, title=self.title, prompt=prompt, yesno=False, list=True).getValue() if ans: self.onSave(v=value) return def clearFind(self, *args): self.text.tag_remove(FOUND, "0.0", END) self.find_text.set("") def onFind(self, *args): target = self.find_text.get() logger.debug('target: {0}'.format(target)) if target: where = self.text.search(target, INSERT, nocase=1) if where: pastit = where + ('+%dc' % len(target)) # self.text.tag_remove(SEL, '1.0', END) self.text.tag_add(FOUND, where, pastit) self.text.mark_set(INSERT, pastit) self.text.see(INSERT) self.text.focus() def cancel(self, e=None): t = self.find_text.get() if t.strip(): self.clearFind() return "break" if self.autocompletewindow: self.hideCompletions() return "break" if self.text.tag_ranges("sel"): self.text.tag_remove('sel', "1.0", END) return # if self.checkmodified(): # return "break" logger.debug(('calling quit')) self.quit() def quit(self, e=None): if self.checkmodified(): ans = self.confirm(parent=self, title=_('Quit'), prompt=_("There are unsaved changes.\nDo you really want to quit?")) else: ans = True if ans: if self.master: logger.debug('setting focus') self.master.focus() self.master.focus_set() logger.debug('focus set') self.destroy() logger.debug('done') def messageWindow(self, title, prompt, opts=None, height=8, width=52): win = Toplevel(self) win.title(title) win.geometry("+%d+%d" % (self.text.winfo_rootx() + 50, self.text.winfo_rooty() + 50)) f = Frame(win) # pack the button first so that it doesn't disappear with resizing b = Button(win, text=_('OK'), width=10, command=win.destroy, default='active', pady=2) b.pack(side='bottom', fill=tkinter.NONE, expand=0, pady=0) win.bind('<Return>', (lambda e, b=b: b.invoke())) win.bind('<Escape>', (lambda e, b=b: b.invoke())) tkfixedfont = tkFont.nametofont("TkFixedFont") if 'fontsize_fixed' in self.loop.options and self.loop.options['fontsize_fixed']: tkfixedfont.configure(size=self.loop.options['fontsize_fixed']) t = ReadOnlyText( f, wrap="word", padx=2, pady=2, bd=2, relief="sunken", font=tkfixedfont, height=height, width=width, takefocus=False) t.insert("0.0", prompt) t.pack(side='left', fill=tkinter.BOTH, expand=1, padx=0, pady=0) if height > 1: ysb = ttk.Scrollbar(f, orient='vertical', command=t.yview) ysb.pack(side='right', fill=tkinter.Y, expand=0, padx=0, pady=0) t.configure(state="disabled", yscroll=ysb.set) t.configure(yscroll=ysb.set) f.pack(padx=2, pady=2, fill=tkinter.BOTH, expand=1) win.focus_set() win.grab_set() win.transient(self) win.wait_window(win) def confirm(self, parent=None, title="", prompt="", instance="xyz"): ok, value = OptionsDialog(parent=parent, title=_("confirm").format(instance), prompt=prompt).getValue() return ok