def make_widgets(self): tkd.Label(self.root, text='This is a test application').pack() # Create a test row lf1 = tkd.LabelFrame(self.root) lf1.pack(expand=True, fill=tk.X) svar = tk.IntVar(value=0) tkd.Label(lf1, text='Option 1').pack(side=tk.LEFT) tkd.Radiobutton(lf1, text='Off', indicatoron=False, value=0, variable=svar).pack(side=tk.RIGHT) tkd.Radiobutton(lf1, text='On', indicatoron=False, value=1, variable=svar).pack(side=tk.RIGHT) # Create choice to change theme lf2 = tkd.LabelFrame(self.root) lf2.pack(expand=True, fill=tk.X) tkd.Label(lf2, text='Active theme').pack(side=tk.LEFT) theme_choices = ttk.Combobox(lf2, textvariable=self.theme_var, state='readonly', values=themes) theme_choices.bind("<FocusOut>", lambda e: theme_choices.selection_clear()) theme_choices.bind("<<ComboboxSelected>>", lambda e: self._change_theme()) theme_choices.config(width=11) theme_choices.pack(side=tk.RIGHT, fill=tk.X, padx=2)
def _extra_info_label(self, text, var): frame = tkd.Frame(self, height=12, width=238) frame.propagate(False) frame.pack(expand=True, fill=tk.X) tkd.Label(frame, text='%s:' % text, font='helvetica 8', anchor=tk.W, justify=tk.LEFT).pack(side=tk.LEFT) tkd.Label(frame, textvariable=var, font='helvetica 8 bold', anchor=tk.W, justify=tk.LEFT).pack(side=tk.LEFT)
def __init__(self, main_frame, timer_frame, drop_frame, parent=None, **kw): tkd.Frame.__init__(self, parent, kw) self.main_frame = main_frame self.modifier_options = system_hotkey.modifier_options self.character_options = system_hotkey.character_options self.hk = system_hotkey.SystemHotkey() lf = tkd.Frame(self, height=20, width=179) lf.pack(expand=True, fill=tk.BOTH) lf.propagate(False) tkd.Label(lf, text='Action', font='Helvetica 11 bold', justify=tk.LEFT).pack(side=tk.LEFT) tkd.Label(lf, text='Key ', font='Helvetica 11 bold', justify=tk.LEFT, width=9).pack(side=tk.RIGHT) tkd.Label(lf, text=' Modifier', font='Helvetica 11 bold', justify=tk.LEFT, width=7).pack(side=tk.RIGHT) self.add_hotkey(label_name='Start new run', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['start_key']), func=timer_frame.stop_start) self.add_hotkey(label_name='End run', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['end_key']), func=timer_frame.stop) self.add_hotkey(label_name='Delete prev', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['delete_prev_key']), func=timer_frame.delete_prev) self.add_hotkey(label_name='Pause', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['pause_key']), func=timer_frame.pause) self.add_hotkey(label_name='Add drop', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['drop_key']), func=drop_frame.add_drop) self.add_hotkey(label_name='Reset lap', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['reset_key']), func=timer_frame.reset_lap) self.add_hotkey(label_name='Make unclickable', keys=other_utils.safe_eval( main_frame.cfg['KEYBINDS']['make_unclickable']), func=main_frame.set_clickthrough)
def create_row(svar, label_name, lf): fr = tkd.Frame(lf, height=22, width=236) fr.propagate(False) fr.pack(expand=False, fill=tk.X) tkd.Label(fr, text='%s:' % label_name, font='helvetica 10', anchor=tk.W, justify=tk.LEFT).pack(side=tk.LEFT) tkd.Label(fr, textvariable=svar, font='helvetica 12 bold', anchor=tk.E, justify=tk.RIGHT).pack(side=tk.RIGHT)
def __init__(self, main_frame, **kw): tkd.Toplevel.__init__(self, **kw) self.main_frame = main_frame self.title('Archive Browser') self.wm_attributes('-topmost', self.main_frame.always_on_top) disp_coords = tk_utils.get_displaced_geom(self.main_frame.root, 790, 500) self.geometry(disp_coords) self.focus_get() self.iconbitmap(media_path + 'icon.ico') self.minsize(790, 500) tkd.Label(self, text='Archive browser', font='Helvetica 14').pack() self.tabcontrol = ttk.Notebook(self) self.tabcontrol.pack(expand=True, fill=tk.BOTH) collected_data = self.collect_data() self.statistics(**collected_data) self.run_table(laps=collected_data['laps']) self.drop_table(drops=collected_data['drops']) self.main_frame.theme.update_colors()
def add_flag(self, flag_name, comment=None, pack=True, config_section='OPTIONS'): lf = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) lf.propagate(False) if pack: lf.pack(expand=False, fill=tk.X) lab = tkd.Label(lf, text=flag_name) lab.pack(side=tk.LEFT) if comment is not None: tkd.create_tooltip(lab, comment) flag_attr = flag_name.lower().replace(' ', '_').replace('-', '_') setattr(self, flag_attr, tk.StringVar(lf)) sv = getattr(self, flag_attr) off_button = tkd.Radiobutton(lf, text='Off', variable=sv, indicatoron=False, value=False, width=4, padx=4) on_button = tkd.Radiobutton(lf, text='On', variable=sv, indicatoron=False, value=True, width=4, padx=3) if other_utils.safe_eval(self.main_frame.cfg[config_section][flag_attr]): on_button.invoke() setattr(self, flag_attr + '_invoked', True) else: off_button.invoke() setattr(self, flag_attr + '_invoked', False) off_button.config(command=lambda: self.toggle_button(flag_attr)) on_button.config(command=lambda: self.toggle_button(flag_attr)) on_button.pack(side=tk.RIGHT) off_button.pack(side=tk.RIGHT) return lf
def add_hotkey(self, label_name, keys, func): if keys[0].lower() not in map(lambda x: x.lower(), self.modifier_options) or keys[1].lower() not in map(lambda x: x.lower(), self.character_options): messagebox.showerror('Invalid hotkey', 'One or several hotkeys are invalid. Please edit/delete mf_config.ini') sys.exit() default_modifier, default_key = keys action = label_name.replace(' ', '_').lower() setattr(self, '_' + action, keys) lf = tkd.LabelFrame(self, height=30, width=179) lf.propagate(False) lf.pack(expand=True, fill=tk.BOTH) lab = tkd.Label(lf, text=label_name) lab.pack(side=tk.LEFT) setattr(self, action + '_e', tk.StringVar()) key = getattr(self, action + '_e') key.set(default_key) drop2 = ttk.Combobox(lf, textvariable=key, state='readonly', values=self.character_options) drop2.bind("<FocusOut>", lambda e: drop2.selection_clear()) drop2.config(width=9) drop2.pack(side=tk.RIGHT, fill=tk.X, padx=2) setattr(self, action + '_m', tk.StringVar()) mod = getattr(self, action + '_m') mod.set(default_modifier) drop1 = ttk.Combobox(lf, textvariable=mod, state='readonly', values=self.modifier_options) drop1.bind("<FocusOut>", lambda e: drop1.selection_clear()) drop1.config(width=7) drop1.pack(side=tk.RIGHT) mod.trace_add('write', lambda name, index, mode: self.re_register(action, getattr(self, '_' + action), func)) key.trace_add('write', lambda name, index, mode: self.re_register(action, getattr(self, '_' + action), func)) if default_key.lower() != 'no_bind': reg_key = [keys[1].lower()] if keys[0] == '' else list(map(lambda x: x.lower(), keys)) self.hk.register(reg_key, callback=lambda event: '' if win32gui.FindWindow(None, 'Add drop') else self.main_frame.queue.put(func))
def __init__(self, main_fr, parent=None, **kw): tkd.Frame.__init__(self, parent, kw) top_fr = tkd.Frame(self) top_fr.pack(side=tk.TOP, fill=tk.BOTH, expand=True) tkd.Label(top_fr, text="MF Run Counter", font=('Segoe UI', 11, 'bold')).pack(pady=[0, 2]) self.img = tk.PhotoImage(file=media_path + 'about_icon.png') tkd.Label(top_fr, image=self.img, borderwidth=0).pack(pady=0) btm_fr = tkd.Frame(self) btm_fr.pack(side=tk.BOTTOM, fill=tk.X, expand=True, anchor=tk.S) tkd.Label(btm_fr, text='New releases & README info', borderwidth=0, highlightthickness=0, justify=tk.LEFT).pack(anchor=tk.W) tkd.Hyperlink(btm_fr, hyperlink=release_repo.rstrip('releases'), text=" Github Repository", borderwidth=0, highlightthickness=0, justify=tk.LEFT).pack(anchor=tk.W, pady=[0, 7]) tkd.Label(btm_fr, text='Created by:', justify=tk.LEFT, borderwidth=0, highlightthickness=0).pack(anchor=tk.W) tkd.Label(btm_fr, text=' oskros#1889', font=('Segoe UI', 9, 'bold'), borderwidth=0, highlightthickness=0, justify=tk.LEFT).pack(anchor=tk.W) tkd.Label(btm_fr, text='Find me here:', justify=tk.LEFT, borderwidth=0, highlightthickness=0).pack(anchor=tk.W, pady=[7, 0]) tkd.Hyperlink(btm_fr, hyperlink='https://discord.gg/JhkTF2g', text=' https://discord.gg/JhkTF2g', justify=tk.LEFT, borderwidth=0, highlightthickness=0).pack(anchor=tk.W) v_fr = tkd.Frame(btm_fr) v_fr.pack(side=tk.BOTTOM, fill=tk.X, expand=True, anchor=tk.S) tkd.Label(v_fr, text="v.%s" % version, justify=tk.RIGHT).pack(side=tk.RIGHT, anchor=tk.E) tkd.Label(v_fr, text='Downloads: %s' % main_fr.dl_count, justify=tk.LEFT).pack(side=tk.LEFT, anchor=tk.W)
def create_row(var_name, lf, default_val='0'): fr = tkd.Frame(lf, height=22, width=236) fr.propagate(False) fr.pack(expand=False, fill=tk.X) sv = tk.StringVar(fr, value=default_val) tkd.Label(fr, text='%s:' % var_name, font='helvetica 10', anchor=tk.W, justify=tk.LEFT).pack(side=tk.LEFT) tkd.Label(fr, textvariable=sv, font='helvetica 12 bold', anchor=tk.E, justify=tk.RIGHT).pack(side=tk.RIGHT) return sv
def _make_widgets(self): tkd.Label(self, text='Drops', font='helvetica 14').pack() lf = tkd.Frame(self) lf.pack(expand=1, fill=tk.BOTH) scrollbar = ttk.Scrollbar(lf, orient=tk.VERTICAL) self.m = tkd.Text(lf, height=8, width=23, yscrollcommand=scrollbar.set, font='courier 11', wrap=tk.WORD, state=tk.DISABLED, cursor='', exportselection=1, name='droplist', borderwidth=2) self.m.pack(side=tk.LEFT, fill=tk.BOTH, expand=1, pady=(1, 2), padx=1) scrollbar.config(command=self.m.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=(2, 1), padx=0)
def add_automode_flag(self): lf = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) lf.propagate(False) lf.pack(expand=False, fill=tk.X) lab = tkd.Label( lf, text='Automode', tooltip= 'Enable automode for monitoring when you enter and exit games') lab.pack(side=tk.LEFT) self.automode_var = tk.StringVar(lf) off_btn = tkd.Radiobutton(lf, text='Off', variable=self.automode_var, indicatoron=False, value=0, width=5, padx=4) simple_btn = tkd.Radiobutton(lf, text='Simple', variable=self.automode_var, indicatoron=False, value=1, width=5, padx=3) adv_btn = tkd.Radiobutton(lf, text='Advanced', variable=self.automode_var, indicatoron=False, value=2, width=7, padx=3) cfg_mode = other_utils.safe_eval( self.main_frame.cfg['AUTOMODE']['automode']) if cfg_mode == 2: adv_btn.invoke() elif cfg_mode == 1: simple_btn.invoke() else: off_btn.invoke() off_btn.config(command=self.toggle_automode_btn) simple_btn.config(command=self.toggle_automode_btn) adv_btn.config(command=self.toggle_automode_btn) adv_btn.pack(side=tk.RIGHT) simple_btn.pack(side=tk.RIGHT) off_btn.pack(side=tk.RIGHT) return off_btn, simple_btn, adv_btn
def _make_widgets(self): tkd.Label(self, text='Select active profile', justify=tk.LEFT).pack(anchor=tk.W) profile_dropdown_frame = tkd.Frame(self, height=28, width=238, pady=2, padx=2) profile_dropdown_frame.propagate(False) profile_dropdown_frame.pack() self.active_profile.set(self.main_frame.active_profile) self.profile_dropdown = tkd.Combobox(profile_dropdown_frame, textvariable=self.active_profile, state='readonly', values=self.main_frame.profiles) self.profile_dropdown.bind("<<ComboboxSelected>>", lambda e: self._change_active_profile()) self.profile_dropdown.bind("<FocusOut>", lambda e: self.profile_dropdown.selection_clear()) self.profile_dropdown.pack(side=tk.LEFT, expand=True, fill=tk.X) tkd.Button(profile_dropdown_frame, text='New...', command=self._add_new_profile).pack(side=tk.LEFT) tkd.Button(profile_dropdown_frame, text='Delete', command=self._delete_profile).pack(side=tk.LEFT) self.run_type = tk.StringVar(self, value=self.extra_data.get('Run type', '')) self.game_mode = tk.StringVar(self, value=self.extra_data.get('Game mode', 'Single Player')) self.char_name = tk.StringVar(self, value=self.extra_data.get('Character name', '')) self._extra_info_label('Run type', self.run_type) # self._extra_info_label('Game mode', self.game_mode) self._extra_info_label('Character name', self.char_name) tkd.Label(self, text='Select an archived run for this profile', justify=tk.LEFT).pack(anchor=tk.W, pady=(6, 0)) sel_frame = tkd.Frame(self, height=28, width=238, pady=2, padx=2) sel_frame.propagate(False) sel_frame.pack() self.archive_dropdown = tkd.Combobox(sel_frame, textvariable=self.selected_archive, state='readonly', values=self.available_archive) self.archive_dropdown.bind("<<ComboboxSelected>>", lambda e: self.update_descriptive_statistics()) self.archive_dropdown.bind("<FocusOut>", lambda e: self.archive_dropdown.selection_clear()) self.archive_dropdown.pack(side=tk.LEFT, expand=True, fill=tk.X) tkd.Button(sel_frame, text='Open', command=lambda: archive_browser.ArchiveBrowser(self.main_frame)).pack(side=tk.LEFT) tkd.Button(sel_frame, text='Delete', command=self.delete_archived_session).pack(side=tk.LEFT) self.descr = tkd.Listbox(self, selectmode=tk.EXTENDED, height=8, activestyle='none', font=('courier', 8)) self.descr.bind('<FocusOut>', lambda e: self.descr.selection_clear(0, tk.END)) self.descr.pack(side=tk.BOTTOM, fill=tk.X, expand=1, anchor=tk.S)
def add_num_entry(self, flag_name, comment=None): lf = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) lf.propagate(False) lf.pack(expand=False, fill=tk.X) lab = tkd.Label(lf, text=flag_name) lab.pack(side=tk.LEFT) if comment is not None: tkd.create_tooltip(lab, comment) flag_attr = flag_name.lower().replace(' ', '_').replace('-', '_').replace('(', '').replace(')', '') setattr(self, flag_attr + '_sv', tk.StringVar()) sv = getattr(self, flag_attr + '_sv') sv.set(other_utils.safe_eval(self.main_frame.cfg['OPTIONS'][flag_attr])) tkd.RestrictedEntry(lf, textvariable=sv, num_only=True, width=13).pack(side=tk.RIGHT, padx=3) sv.trace_add('write', lambda name, index, mode: setattr(self.main_frame, flag_attr, float('0' + sv.get())))
def _make_widgets(self): flt = tkd.Frame(self) flt.pack(fill=tk.X, expand=tk.NO) self.c1, self.circ_id = tk_utils.add_circle(flt, 14, 'red') self.c1.grid(row=0, column=0, padx=3, pady=3) tkd.Label(flt, textvariable=self.sessionstr, font='arial 10').grid(row=0, column=1, sticky=tk.N, padx=20) self._set_time(self.session_time, for_session=True) tkd.Label(self, textvariable=self.timestr, font='arial 20').pack(fill=tk.X, expand=False, pady=4) self._set_time(0, for_session=False) l2f = tkd.Frame(self) l2f.pack(pady=2) tkd.Label(l2f, text='---- Run count:', font='arial 12').pack(side=tk.LEFT) tkd.RunLabel(l2f, textvariable=self.no_of_laps, font='arial 17').pack(side=tk.LEFT) tkd.RunLabel(l2f, textvariable=self.total_laps, font='helvetica 12').pack(side=tk.LEFT) tkd.Label(l2f, text='----', font='arial 12').pack(side=tk.LEFT) self._set_laps(add_lap=False) tkd.Label(self, textvariable=self.min_lap, font='arial 11').pack(fill=tk.X, expand=False, pady=1, padx=2) self._set_fastest() tkd.Label(self, textvariable=self.avg_lap, font='arial 11').pack(fill=tk.X, expand=False, pady=1, padx=2) self._set_average() lf0 = tkd.Frame(self) lf0.pack() scrollbar = ttk.Scrollbar(lf0, orient=tk.VERTICAL) self.m = tkd.Listbox(lf0, selectmode=tk.BROWSE, height=5, yscrollcommand=scrollbar.set, font='courier 12', activestyle=tk.NONE) self.m.bind('<FocusOut>', lambda e: self.m.selection_clear(0, tk.END)) self.m.bind( '<MouseWheel>', lambda e: self.m.yview_scroll(int(-1 * (e.delta / 120)), "units")) self.m.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, pady=5) scrollbar.config(command=self.m.yview) scrollbar.pack(side=tk.RIGHT, fill=tk.Y, pady=5, padx=1)
def rec_checkbox_add(master, frame, dct, rows=4, depth=None): # Default arguments cannot be mutable if depth is None: depth = [] # Init count to determine number of rows before adding a new column cnt = 0 for k, v in dct.items(): # Bottom of tree nodes are either empty dicts or has the 'wasFound' key if v == dict() or 'wasFound' in v: found = v.get('wasFound', False) # Due to weird handling of rainbow facets in herokuapp, we utilise the saved stack of keys (in the # 'depth' variable) to determine the appropriate item name if k in ['Cold', 'Fire', 'Light', 'Poison']: i_name = 'Rainbow Facet (%s %s)' % (k, depth[-1].title()) var_name = 'grail_item_' + i_name.replace("'", "1").replace(' ', '_') else: # Replace characters that cannot be used in variable names var_name = 'grail_item_' + k.replace("'", "1").replace(' ', '_') i_name = k # Define an IntVar as a class attribute that we can later call when needed. Then build the # checkbutton, noting that the lambda needs to be passed a default argument, otherwise it will # overwrite its own definition at each iteration setattr(master, var_name, tk.IntVar(value=found)) tkd.Checkbutton(frame, text=k, variable=getattr(master, var_name), command=lambda _k=i_name: master.update_grail_from_name(_k)).pack(expand=True, anchor=tk.W) # We are not at the bottom of tree node, thus we will create a child node and call recursively else: # When at top node, determine if a new column should be made or not, based on number of rows if len(depth) == 0: if cnt % rows == 0: topframe = tkd.Frame(frame) topframe.pack(side=tk.LEFT, expand=True, anchor=tk.NW) new_frame = tkd.Frame(topframe) new_frame.pack(side=tk.TOP, expand=True, anchor=tk.NW, fill=tk.Y, pady=[0, 30]) cnt += 1 else: new_frame = tkd.Frame(frame) new_frame.pack(side=tk.LEFT, expand=True, anchor=tk.N) # .title() function bugs out with apostrophes. Handle the specific issues hardcoded here # Also, there is a spelling mistake in herokuapp that we fix: Hsaru's -> Hsarus' txt = k.title().replace("'S", "'s").replace("'A", "'a").replace("Hsaru's", "Hsarus'") tkd.Label(new_frame, text=txt, font='Arial 15 bold').pack(expand=True, anchor=tk.N) rec_checkbox_add(master, new_frame, v, rows, depth + [k])
def add_theme_choice(self, comment=None): lf = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) lf.propagate(False) lf.pack(expand=False, fill=tk.X) lab = tkd.Label(lf, text='Active theme') lab.pack(side=tk.LEFT) if comment is not None: tkd.create_tooltip(lab, comment) self.active_theme = tk.StringVar() self.active_theme.set(self.main_frame.active_theme) theme_choices = tkd.Combobox(lf, textvariable=self.active_theme, state='readonly', values=available_themes) theme_choices.bind("<FocusOut>", lambda e: theme_choices.selection_clear()) theme_choices.bind("<<ComboboxSelected>>", lambda e: self._change_theme()) theme_choices.config(width=11) theme_choices.pack(side=tk.RIGHT, fill=tk.X, padx=2)
def make_widgets(self): tkd.Label(self, text='Advanced stats tracker', font='Helvetica 15').pack() lf0 = tkd.LabelFrame(self) lf0.pack(expand=False, fill=tk.X, padx=1) self.create_row(self.mf_sv, label_name='MF', lf=lf0) self.create_row(self.players_x_sv, label_name='Players X', lf=lf0) lf1 = tkd.LabelFrame(self) lf1.pack(expand=False, fill=tk.X, padx=1, pady=[8, 0]) self.create_row(self.unique_kills_sv, label_name='Unique kills', lf=lf1) self.create_row(self.champ_kills_sv, label_name='Champion kills', lf=lf1) self.xp_lf1 = tkd.LabelFrame(self) self.xp_lf1.pack(expand=False, fill=tk.X, padx=1, pady=8) self.create_row(self.exp_perc_sv, label_name='Exp %', lf=self.xp_lf1) self.create_row(self.exp_session_sv, label_name='Exp (session)', lf=self.xp_lf1) self.create_row(self.exp_run_sv, label_name='Exp (run)', lf=self.xp_lf1) self.create_row(self.exp_hour_sv, label_name='Exp / hour', lf=self.xp_lf1) self.xp_lf2 = tkd.LabelFrame(self) self.xp_lf2.pack(expand=False, fill=tk.X, padx=1) self.create_row(self.exp_level_sv, label_name='Exp to level', lf=self.xp_lf2) self.create_row(self.hours_level_sv, label_name='Time to level', lf=self.xp_lf2) self.create_row(self.runs_level_sv, label_name='Runs to level', lf=self.xp_lf2)
def __init__(self, main_frame, **kw): tkd.Frame.__init__(self, main_frame.root, **kw) self.main_frame = main_frame self.session_char_xp_start = 0 self.session_char_xp = 0 self.session_char_time_start = time.time() self.session_char_xp_missing = 0 self.avg_run = 0 self.session_xp_runs = set() # ==================================== WIDGETS ==================================== # tkd.Label(self, text='Advanced stats tracker', font='Helvetica 15').pack() lf1 = tkd.LabelFrame(self) lf1.pack(expand=False, fill=tk.X, padx=1) self.name_sv = self.create_row('Name', lf=lf1, default_val='-----') self.level_sv = self.create_row('Level', lf=lf1, default_val='-----') self.mf_sv = self.create_row('MF', lf=lf1, default_val='-----') self.players_x_sv = self.create_row('Players X', lf=lf1, default_val='-----') lf2 = tkd.LabelFrame(self) lf2.pack(expand=False, fill=tk.X, padx=1, pady=8) self.exp_sv = self.create_row('Exp %', lf=lf2) self.exp_session_sv = self.create_row('Exp (session)', lf=lf2) self.exp_run_sv = self.create_row('Exp (run)', lf=lf2) self.exp_hour_sv = self.create_row('Exp / hour', lf=lf2) lf3 = tkd.LabelFrame(self) lf3.pack(expand=False, fill=tk.X, padx=1) self.exp_level_sv = self.create_row('Exp to level', lf=lf3) self.hours_level_sv = self.create_row('Time to level', lf=lf3) self.runs_level_sv = self.create_row('Runs to level', lf=lf3) # ==================================== WIDGETS ==================================== # color_themes.Theme(main_frame.active_theme).update_colors()
def open_archive_browser(self): chosen = self.archive_dropdown.get() if chosen == '': # If nothing is selected the function returns return # We build the new tkinter window to be opened new_win = tkd.Toplevel() new_win.title('Archive browser') new_win.wm_attributes('-topmost', 1) disp_coords = tk_utils.get_displaced_geom(self.main_frame.root, 400, 460) new_win.geometry(disp_coords) new_win.focus_get() new_win.iconbitmap(os.path.join(getattr(sys, '_MEIPASS', os.path.abspath('.')), media_path + 'icon.ico')) new_win.minsize(400, 460) title = tkd.Label(new_win, text='Archive browser', font='Helvetica 14') # Handle how loading of session data should be treated in the 3 different cases if chosen == 'Active session': # Load directly from timer module session_time = self.main_frame.timer_tab.session_time laps = self.main_frame.timer_tab.laps drops = self.main_frame.drops_tab.drops elif chosen == 'Profile history': # Load everything from profile .json, and append data from timer module active = self.main_frame.load_state_file() laps = [] session_time = 0 drops = dict() # Concatenate information from each available session for key in [x for x in active.keys() if x not in ['active_state', 'extra_data']]: session_drops = active[key].get('drops', dict()) for run_no, run_drop in session_drops.items(): drops[str(int(run_no)+len(laps))] = run_drop laps.extend(active[key].get('laps', [])) session_time += active[key].get('session_time', 0) # Append data for active session from timer module for run_no, run_drop in self.main_frame.drops_tab.drops.items(): drops[str(int(run_no) + len(laps))] = run_drop laps.extend(self.main_frame.timer_tab.laps) session_time += self.main_frame.timer_tab.session_time else: # Load selected session data from profile .json active = self.main_frame.load_state_file() chosen_archive = active.get(chosen, dict()) session_time = chosen_archive.get('session_time', 0) laps = chosen_archive.get('laps', []) drops = chosen_archive.get('drops', dict()) # Ensure no division by zero errors by defaulting to displaying 0 avg_lap = sum(laps) / len(laps) if laps else 0 pct = sum(laps) * 100 / session_time if session_time > 0 else 0 # Configure the list frame with scrollbars which displays the archive of the chosen session list_win = tkd.Frame(new_win) list_frame = tkd.Frame(list_win) vscroll = ttk.Scrollbar(list_frame, orient=tk.VERTICAL) hscroll = ttk.Scrollbar(list_win, orient=tk.HORIZONTAL) txt_list = tkd.Text(list_frame, yscrollcommand=vscroll.set, xscrollcommand=hscroll.set, font='courier 10', wrap=tk.WORD, state=tk.NORMAL, cursor='', exportselection=1, name='archivebrowser') # txt_list.bind('<FocusOut>', lambda e: txt_list.tag_remove(tk.SEL, "1.0", tk.END)) # Lose selection when shifting focus vscroll.pack(side=tk.RIGHT, fill=tk.Y) txt_list.pack(side=tk.LEFT, fill=tk.BOTH, expand=1) txt_list.tag_configure("HEADER", font=tkFont(family='courier', size=12, weight='bold', underline=True)) hscroll.config(command=txt_list.xview) vscroll.config(command=txt_list.yview) # Build header for output file with information and descriptive statistics output = [['Statistics'], ['Character name: ', self.extra_data.get('Character name', '')], ['Run type: ', self.extra_data.get('Run type', '')], ['Game mode: ', self.extra_data.get('Game mode', 'Single Player')], [''], ['Total session time: ', utils.other_utils.build_time_str(session_time)], ['Total run time: ', utils.other_utils.build_time_str(sum(laps))], ['Average run time: ', utils.other_utils.build_time_str(avg_lap)], ['Fastest run time: ', utils.other_utils.build_time_str(min(laps, default=0))], ['Number of runs: ', str(len(laps))], ['Time spent in runs: ', str(round(pct, 2)) + '%'], ['']] # Backwards compatibility with old drop format for k, v in drops.items(): for i in range(len(v)): if not isinstance(v[i], dict): drops[k][i] = {'item_name': None, 'input': v[i], 'extra': ''} # List all drops collected if drops: if any(drop for drop in drops.values()): output.append(['Collected drops']) for run_no, drop in drops.items(): if drop: str_n = ' ' * max(len(str(len(laps))) - len(str(run_no)), 0) + str(run_no) output.append(['Run ' + str_n, '', *[x['input'] for x in drop]]) output.append(['']) if laps: output.append(['Run times']) # Loop through all runs and add run times and drops for each run for n, lap in enumerate(laps, 1): str_n = ' ' * max(len(str(len(laps))) - len(str(n)), 0) + str(n) droplst = drops.get(str(n), []) tmp = ['Run ' + str_n + ': ', utils.other_utils.build_time_str(lap)] if droplst: tmp += [d['input'] for d in droplst] output.append(tmp) # Format string list to be shown in the archive browser for i, op in enumerate(output, 1): tmpstr = ''.join(op[:2]) if len(op) > 2: tmpstr += ' --- ' + ', '.join(op[2:]) if txt_list.get('1.0', tk.END) != '\n': tmpstr = '\n' + tmpstr txt_list.insert(tk.END, tmpstr) if op[0] in ['Statistics', 'Collected drops', 'Run times']: txt_list.tag_add("HEADER", str(i) + ".0", str(i) + ".0 lineend") # Add bold tags # txt_list.tag_add("BOLD", "1.0", "1.15") # txt_list.tag_add("BOLD", "2.0", "2.9") # txt_list.tag_add("BOLD", "3.0", "3.10") # txt_list.tag_add("BOLD", "5.0", "5.19") # txt_list.tag_add("BOLD", "6.0", "6.15") # txt_list.tag_add("BOLD", "7.0", "7.17") # txt_list.tag_add("BOLD", "8.0", "8.17") # txt_list.tag_add("BOLD", "9.0", "9.15") # txt_list.tag_add("BOLD", "10.0", "10.19") # txt_list.tag_add("BOLD", "1.16", "1.0 lineend") # txt_list.tag_add("BOLD", "2.16", "2.0 lineend") # txt_list.tag_add("BOLD", "3.16", "3.0 lineend") # txt_list.tag_add("BOLD", "5.20", "5.0 lineend") # txt_list.tag_add("BOLD", "6.20", "6.0 lineend") # txt_list.tag_add("BOLD", "7.20", "7.0 lineend") # txt_list.tag_add("BOLD", "8.20", "8.0 lineend") # txt_list.tag_add("BOLD", "9.20", "9.0 lineend") # txt_list.tag_add("BOLD", "10.20", "10.0 lineend") txt_list.tag_add("HEADER", "12.0", "12.0 lineend") txt_list.config(state=tk.DISABLED) button_frame = tkd.Frame(new_win) tkd.Button(button_frame, text='Copy to clipboard', command=lambda: self.copy_to_clipboard(new_win, txt_list.get(1.0, tk.END))).pack(side=tk.LEFT, fill=tk.X) tkd.Button(button_frame, text='Save as .txt', command=lambda: self.save_to_txt(txt_list.get(1.0, tk.END))).pack(side=tk.LEFT, fill=tk.X) tkd.Button(button_frame, text='Save as .csv', command=lambda: self.save_to_csv(output)).pack(side=tk.LEFT, fill=tk.X) # Packs all the buttons and UI in the archive browser. Packing order is very important: # TOP: Title first (furthest up), then list frame # BOTTOM: Buttons first (furthest down) and then horizontal scrollbar title.pack(side=tk.TOP) list_win.pack(side=tk.TOP, fill=tk.BOTH, expand=1) list_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=1) hscroll.pack(side=tk.BOTTOM, fill=tk.X) button_frame.pack(side=tk.BOTTOM) theme = Theme(self.main_frame.active_theme) theme.update_colors()
def make_widgets(self): self.gamemode_frame = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) self.gamemode_frame.propagate(False) self.gamemode_lab = tkd.Label( self.gamemode_frame, text='Game mode', tooltip= 'If Multiplayer is selected, the .map file is used to check for updates.\n' 'Thus, new runs begin every time you enter a new game (since your local .map files will be updated by this)\n\n' 'If Single Player is selected the .d2s file is used to check for updates.\n' 'Thus, a new run begins every time you leave a game (since your .d2s files are saved upon exit)' ) self.game_mode = tk.StringVar() self.game_mode.set(self.main_frame.profile_tab.game_mode.get()) self.gamemode_cb = ttk.Combobox( self.gamemode_frame, textvariable=self.game_mode, state='readonly', values=['Single Player', 'Multiplayer']) self.gamemode_cb.bind("<FocusOut>", lambda e: self.gamemode_cb.selection_clear()) self.gamemode_cb.config(width=11) self.game_mode.trace_add( 'write', lambda name, index, mode: self.update_game_mode()) self.charname_frame = tkd.LabelFrame(self, height=LAB_HEIGHT, width=LAB_WIDTH) self.charname_frame.propagate(False) self.char_var = tk.StringVar() self.char_var.set(self.main_frame.profile_tab.char_name.get()) self.charname_text_lab = tkd.Label( self.charname_frame, text='Character name', tooltip='Your character name is inferred from the active profile.\n' 'Make sure the character name in your profile is matching your in-game character name' ) self.charname_val_lab = tkd.Label(self.charname_frame, textvariable=self.char_var) self.sp_path_lab = tkd.Label(self, text='Game path (Single Player)') self.SP_game_path = tk.StringVar() self.SP_game_path.set(self.main_frame.SP_game_path) self.sp_path_entry = tkd.Entry(self, textvariable=self.SP_game_path) self.sp_path_frame = tkd.Frame(self) self.sp_path_get = tkd.Button( self.sp_path_frame, text='Get', command=lambda: self.get_game_path(is_sp=True), tooltip= 'The app tries to automatically find your game path for single player\n' 'If nothing is returned you have to type it in manually') self.sp_path_apply = tkd.Button( self.sp_path_frame, text='Apply', command=self.apply_path_ch, tooltip='Apply the current specified path') self.mp_path_lab = tkd.Label(self, text='Game path (Multiplayer)') self.MP_game_path = tk.StringVar() self.MP_game_path.set(self.main_frame.MP_game_path) self.mp_path_entry = tkd.Entry(self, textvariable=self.MP_game_path) self.mp_path_frame = tkd.Frame(self) self.mp_path_get = tkd.Button( self.mp_path_frame, text='Get', command=lambda: self.get_game_path(is_sp=False), tooltip= 'The app tries to automatically find your game path for multiplayer\n' 'If nothing is returned you have to type it in manually') self.mp_path_apply = tkd.Button( self.mp_path_frame, text='Apply', command=self.apply_path_ch, tooltip='Apply the current specified path') # Stuff for advanced mode self.advanced_mode_stop = self.add_flag( flag_name='Stop when leaving', comment= 'On: Stops the current run when you exit to menu.\nOff: The run counter will continue ticking until you enter a new game', pack=False, config_section='AUTOMODE') self.advanced_pause_on_esc_menu = self.add_flag( flag_name='Pause on ESC menu', comment= 'When activated, the counter will be paused when ESC menu\nis open inside d2 (not working for 1.14b and 1.14c)', pack=False, config_section='AUTOMODE') self.advanced_automode_warning = tkd.Label( self, text='"Advanced automode" is highly \n' 'discouraged when playing\n' 'multiplayer, as it might result\n' 'in a ban.\n' 'Explanation: Advanced automode\n' 'utilizes "memory reading" of the\n' 'D2 process to discover information\n' 'about the current game state,\n' 'and this could be deemed cheating.', justify=tk.LEFT) self.toggle_automode_btn(first=True)
def __init__(self): # Check if application is already open self.title = 'MF run counter' # Create error logger lh = logging.FileHandler(filename='mf_timer.log', mode='w', delay=True) logging.basicConfig(handlers=[lh], format='%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s', datefmt='%H:%M:%S', level=logging.WARNING) # Check OS self.os_platform = platform.system() self.os_release = platform.release() if not self.os_platform == 'Windows': raise SystemError("MF Run Counter only supports windows") # Create root self.root = tkd.Tk() # Ensure errors are handled with an exception pop-up if encountered self.root.report_callback_exception = self.report_callback_exception # Build/load config file self.cfg = self.load_config_file() if hasattr(logging, self.cfg['DEFAULT']['logging_level']): logging.getLogger().setLevel(getattr(logging, self.cfg['DEFAULT']['logging_level'])) self.SP_game_path = self.cfg['DEFAULT']['SP_game_path'] self.MP_game_path = self.cfg['DEFAULT']['MP_game_path'] self.herokuapp_username = self.cfg['DEFAULT']['herokuapp_username'] self.herokuapp_password = base64.b64decode(self.cfg['DEFAULT']['herokuapp_password']).decode('utf-8') self.webproxies = other_utils.safe_eval(self.cfg['DEFAULT']['webproxies']) self.automode = other_utils.safe_eval(self.cfg['AUTOMODE']['automode']) self.end_run_in_menu = other_utils.safe_eval(self.cfg['AUTOMODE']['end_run_in_menu']) self.pause_on_esc_menu = other_utils.safe_eval(self.cfg['AUTOMODE']['pause_on_esc_menu']) self.always_on_top = other_utils.safe_eval(self.cfg['OPTIONS']['always_on_top']) self.tab_switch_keys_global = other_utils.safe_eval(self.cfg['OPTIONS']['tab_switch_keys_global']) self.check_for_new_version = other_utils.safe_eval(self.cfg['OPTIONS']['check_for_new_version']) self.enable_sound_effects = other_utils.safe_eval(self.cfg['OPTIONS']['enable_sound_effects']) self.start_run_delay_seconds = other_utils.safe_eval(self.cfg['OPTIONS']['start_run_delay_seconds']) self.show_drops_tab_below = other_utils.safe_eval(self.cfg['OPTIONS']['show_drops_tab_below']) self.active_theme = self.cfg['OPTIONS']['active_theme'].lower() self.auto_upload_herokuapp = other_utils.safe_eval(self.cfg['OPTIONS']['auto_upload_herokuapp']) self.auto_archive_hours = other_utils.safe_eval(self.cfg['OPTIONS']['auto_archive_hours']) self.autocompletion_unids = other_utils.safe_eval(self.cfg['OPTIONS']['autocompletion_unids']) self.add_to_last_run = other_utils.safe_eval(self.cfg['OPTIONS']['add_to_last_run']) self.disable_scaling = other_utils.safe_eval(self.cfg['OPTIONS']['disable_dpi_scaling']) # UI config self.show_buttons = other_utils.safe_eval(self.cfg['UI']['show_buttons']) self.show_drops_section = other_utils.safe_eval(self.cfg['UI']['show_drops_section']) self.show_advanced_tracker = other_utils.safe_eval(self.cfg['UI']['show_advanced_tracker']) self.show_xp_tracker = other_utils.safe_eval(self.cfg['UI']['show_xp_tracker']) # Initiate variables for memory reading self.is_user_admin = reader_utils.is_user_admin() self.advanced_error_thrown = False self.d2_reader = None # Load theme if self.active_theme not in available_themes: self.active_theme = 'vista' self.theme = Theme(used_theme=self.active_theme) # Create hotkey queue and initiate process for monitoring the queue self.queue = queue.Queue(maxsize=1) self.process_queue() # Check for version update if self.check_for_new_version: self.dl_count = github_releases.check_newest_version(release_repo) else: self.dl_count = '' # Load profile info self.make_profile_folder() self.profiles = [x[:-5] for x in os.listdir('Profiles') if x.endswith('.json') and not x == 'grail.json'] self.active_profile = self.cfg['DEFAULT']['active_profile'] if len(self.profiles) == 0: self.active_profile = '' elif len(self.profiles) > 0 and self.active_profile not in self.profiles: self.active_profile = self.profiles[0] self.profiles = self.sorted_profiles() # Modify root window self.root.title(self.title) self.clickable = True self.root.resizable(False, False) self.root.geometry('+%d+%d' % other_utils.safe_eval(self.cfg['DEFAULT']['window_start_position'])) self.root.config(borderwidth=2, height=365, width=240, relief='raised') # self.root.wm_attributes("-transparentcolor", "purple") self.root.wm_attributes("-topmost", self.always_on_top) self.root.focus_get() self.root.protocol("WM_DELETE_WINDOW", self.Quit) self.root.iconbitmap(media_path + 'icon.ico') self.root.pack_propagate(False) # Build banner image and make window draggable on the banner d2banner = media_path + 'd2icon.png' img = tk.PhotoImage(file=d2banner) self.img_panel = tkd.Label(self.root, image=img, borderwidth=0) self.img_panel.pack() self.img_panel.bind("<ButtonPress-1>", self.root.start_move) self.img_panel.bind("<ButtonRelease-1>", self.root.stop_move) self.img_panel.bind("<B1-Motion>", self.root.on_motion) self.root.bind("<Delete>", self.delete_selection) self.root.bind("<Left>", self.root.moveleft) self.root.bind("<Right>", self.root.moveright) self.root.bind("<Up>", self.root.moveup) self.root.bind("<Down>", self.root.movedown) # Add buttons to main widget self.btn_frame = tkd.Frame(self.root) tkd.Button(self.btn_frame, text='Delete selection', command=self.delete_selection).pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=[2, 1], pady=1) tkd.Button(self.btn_frame, text='Archive session', command=self.ArchiveReset).pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=[0, 1], pady=1) # Build tabs self.caret_frame = tkd.Frame(self.root) self.drops_frame = tkd.Frame(self.caret_frame) self.adv_stats_frame = tkd.Frame(self.caret_frame) self.tabcontrol = tkd.Notebook(self.root) self.tabcontrol.pack(expand=False, fill=tk.BOTH) self.profile_tab = Profile(self, parent=self.tabcontrol) self.timer_tab = MFRunTimer(self, parent=self.tabcontrol) self.drops_tab = Drops(self, parent=self.drops_frame) self.options_tab = Options(self, self.timer_tab, self.drops_tab, parent=self.tabcontrol) self.grail_tab = Grail(self, parent=self.tabcontrol) self.about_tab = About(self, parent=self.tabcontrol) self.tabcontrol.add(self.timer_tab, text='Timer') self.tabcontrol.add(self.options_tab, text='Options') self.tabcontrol.add(self.profile_tab, text='Profile') self.tabcontrol.add(self.grail_tab, text='Grail') self.tabcontrol.add(self.about_tab, text='About') self.root.bind("<<NotebookTabChanged>>", lambda _e: self.notebook_tab_change()) self.profile_tab.update_descriptive_statistics() self.toggle_drops_frame(show=self.show_drops_tab_below) self.drops_caret = tkd.CaretButton(self.drops_frame, active=self.show_drops_tab_below, command=self.toggle_drops_frame, text='Drops', compound=tk.RIGHT, height=13) self.drops_caret.propagate(False) self.drops_caret.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=[2, 1], pady=[0, 1]) tracker_is_active = other_utils.safe_eval(self.cfg['AUTOMODE']['advanced_tracker_open']) and self.automode == 2 and self.is_user_admin self.advanced_stats_tracker = StatsTracker(self, self.adv_stats_frame) self.advanced_stats_caret = tkd.CaretButton(self.adv_stats_frame, active=tracker_is_active, text='Advanced stats', compound=tk.RIGHT, height=13, command=self.toggle_advanced_stats_frame) self.advanced_stats_caret.propagate(False) self.advanced_stats_caret.pack(side=tk.BOTTOM, fill=tk.X, expand=True, padx=[2, 1], pady=[0, 1]) # Register binds for changing tabs if self.tab_switch_keys_global: self.options_tab.tab2.hk.register(['control', 'shift', 'next'], callback=lambda event: self.queue.put(self.tabcontrol.next_tab)) self.options_tab.tab2.hk.register(['control', 'shift', 'prior'], callback=lambda event: self.queue.put(self.tabcontrol.prev_tab)) else: self.root.bind_all('<Control-Shift-Next>', lambda event: self.tabcontrol.next_tab()) self.root.bind_all('<Control-Shift-Prior>', lambda event: self.tabcontrol.prev_tab()) # Load save state and start autosave process active_state = self.load_state_file() self.LoadActiveState(active_state) self.root.after(30000, self._autosave_state) # Apply styling options self.theme.apply_theme_style() self.theme.update_colors() # Automode and advanced stats loop self.am_lab = tk.Text(self.root, height=1, width=13, wrap=tk.NONE, bg="black", font=('Segoe UI', 9), cursor='', borderwidth=0) self.am_lab.tag_configure("am", foreground="white", background="black") self.am_lab.tag_configure("on", foreground="lawn green", background="black") self.am_lab.tag_configure("off", foreground="red", background="black") self.am_lab.place(x=1, y=0.4) self.toggle_automode() self.toggle_advanced_stats_frame(show=tracker_is_active) # A trick to disable windows DPI scaling - the app doesnt work well with scaling, unfortunately if self.os_release == '10' and self.disable_scaling: ctypes.windll.shcore.SetProcessDpiAwareness(2) # Used if "auto archive session" is activated self.profile_tab.auto_reset_session() # Pressing ALT_L paused UI updates when in focus, disable (probably hooked to opening menus) self.root.unbind_all('<Alt_L>') # Start the program self.root.mainloop()