-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathGUI.py
266 lines (213 loc) · 10.2 KB
/
GUI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
from MarsCore import MarsCore
import tkinter as tk
from tkinter import ttk
from functools import partial
from time import time
from math import ceil
from PIL import ImageTk, Image
import logging
INDEX = 0
RATING = 1
JSON_PATH = "album_info/album_info.json"
album_covers = "album_info/album_covers/"
logging.basicConfig(level=logging.DEBUG)
class LoopState:
"""
Objects of this class contains and maintains information about what and how to display.
It provides functionalities for dealing with user input, i.e. searchbar and buttons
"""
def __init__(self, chunk_length, mars_object):
"""
:param chunk_length: number of albums displayed on one page
:param mars_object: this is the core of our system. It computes grading for every album
and provides information about albums
searched_phrase - phrase searched by the user
display_list - all the entries that satisfy the search_phrase
"""
self.mars_object = mars_object
self.chunk_length = chunk_length
self.curr_chunk_index = 0
self.searched_phrase = ""
self.display_list = mars_object.result_list
# methods for rating unranked and deleting ranked albums
def rate_album(self, index, rate):
"""Rates the album and modifies data"""
log_time_debug_message(
lambda: self.mars_object.choose(index, rate),
'Time of giving grade (adding similarities, sorting, etc.)')
self.__show_modifications()
def del_album(self, index):
"""Deletes the given album and modifies data"""
log_time_debug_message(
lambda: self.mars_object.unchoose(index),
'Time of deleting grade')
self.__show_modifications()
def __show_modifications(self):
"""Modifies data"""
log_time_debug_message(
self.set_display_list,
"Time of resorting the list")
log_time_debug_message(
lambda: update(self),
'Time of updating the root')
# left and right buttons
def increment_chunk_index(self, event=None):
"""Turns to the next page (the next chunk of albums is displayed)"""
if self.can_click_right() == "normal":
self.curr_chunk_index += 1
update(self)
def decrement_chunk_index(self, event=None):
"""Turns to the previous page (the previous chunk of albums is displayed)"""
if self.can_click_left() == "normal":
self.curr_chunk_index -= 1
update(self)
def can_click_left(self):
"""Checks if the left button can be clicked"""
return boolean_to_button_constants(self.curr_chunk_index > 0)
def can_click_right(self):
"""Checks if the right button can be clicked"""
return boolean_to_button_constants(
(self.curr_chunk_index + 1) * self.chunk_length < len(self.display_list))
# functions for searchbar
def set_display_list(self):
"""Filters the display_list by the searched phrase set by the user"""
if not self.searched_phrase:
self.display_list = self.mars_object.result_list
else:
titles = list(map(str.lower, self.mars_object.album_titles))
artists = list(map(str.lower, self.mars_object.album_artists))
result_list = self.mars_object.result_list
self.display_list = list(filter(
lambda elem: self.searched_phrase in titles[elem[INDEX]] or
self.searched_phrase in artists[elem[INDEX]],
result_list))
def set_searched_phrase(self, new_phrase):
self.searched_phrase = new_phrase.lower()
self.set_display_list()
self.curr_chunk_index = 0
update(self)
def clear_search(self, search_bar):
"""Clears the searchbar and shows all unranked albums"""
search_bar.delete(0, len(search_bar.get()))
self.set_searched_phrase("") # setting search phrase to BLANK resets the search
# getters
def get_truncated_list(self):
"""Returns albums from the display list that are within the current chunk"""
display_list = self.display_list
return display_list[self.chunk_length * self.curr_chunk_index:
self.chunk_length * (self.curr_chunk_index + 1)]
def get_no_displayed_albums(self):
return len(self.display_list)
def get_no_total_albums(self):
return len(self.mars_object.result_list)
def get_no_chunks(self):
return ceil(self.get_no_displayed_albums() / self.chunk_length)
def log_time_debug_message(func, message):
t1 = time()
func()
t2 = time()
logging.debug(message + f": {round(t2 - t1, 5)} [s]")
def boolean_to_button_constants(expression):
return tk.NORMAL if expression else tk.DISABLED
def update(state):
mars_core = state.mars_object
for ele in root.winfo_children():
ele.destroy()
# creating left slidebar for albums sorted (to rank)
not_chosen = tk.LabelFrame(root, border=2)
not_chosen.pack(side='left', fill='both', expand=1)
canvas_left = tk.Canvas(not_chosen)
canvas_left.pack(side='left', fill='both', expand=1)
scrollbar_left = ttk.Scrollbar(not_chosen, orient='vertical', command=canvas_left.yview)
scrollbar_left.pack(side='right', fill='y')
canvas_left.configure(yscrollcommand=scrollbar_left.set)
canvas_left.bind('<Configure>', lambda e: canvas_left.configure(scrollregion=canvas_left.bbox('all')))
display_window_left = tk.Frame(canvas_left)
canvas_left.create_window((0, 0), window=display_window_left, anchor='nw')
# buttons for traversing the list
left_button = tk.Button(display_window_left, text="left",
command=state.decrement_chunk_index,
state=state.can_click_left(),
padx=7, pady=5)
chunk_num_label = tk.Label(display_window_left,
text=f"{state.curr_chunk_index + 1} out of {state.get_no_chunks()}")
right_button = tk.Button(display_window_left, text="right",
command=state.increment_chunk_index,
state=state.can_click_right(),
padx=7, pady=5)
left_button.grid(row=0, column=0)
chunk_num_label.grid(row=0, column=1)
right_button.grid(row=0, column=2, columnspan=5)
root.bind("<Left>", state.decrement_chunk_index)
root.bind("<Right>", state.increment_chunk_index)
# searchbar
search_label = tk.LabelFrame(display_window_left, border=0)
search_bar = tk.Entry(search_label,
width=30)
search_button = tk.Button(search_label,
command=lambda: state.set_searched_phrase(search_bar.get()),
text="Search", padx=10, pady=5)
clear_button = tk.Button(search_label,
command=lambda: state.clear_search(search_bar),
text="Clear", padx=10, pady=5)
search_bar.grid(row=0, column=0, padx=5, ipady=5)
search_button.grid(row=0, column=1, padx=5)
clear_button.grid(row=0, column=2, padx=5)
search_label.grid(row=1, column=1, pady=10, columnspan=3)
# filling slidebar with albums
truncated_list = state.get_truncated_list()
global cover
cover = [None for _ in range(len(truncated_list))]
for index, entry in enumerate(truncated_list):
try:
cover[index] = ImageTk.PhotoImage(Image.open(
album_covers + "{} - {}.png".format(state.mars_object.album_artists[entry[INDEX]],
state.mars_object.album_titles[entry[INDEX]])
.replace('/', ' ').replace('?', ' ').replace(':', ' ')))
except FileNotFoundError:
cover[index] = ImageTk.PhotoImage(Image.open(album_covers + "no_image.png"))
cover_label = tk.Label(display_window_left, image=cover[index])
cover_label.grid(row=index + 2, column=0)
label = tk.Label(display_window_left,
text="{}\n{}".format(mars_core.album_titles[entry[INDEX]],
mars_core.album_artists[entry[INDEX]]),
width=50)
label.grid(row=index + 2, column=1)
# adding buttons to rank
for rate in range(-2, 3):
rate_to_display = rate + 3
new_button = tk.Button(display_window_left, text=rate_to_display,
padx=20, pady=20,
command=partial(state.rate_album, entry[INDEX], rate))
new_button.grid(row=index + 2, column=rate_to_display + 1)
# creating right slidebar for albums with grades
chosen = tk.LabelFrame(root)
chosen.pack(side='right', fill='both')
canvas_right = tk.Canvas(chosen)
canvas_right.pack(side='left', fill='both', expand=1)
scrollbar_right = ttk.Scrollbar(chosen, orient='vertical', command=canvas_right.yview)
scrollbar_right.pack(side='right', fill='y')
canvas_right.configure(yscrollcommand=scrollbar_right.set)
canvas_right.bind('<Configure>',
lambda e: canvas_right.configure(scrollregion=canvas_right.bbox('all')))
display_window_right = tk.Frame(canvas_right)
canvas_right.create_window((0, 0), window=display_window_right, anchor='nw')
# filling slidebar with info
for i, (index, grade) in enumerate(mars_core.already_chosen.items()):
display_grade = grade + 3
label = tk.Label(display_window_right, width=46, height=5,
text="{}\n{}\nRating: {}".format(mars_core.album_titles[index],
mars_core.album_artists[index],
display_grade))
remove_button = tk.Button(display_window_right, text="remove",
command=partial(state.del_album, index))
label.grid(row=i, column=0)
remove_button.grid(row=i, column=1)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("1200x700")
root.title("MARS")
root.iconbitmap('album_info/MARS_icon.ico')
state = LoopState(mars_object=MarsCore(json_path=JSON_PATH), chunk_length=30)
update(state)
root.mainloop()