def clear_data(self): if self.thread is not None: self.thread.stop() self.tree.delete(*self.tree.get_children()) self.queue = queue.Queue() self.state = auction.AuctionState() self.state.update({ 'action': 'initialize', 'timestamp': dt.datetime.now(), 'api_token': self.api_token })
def test_failed_bid_case(): auc = auction.AuctionState() lines = [ "[Wed Jun 23 23:24:33 2019] You tell your raid, '!Bids open !2 Cloak of Flames'", "[Wed Jun 23 23:07:49 2019] Foo -> Quaff: Cloak of Flames 35", "[Wed Jun 23 23:07:49 2019] Bar -> Quaff: Cloak of Flames 56", "[Wed Jun 23 23:07:49 2019] Bar -> Quaff: I don't understand the rules, help?", ] for line in lines: action = parse.handle_line(line, set(['Cloak of Flames'])) # we assume that every action was parsed properly. parse failures will cause a type error here update = auc.update(action) assert update.status_messages[ 0] == "Failed to parse bid: Bar -> Quaff: I don't understand the rules, help?"
def test_whole_auction_case_4(): auc = auction.AuctionState() lines = [ "[Wed Jun 23 23:24:33 2019] You tell your raid, '!Bids open !2 Cloak of Flames'", "[Wed Jun 23 23:07:49 2019] Foo -> Quaff: Cloak of Flames 35", "[Wed Jun 23 23:07:49 2019] Bar -> Quaff: Cloak of Flames 56", "[Wed Jun 23 23:24:33 2019] You tell your raid, '!Bids closed Cloak of Flames '", "[Wed Jun 23 23:24:34 2019] You tell your raid, '!correction !award Cloak of Flames !to Baz 30'", ] for line in lines: action = parse.handle_line(line, set(['Cloak of Flames'])) # we assume that every action was parsed properly. parse failures will cause a type error here update = auc.update(action) assert len(auc.concluded_auctions) == 1 assert update.update_rows[0].winner == 'Baz' assert update.update_rows[0].price == '30'
def test_whole_auction_case_1(): auc = auction.AuctionState() lines = [ "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids open Cloak of Flames'", "[Wed Jun 12 23:07:49 2019] Foobar -> Quaff: Cloak of Flames 10", "[Wed Jun 12 23:07:49 2019] Playerone -> Quaff: Cloak of Flames 10", "[Wed Jun 12 23:07:49 2019] Playerone -> Quaff: Cloak of Flames 11", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Cloak of Flames '", ] for line in lines: # we assume that every action was parsed properly. parse failures will cause a type error here action = parse.handle_line(line, set(['Cloak of Flames'])) auc.update(action) finished_auction = auc.concluded_auctions[0] assert finished_auction['item'] == 'Cloak of Flames' assert len(finished_auction['bids']) == 2
def test_failed_bid_case(): auc = auction.AuctionState("Tester") lines = [ "[Wed Jun 23 23:24:33 2019] You tell your raid, '!Bids open !2 Cloak of Flames'", "[Wed Jun 23 23:07:49 2019] Foo tells you, 'Cloak of Flames 35'", "[Wed Jun 23 23:07:49 2019] Bar tells you, 'Cloak of Flames 56'", "[Wed Jun 23 23:07:49 2019] Bar tells you, 'I don't understand the rules, help?", ] for line in lines: actions = parse.handle_line(line, set(['Cloak of Flames'])) # we assume that every action was parsed properly. parse failures will cause a type error here if not isinstance(actions, list): actions = [actions] for action in actions: update = auc.update(action) assert update.status_messages[ 0] == "Failed to parse bid: Bar tells you, 'I don't understand the rules, help?"
def test_preregister_bid_alt(): auc = auction.AuctionState("Tester") lines = [ "[Wed Jun 12 23:24:32 2019] You told Grunt, '!preregister Cloak of Flames 20 alt'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids open Cloak of Flames'", "[Wed Jun 12 23:07:49 2019] Foobar tells you, 'Cloak of Flames 5'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 5'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Cloak of Flames '", ] body = { 'message': 'Cloak of Flames awarded to - Tester for 1', 'warnings': [] } responses.add(responses.POST, 'http://padkp.net/api/resolve_auction/', json=body) for line in lines: # we assume that every action was parsed properly. parse failures will cause a type error here actions = parse.handle_line(line, set(['Cloak of Flames'])) if not isinstance(actions, list): actions = [actions] for action in actions: update = auc.update(action) data = json.loads(responses.calls[0].request.body) finished_auction = auc.concluded_auctions['Cloak of Flames'] assert finished_auction['item'] == 'Cloak of Flames' assert len(finished_auction['bids']) == 6 assert update.update_rows[ 0].winner == 'Cloak of Flames awarded to - Tester for 1' assert data['bids'] == [{ 'name': 'Tester', 'bid': 20, 'tag': 'ALT' }, { 'name': "Foobar", 'bid': 5, 'tag': '' }, { 'name': 'Playerone', 'bid': 5, 'tag': '' }]
def test_whole_auction_correction_after_close_multibid_case(): auc = auction.AuctionState("Tester") lines = [ "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids open !2 Cloak of Flames'", "[Wed Jun 12 23:07:49 2019] Foobar tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 11, 15 alt'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Cloak of Flames '", "[Wed Jun 23 23:24:34 2019] You tell your raid, '!correction !award Cloak of Flames !to Baz 30, Foobar 15'", ] body = { 'message': 'Cloak of Flames awarded to - Tester for 1', 'warnings': [] } responses.add(responses.POST, 'http://padkp.net/api/resolve_auction/', json=body) responses.add(responses.POST, 'http://padkp.net/api/correct_auction/', json=body) for line in lines: # we assume that every action was parsed properly. parse failures will cause a type error here actions = parse.handle_line(line, set(['Cloak of Flames'])) if not isinstance(actions, list): actions = [actions] for action in actions: update = auc.update(action) close_finger = json.loads(responses.calls[0].request.body)['fingerprint'] correct_finger = json.loads(responses.calls[1].request.body)['fingerprint'] data = json.loads(responses.calls[1].request.body) assert len(auc.concluded_auctions) == 1 assert close_finger == correct_finger assert data['bids'] == [{ 'name': 'Baz', 'bid': '30' }, { 'name': 'Foobar', 'bid': '15' }]
def test_preregister_bid(): auc = auction.AuctionState() auc.my_name = "Tester" lines = [ "[Wed Jun 12 23:24:32 2019] You told Grunt, '!preregister Cloak of Flames 20'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids open Cloak of Flames'", "[Wed Jun 12 23:07:49 2019] Foobar -> Quaff: Cloak of Flames 10", "[Wed Jun 12 23:07:49 2019] Playerone -> Quaff: Cloak of Flames 10", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Cloak of Flames '", ] for line in lines: # we assume that every action was parsed properly. parse failures will cause a type error here action = parse.handle_line(line, set(['Cloak of Flames'])) update = auc.update(action) finished_auction = auc.concluded_auctions[0] assert finished_auction['item'] == 'Cloak of Flames' assert len(finished_auction['bids']) == 3 assert update.update_rows[0].winner == 'Tester' assert update.update_rows[0].price == '20'
def test_whole_auction_cancel_pre_close_case(): auc = auction.AuctionState("Tester") lines = [ "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids open Cloak of Flames'", "[Wed Jun 12 23:07:49 2019] Foobar tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 11, 15 alt'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Cancel Cloak of Flames '", ] for line in lines: # we assume that every action was parsed properly. parse failures will cause a type error here actions = parse.handle_line(line, set(['Cloak of Flames'])) if not isinstance(actions, list): actions = [actions] for action in actions: update = auc.update(action) assert len(auc.concluded_auctions) == 0 assert auc.active_auctions == {}
def test_whole_auction_case_2(): auc = auction.AuctionState() lines = [ "[Wed Jun 12 23:01:33 2019] You tell your raid, '!Bids open Green Dragon Scale'", "[Wed Jun 12 23:02:49 2019] Foobar -> Quaff: Cloak of Flames 10", "[Wed Jun 12 23:03:49 2019] Foobar -> Quaff: Green Dragon Scale 10", "[Wed Jun 12 23:04:49 2019] Grunt -> Quaff: Green Dragon Scale 4", "[Wed Jun 12 23:05:49 2019] Papapa -> Quaff: Green Dragon Scale 5", "[Wed Jun 12 23:06:49 2019] Baz -> Quaff: Green Dragon Scale 10", "[Wed Jun 12 23:07:33 2019] You tell your raid, '!Bids closed Green Dragon Scale'", ] for line in lines: action = parse.handle_line( line, set(['Cloak of Flames', 'Green Dragon Scale'])) auc.update(action) assert len( auc.concluded_auctions) == 0 # bids are tied, auction wasn't completed tied_auction = list(auc.active_auctions.values())[0] assert tied_auction['item'] == 'Green Dragon Scale' assert len(tied_auction['bids']) == 4
def test_whole_auction_case_3(): auc = auction.AuctionState() lines = [ "[Wed Jun 12 23:01:33 2019] You tell your raid, '!Bids open Amulet of Necropotence'", "[Wed Jun 12 23:07:49 2019] Foo -> Quaff: Amulet of Necropotence 90", "[Wed Jun 12 23:07:49 2019] Bar -> Quaff: Amulet of Necropotence 112 ALT", "[Wed Jun 12 23:07:49 2019] Baz -> Quaff: Amulet of Necropotence 75", "[Wed Jun 12 23:07:49 2019] Qux -> Quaff: Amulet of Necropotence 40", "[Wed Jun 12 23:07:49 2019] Quux -> Quaff: Amulet of Necropotence 2", "[Wed Jun 12 23:07:49 2019] Thud -> Quaff: Amulet of Necropotence 89", "[Wed Jun 12 23:07:49 2019] Waldo -> Quaff: Amulet of Necropotence 13", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Amulet of Necropotence'", ] for line in lines: action = parse.handle_line(line, set(['Amulet of Necropotence'])) # we assume that every action was parsed properly. parse failures will cause a type error here update = auc.update(action) assert len(auc.concluded_auctions) == 1 assert len(update.update_rows) == 1 assert update.update_rows[0].winner == 'Foo'
def test_whole_auction_case_1(): auc = auction.AuctionState("Tester") lines = [ "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids open Cloak of Flames'", "[Wed Jun 12 23:07:49 2019] Foobar tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 11'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Cloak of Flames '", ] body = { 'message': 'Cloak of Flames awarded to - Playerone for 11', 'warnings': [] } responses.add(responses.POST, 'http://padkp.net/api/resolve_auction/', json=body) for line in lines: # we assume that every action was parsed properly. parse failures will cause a type error here actions = parse.handle_line(line, set(['Cloak of Flames'])) if not isinstance(actions, list): actions = [actions] for action in actions: auc.update(action) data = json.loads(responses.calls[0].request.body) finished_auction = auc.concluded_auctions['Cloak of Flames'] assert finished_auction['item'] == 'Cloak of Flames' assert len(finished_auction['bids']) == 4 assert data['bids'] == [{ 'name': 'Foobar', 'bid': 10, 'tag': '' }, { 'name': 'Playerone', 'bid': 11, 'tag': '' }] assert data['item_name'] == 'Cloak of Flames' assert data['item_count'] == 1
def test_whole_auction_cancel_after_close_case(): auc = auction.AuctionState("Tester") lines = [ "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids open Cloak of Flames'", "[Wed Jun 12 23:07:49 2019] Foobar tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 10'", "[Wed Jun 12 23:07:49 2019] Playerone tells you, 'Cloak of Flames 11, 15 alt'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Cloak of Flames '", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Cancel Cloak of Flames '", ] body = { 'message': 'Cloak of Flames awarded to - Tester for 1', 'warnings': [] } responses.add(responses.POST, 'http://padkp.net/api/resolve_auction/', json=body) responses.add(responses.POST, 'http://padkp.net/api/cancel_auction/', json=body) for line in lines: # we assume that every action was parsed properly. parse failures will cause a type error here actions = parse.handle_line(line, set(['Cloak of Flames'])) if not isinstance(actions, list): actions = [actions] for action in actions: update = auc.update(action) close_finger = json.loads(responses.calls[0].request.body)['fingerprint'] cancel_finger = json.loads(responses.calls[1].request.body)['fingerprint'] assert len(auc.concluded_auctions) == 0 assert auc.active_auctions == {} assert close_finger == cancel_finger
def __init__(self, master): self.master = master self.frame = tkinter.Frame(self.master) menu_bar = tkinter.Menu(master) file_menu = tkinter.Menu(menu_bar, tearoff=0) file_menu.add_command(label="Open log file (Ctrl-F)", command=self.open_log_file) file_menu.add_command(label="Choose raid dump directory (Ctrl-R)", command=self.choose_raid_dump_dir) file_menu.add_command(label="Enter API token (Ctrl-T)", command=self.ask_api_token) file_menu.add_command(label="Close (Ctrl-Q)", command=self.confirm_quit) menu_bar.add_cascade(label="File", menu=file_menu) auction_menu = tkinter.Menu(menu_bar, tearoff=0) auction_menu.add_command(label="See auction details (Ctrl-D)", command=self.open_details_window) auction_menu.add_command(label="Copy grats message (Ctrl-G)", command=self.copy_grats_message) auction_menu.add_command(label="Copy all concluded auctions (Ctrl-Shift-C)", command=self.copy_report) auction_menu.add_command(label="Copy concluded auctions from selection (Ctrl-C)", command=self.copy_report_from_selection) auction_menu.add_command(label="Charge DKP (Ctrl-B)", command=self.charge_dkp) menu_bar.add_cascade(label="Auctions", menu=auction_menu) dkp_menu = tkinter.Menu(menu_bar, tearoff=0) dkp_menu.add_command(label="Award DKP (Ctrl-W)", command=self.open_award_dkp_window) menu_bar.add_cascade(label="Awards", menu=dkp_menu) master.config(menu=menu_bar) columns = ['item', 'item_count', 'status', 'winner', 'price'] self.tree = ttk.Treeview(self.master, columns=columns) self.tree.heading('#0', text='time') self.tree.heading('#1', text='item') self.tree.heading('#2', text='count') self.tree.heading('#3', text='status') self.tree.heading('#4', text='winner') self.tree.heading('#5', text='price') self.tree.column('#0', stretch=tkinter.YES) self.tree.column('#1', stretch=tkinter.YES) self.tree.column('#2', stretch=tkinter.YES) self.tree.column('#3', stretch=tkinter.YES) self.tree.column('#4', stretch=tkinter.YES) self.tree.column('#5', stretch=tkinter.YES) self.tree.grid(row=1, columnspan=4, sticky='nsw') self.button = tkinter.Button(master, text="Load log file (Ctrl-F)", command=self.open_log_file) self.button.grid(row=2, column=0) self.button = tkinter.Button(master, text="Auction details (Ctrl-D)", command=self.open_details_window) self.button.grid(row=2, column=1) self.button = tkinter.Button(master, text="Award DKP (Ctrl-W)", command=self.open_award_dkp_window) self.button.grid(row=2, column=2) self.button = tkinter.Button(master, text="Close (Ctrl-Q)", command=self.confirm_quit) self.button.grid(row=2, column=3) self.status_window = tkinter.Text(master, height= 10, wrap="word") self.status_window.grid(row=4, columnspan=4, sticky='nsew') self.scroll = tkinter.Scrollbar(master, orient="vertical", command=self.status_window.yview) self.scroll.grid(row=4, column=4, sticky='nse') self.status_window.configure(state='normal') self.status_window.configure(yscrollcommand=self.scroll.set) self.status_window.see("end") self.status_window.configure(state='disabled') self.raid_dump_pane = ttk.Treeview(master, columns=['time'], selectmode='browse') self.raid_dump_pane.grid(row=3, column=0, columnspan=4, sticky='ew') self.raid_dump_pane.heading('#0', text='dump') self.raid_dump_pane.heading('#1', text='time') self.raid_dump_files = set() self.master.protocol("WM_DELETE_WINDOW", self.confirm_quit) self.thread = None # thread to asynchronously read data from the log file self.queue = queue.Queue() # holds actions from the log file that update the state of the world self.state = auction.AuctionState() def auction_context_menu_popup(event): self.tree.focus() auction_context_menu.post(event.x_root, event.y_root) self.tree.bind("<Control-Button-1>", auction_context_menu_popup) self.tree.bind("<Button-3>", auction_context_menu_popup) auction_context_menu = tkinter.Menu(self.frame, tearoff=0) auction_context_menu.add_command(label="Copy grats message (Ctrl-G)", command=self.copy_grats_message) auction_context_menu.add_command(label="Copy all concluded auctions (Ctrl-Shift-C)", command=self.copy_report) auction_context_menu.add_command(label="Copy concluded auctions from selection (Ctrl-C)", command=self.copy_report_from_selection) auction_context_menu.add_command(label="Charge DKP (Ctrl-B)", command=self.charge_dkp) def raid_dump_context_menu_popup(event): self.raid_dump_pane.focus() raid_dump_context_menu.post(event.x_root, event.y_root) self.raid_dump_pane.bind("<Control-Button-1>", raid_dump_context_menu_popup) self.raid_dump_pane.bind("<Button-3>", raid_dump_context_menu_popup) raid_dump_context_menu = tkinter.Menu(self.frame, tearoff=0) raid_dump_context_menu.add_command(label="Award DKP (Ctrl-W)", command=self.open_award_dkp_window) raid_dump_context_menu.add_command(label="Quick award DKP (Ctrl-Shift-W)", command=self.quick_award_dkp) # add keyboard shortcuts self.master.bind("<Control-f>", lambda _: self.open_log_file()) self.master.bind("<Control-c>", lambda _: self.copy_report_from_selection()) self.master.bind("<Control-C>", lambda _: self.copy_report()) self.master.bind("<Control-g>", lambda _: self.copy_grats_message()) self.master.bind("<Control-q>", lambda _: self.confirm_quit()) self.master.bind("<Control-d>", lambda _: self.open_details_window()) self.master.bind("<Control-b>", lambda _: self.charge_dkp()) self.master.bind("<Control-w>", lambda _: self.open_award_dkp_window()) self.master.bind("<Control-Shift-W>", lambda _: self.quick_award_dkp()) self.master.bind("<Control-t>", lambda _: self.ask_api_token()) self.master.bind("<Control-r>", lambda _: self.choose_raid_dump_dir()) self.master.after(1, self.tree.focus_force) self.my_name = '' self.config = config.load_saved_config() self.api_token_asked = False self.api_token = self.config.get('api_token', None) if 'log_file' in self.config and os.path.exists(self.config['log_file']): self.open_log_file(self.config['log_file']) if 'dump_path' in self.config and os.path.exists(self.config['dump_path']): self.show_raid_dumps() print("LOADED")
def clear_data(self): if self.thread is not None: self.thread.stop() self.tree.delete(*self.tree.get_children()) self.queue = queue.Queue() self.state = auction.AuctionState()
def test_whole_auction_case_3(): auc = auction.AuctionState("Tester") lines = [ "[Wed Jun 12 23:01:33 2019] You tell your raid, '!Bids open Amulet of Necropotence'", "[Wed Jun 12 23:01:34 2019] You tell your raid, '!Bids open Amulet of Awesome'", "[Wed Jun 12 23:07:49 2019] Foo tells you, 'Amulet of Necropotence 90'", "[Wed Jun 12 23:07:49 2019] Bar tells you, 'Amulet of Necropotence 112 ALT'", "[Wed Jun 12 23:07:49 2019] Baz tells you, 'Amulet of Necropotence 75'", "[Wed Jun 12 23:07:49 2019] Qux tells you, 'Amulet of Necropotence 40'", "[Wed Jun 12 23:07:50 2019] Qux tells you, 'Amulet of Awesome 40'", "[Wed Jun 12 23:07:49 2019] Quux tells you, 'Amulet of Necropotence 2'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Amulet of Awesome'", "[Wed Jun 12 23:07:49 2019] Thud tells you, 'Amulet of Necropotence 89'", "[Wed Jun 12 23:07:49 2019] Waldo tells you, 'Amulet of Necropotence 13'", "[Wed Jun 12 23:24:33 2019] You tell your raid, '!Bids closed Amulet of Necropotence'", ] body = { 'message': 'Cloak of Flames awarded to - Foo for 1', 'warnings': ['bad bid 1', 'bad bid 2'] } responses.add(responses.POST, 'http://padkp.net/api/resolve_auction/', json=body) for line in lines: actions = parse.handle_line( line, set(['Amulet of Necropotence', 'Amulet of Awesome'])) # we assume that every action was parsed properly. parse failures will cause a type error here if not isinstance(actions, list): actions = [actions] for action in actions: update = auc.update(action) data = json.loads(responses.calls[1].request.body) assert len(auc.concluded_auctions) == 2 assert len(update.update_rows) == 1 assert update.update_rows[ 0].winner == 'Cloak of Flames awarded to - Foo for 1' assert data['bids'] == [{ 'name': 'Foo', 'bid': 90, 'tag': '' }, { 'name': "Bar", 'bid': 112, 'tag': 'ALT' }, { 'name': 'Baz', 'bid': 75, 'tag': '' }, { 'name': 'Qux', 'bid': 40, 'tag': '' }, { 'name': 'Quux', 'bid': 2, 'tag': '' }, { 'name': 'Thud', 'bid': 89, 'tag': '' }, { 'name': 'Waldo', 'bid': 13, 'tag': '' }] assert update.update_rows[0].warnings == 'bad bid 1, bad bid 2'
def __init__(self, master): self.master = master self.frame = tkinter.Frame(self.master) menu_bar = tkinter.Menu(master) file_menu = tkinter.Menu(menu_bar, tearoff=0) file_menu.add_command(label="Open log file (Ctrl-F)", command=self.open_log_file) file_menu.add_command(label="Choose raid dump directory (Ctrl-R)", command=self.choose_raid_dump_dir) file_menu.add_command(label="Enter API token (Ctrl-T)", command=self.reset_api_token) file_menu.add_command(label="Close (Ctrl-Q)", command=self.confirm_quit) menu_bar.add_cascade(label="File", menu=file_menu) auction_menu = tkinter.Menu(menu_bar, tearoff=0) auction_menu.add_command(label="See auction details (Ctrl-D)", command=self.open_details_window) auction_menu.add_command(label="Copy grats message (Ctrl-G)", command=self.copy_grats_message) auction_menu.add_command( label="Copy all concluded auctions (Ctrl-Shift-C)", command=self.copy_report) auction_menu.add_command( label="Copy concluded auctions from selection (Ctrl-C)", command=self.copy_report_from_selection) menu_bar.add_cascade(label="Auctions", menu=auction_menu) dkp_menu = tkinter.Menu(menu_bar, tearoff=0) dkp_menu.add_command(label="Award DKP (Ctrl-W)", command=self.open_award_dkp_window) menu_bar.add_cascade(label="Awards", menu=dkp_menu) waitlist_menu = tkinter.Menu(menu_bar, tearoff=0) waitlist_menu.add_command(label="View waitlist", command=self.open_waitlist_window) menu_bar.add_cascade(label="Waitlist", menu=waitlist_menu) master.config(menu=menu_bar) # Bug in this version of python/tkinkter with background colors in tree views. # https://stackoverflow.com/questions/56331001/python-tkinter-treeview-colors-are-not-updating def fixed_map(option): # Returns the style map for 'option' with any styles starting with # ("!disabled", "!selected", ...) filtered out # style.map() returns an empty list for missing options, so this should # be future-safe return [ elm for elm in style.map("Treeview", query_opt=option) if elm[:2] != ("!disabled", "!selected") ] style = ttk.Style() style.map("Treeview", foreground=fixed_map("foreground"), background=fixed_map("background")) columns = ['item', 'item_count', 'status', 'results', 'warnings'] self.tree = ttk.Treeview(self.master, columns=columns) self.tree.heading('#0', text='time') self.tree.heading('#1', text='item') self.tree.heading('#2', text='count') self.tree.heading('#3', text='status') self.tree.heading('#4', text='results') self.tree.heading('#5', text='warnings') self.tree.column('#0', stretch=tkinter.YES, minwidth=175) self.tree.column('#1', stretch=tkinter.YES, minwidth=200) self.tree.column('#2', stretch=tkinter.YES, minwidth=40) self.tree.column('#3', stretch=tkinter.YES, minwidth=75, width=75) self.tree.column('#4', stretch=tkinter.YES, minwidth=200) self.tree.column('#5', stretch=tkinter.YES, minwidth=300) self.tree.grid(row=1, columnspan=4, sticky='nsw') self.tree.tag_configure("charged", background='green') self.tree.tag_configure("warning", background='orange') self.tree.tag_configure("error", background='red') self.tree.tag_configure("corrected", background='yellow') self.button = tkinter.Button(master, text="Load log file (Ctrl-F)", command=self.open_log_file) self.button.grid(row=2, column=0) self.button = tkinter.Button(master, text="Auction details (Ctrl-D)", command=self.open_details_window) self.button.grid(row=2, column=1) self.button = tkinter.Button(master, text="Award DKP (Ctrl-W)", command=self.open_award_dkp_window) self.button.grid(row=2, column=2) self.button = tkinter.Button(master, text="Close (Ctrl-Q)", command=self.confirm_quit) self.button.grid(row=2, column=3) self.status_window = tkinter.Text(master, height=10, wrap="word") self.status_window.grid(row=4, columnspan=4, sticky='nsew') self.scroll = tkinter.Scrollbar(master, orient="vertical", command=self.status_window.yview) self.scroll.grid(row=4, column=4, sticky='nse') self.status_window.configure(state='normal') self.status_window.configure(yscrollcommand=self.scroll.set) self.status_window.see("end") self.status_window.configure(state='disabled') self.raid_dump_pane = ttk.Treeview(master, columns=['time'], selectmode='browse') self.raid_dump_pane.grid(row=3, column=0, columnspan=4, sticky='ew') self.raid_dump_pane.heading('#0', text='dump') self.raid_dump_pane.heading('#1', text='time') self.raid_dump_files = set() self.master.protocol("WM_DELETE_WINDOW", self.confirm_quit) self.thread = None # thread to asynchronously read data from the log file # holds actions from the log file that update the state of the world self.queue = queue.Queue() self.state = auction.AuctionState() def auction_context_menu_popup(event): self.tree.focus() auction_context_menu.post(event.x_root, event.y_root) self.tree.bind("<Control-Button-1>", auction_context_menu_popup) self.tree.bind("<Button-3>", auction_context_menu_popup) auction_context_menu = tkinter.Menu(self.frame, tearoff=0) auction_context_menu.add_command(label="Copy grats message (Ctrl-G)", command=self.copy_grats_message) auction_context_menu.add_command( label="Copy all concluded auctions (Ctrl-Shift-C)", command=self.copy_report) auction_context_menu.add_command( label="Copy concluded auctions from selection (Ctrl-C)", command=self.copy_report_from_selection) def raid_dump_context_menu_popup(event): self.raid_dump_pane.focus() raid_dump_context_menu.post(event.x_root, event.y_root) self.raid_dump_pane.bind("<Control-Button-1>", raid_dump_context_menu_popup) self.raid_dump_pane.bind("<Button-3>", raid_dump_context_menu_popup) raid_dump_context_menu = tkinter.Menu(self.frame, tearoff=0) raid_dump_context_menu.add_command(label="Award DKP (Ctrl-W)", command=self.open_award_dkp_window) raid_dump_context_menu.add_command( label="Quick award DKP (Ctrl-Shift-W)", command=self.quick_award_dkp) raid_dump_context_menu.add_command( label="Award CASUAL RAID DKP (Ctrl-Shift-M)", command=self.casual_award_dkp) # add keyboard shortcuts self.master.bind("<Control-f>", lambda _: self.open_log_file()) self.master.bind("<Control-c>", lambda _: self.copy_report_from_selection()) self.master.bind("<Control-C>", lambda _: self.copy_report()) self.master.bind("<Control-g>", lambda _: self.copy_grats_message()) self.master.bind("<Control-q>", lambda _: self.confirm_quit()) self.master.bind("<Control-d>", lambda _: self.open_details_window()) self.master.bind("<Control-w>", lambda _: self.open_award_dkp_window()) self.master.bind("<Control-Shift-W>", lambda _: self.quick_award_dkp()) self.master.bind("<Control-Shift-M>", lambda _: self.casual_award_dkp()) self.master.bind("<Control-t>", lambda _: self.reset_api_token()) self.master.bind("<Control-r>", lambda _: self.choose_raid_dump_dir()) self.master.after(1, self.tree.focus_force) self.my_name = '' self.config = config.load_saved_config() # trigger the various events that happen "on update" self.force_token = False self.ask_api_token() if 'log_file' in self.config and os.path.exists( self.config['log_file']): self.open_log_file(self.config['log_file']) if 'dump_path' in self.config and os.path.exists( self.config['dump_path']): self.show_raid_dumps() print("LOADED")