commit
4a1b8a4aac
|
@ -0,0 +1,228 @@
|
|||
import os
|
||||
import PySimpleGUI as g
|
||||
import mido
|
||||
import time
|
||||
|
||||
PLAYER_COMMAND_NONE = 0
|
||||
PLAYER_COMMAND_EXIT = 1
|
||||
PLAYER_COMMAND_PAUSE = 2
|
||||
PLAYER_COMMAND_NEXT = 3
|
||||
PLAYER_COMMAND_RESTART_SONG = 4
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# PlayerGUI CLASS #
|
||||
# ---------------------------------------------------------------------- #
|
||||
class PlayerGUI():
|
||||
'''
|
||||
Class implementing GUI for both initial screen but the player itself
|
||||
'''
|
||||
|
||||
def __init__(self):
|
||||
self.Form = None
|
||||
self.TextElem = None
|
||||
self.PortList = mido.get_output_names() # use to get the list of midi ports
|
||||
self.PortList = self.PortList[::-1] # reverse the list so the last one is first
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# PlayerChooseSongGUI #
|
||||
# Show a GUI get to the file to playback #
|
||||
# ---------------------------------------------------------------------- #
|
||||
def PlayerChooseSongGUI(self):
|
||||
|
||||
# ---------------------- DEFINION OF CHOOSE WHAT TO PLAY GUI ----------------------------
|
||||
with g.FlexForm('MIDI File Player', auto_size_text=False,
|
||||
default_element_size=(30, 1),
|
||||
font=("Helvetica", 12)) as form:
|
||||
layout = [[g.Text('MIDI File Player', font=("Helvetica", 15), size=(20, 1), text_color='green')],
|
||||
[g.Text('File Selection', font=("Helvetica", 15), size=(20, 1))],
|
||||
[g.Text('Single File Playback', justification='right'), g.InputText(size=(65, 1), key='midifile'), g.FileBrowse(size=(10, 1), file_types=(("MIDI files", "*.mid"),))],
|
||||
[g.Text('Or Batch Play From This Folder', auto_size_text=False, justification='right'), g.InputText(size=(65, 1), key='folder'), g.FolderBrowse(size=(10, 1))],
|
||||
[g.Text('_' * 250, auto_size_text=False, size=(100, 1))],
|
||||
[g.Text('Choose MIDI Output Device', size=(22, 1)),
|
||||
g.Listbox(values=self.PortList, size=(30, len(self.PortList) + 1), key='device')],
|
||||
[g.Text('_' * 250, auto_size_text=False, size=(100, 1))],
|
||||
[g.SimpleButton('PLAY!', size=(10, 2), button_color=('red', 'white'), font=("Helvetica", 15), bind_return_key=True), g.Text(' ' * 2, size=(4, 1)), g.Cancel(size=(8, 2), font=("Helvetica", 15))]]
|
||||
|
||||
|
||||
self.Form = form
|
||||
return form.LayoutAndRead(layout)
|
||||
|
||||
|
||||
def PlayerPlaybackGUIStart(self, NumFiles=1):
|
||||
# ------- Make a new FlexForm ------- #
|
||||
|
||||
image_pause = './ButtonGraphics/Pause.png'
|
||||
image_restart = './ButtonGraphics/Restart.png'
|
||||
image_next = './ButtonGraphics/Next.png'
|
||||
image_exit = './ButtonGraphics/Exit.png'
|
||||
|
||||
self.TextElem = g.T('Song loading....', size=(85, 5 + NumFiles), font=("Helvetica", 14), auto_size_text=False)
|
||||
form = g.FlexForm('MIDI File Player', default_element_size=(30, 1),font=("Helvetica", 25))
|
||||
layout = [
|
||||
[g.T('MIDI File Player', size=(30, 1), font=("Helvetica", 25))],
|
||||
[self.TextElem],
|
||||
[g.ReadFormButton('PAUSE', button_color=g.TRANSPARENT_BUTTON,
|
||||
image_filename=image_pause, image_size=(50,50),image_subsample=2, border_width=0,
|
||||
font=("Helvetica", 15), size=(10, 2)), g.T(' ' * 3),
|
||||
g.ReadFormButton('NEXT', button_color=g.TRANSPARENT_BUTTON,
|
||||
image_filename=image_next, image_size=(50,50),image_subsample=2, border_width=0,
|
||||
size=(10, 2), font=("Helvetica", 15)), g.T(' ' * 3),
|
||||
g.ReadFormButton('Restart Song', button_color=g.TRANSPARENT_BUTTON,
|
||||
image_filename=image_restart, image_size=(50,50), image_subsample=2,border_width=0,
|
||||
size=(10, 2), font=("Helvetica", 15)), g.T(' ' * 3),
|
||||
g.T(' '*2), g.SimpleButton('EXIT', button_color=g.TRANSPARENT_BUTTON,
|
||||
image_filename=image_exit, image_size=(50,50), image_subsample=2,border_width=0,
|
||||
size=(10, 2), font=("Helvetica", 15))]
|
||||
]
|
||||
|
||||
form.LayoutAndRead(layout, non_blocking=True)
|
||||
self.Form = form
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------- #
|
||||
# PlayerPlaybackGUIUpdate #
|
||||
# Refresh the GUI for the main playback interface (must call periodically #
|
||||
# ------------------------------------------------------------------------- #
|
||||
def PlayerPlaybackGUIUpdate(self, DisplayString):
|
||||
form = self.Form
|
||||
if 'form' not in locals() or form is None: # if the form has been destoyed don't mess with it
|
||||
return PLAYER_COMMAND_EXIT
|
||||
self.TextElem.Update(DisplayString)
|
||||
button, (values) = form.ReadNonBlocking()
|
||||
if values is None:
|
||||
return PLAYER_COMMAND_EXIT
|
||||
if button == 'PAUSE':
|
||||
return PLAYER_COMMAND_PAUSE
|
||||
elif button == 'EXIT':
|
||||
return PLAYER_COMMAND_EXIT
|
||||
elif button == 'NEXT':
|
||||
return PLAYER_COMMAND_NEXT
|
||||
elif button == 'Restart Song':
|
||||
return PLAYER_COMMAND_RESTART_SONG
|
||||
return PLAYER_COMMAND_NONE
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# MAIN - our main program... this is it #
|
||||
# Runs the GUI to get the file / path to play #
|
||||
# Decodes the MIDI-Video into a MID file #
|
||||
# Plays the decoded MIDI file #
|
||||
# ---------------------------------------------------------------------- #
|
||||
def main():
|
||||
def GetCurrentTime():
|
||||
'''
|
||||
Get the current system time in milliseconds
|
||||
:return: milliseconds
|
||||
'''
|
||||
return int(round(time.time() * 1000))
|
||||
|
||||
g.SetOptions(border_width=1, element_padding=(4, 6), font=("Helvetica", 10), button_color=('white', g.BLUES[0]),
|
||||
progress_meter_border_depth=1, slider_border_width=1)
|
||||
pback = PlayerGUI()
|
||||
|
||||
button, values = pback.PlayerChooseSongGUI()
|
||||
if button != 'PLAY!':
|
||||
g.MsgBoxCancel('Cancelled...\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
|
||||
exit(69)
|
||||
if values['device'] is not None:
|
||||
midi_port = values['device'][0]
|
||||
else:
|
||||
g.MsgBoxCancel('No devices found\nAutoclose in 2 sec...', auto_close=True, auto_close_duration=2)
|
||||
|
||||
batch_folder = values['folder']
|
||||
midi_filename = values['midifile']
|
||||
# ------ Build list of files to play --------------------------------------------------------- #
|
||||
if batch_folder:
|
||||
filelist = os.listdir(batch_folder)
|
||||
filelist = [batch_folder+'/'+f for f in filelist if f.endswith(('.mid', '.MID'))]
|
||||
filetitles = [os.path.basename(f) for f in filelist]
|
||||
elif midi_filename: # an individual filename
|
||||
filelist = [midi_filename,]
|
||||
filetitles = [os.path.basename(midi_filename),]
|
||||
else:
|
||||
g.MsgBoxError('*** Error - No MIDI files specified ***')
|
||||
exit(666)
|
||||
|
||||
# ------ LOOP THROUGH MULTIPLE FILES --------------------------------------------------------- #
|
||||
pback.PlayerPlaybackGUIStart(NumFiles=len(filelist) if len(filelist) <=10 else 10)
|
||||
port = None
|
||||
# Loop through the files in the filelist
|
||||
for now_playing_number, current_midi_filename in enumerate(filelist):
|
||||
display_string = 'Playing Local File...\n{} of {}\n{}'.format(now_playing_number+1, len(filelist), current_midi_filename)
|
||||
midi_title = filetitles[now_playing_number]
|
||||
# --------------------------------- REFRESH THE GUI ----------------------------------------- #
|
||||
pback.PlayerPlaybackGUIUpdate(display_string)
|
||||
|
||||
# ---===--- Output Filename is .MID --- #
|
||||
midi_filename = current_midi_filename
|
||||
|
||||
# --------------------------------- MIDI - STARTS HERE ----------------------------------------- #
|
||||
if not port: # if the midi output port not opened yet, then open it
|
||||
port = mido.open_output(midi_port if midi_port else None)
|
||||
|
||||
try:
|
||||
mid = mido.MidiFile(filename=midi_filename)
|
||||
except:
|
||||
print('****** Exception trying to play MidiFile filename = {}***************'.format(midi_filename))
|
||||
g.MsgBoxError('Exception trying to play MIDI file:', midi_filename, 'Skipping file')
|
||||
continue
|
||||
|
||||
# Build list of data contained in MIDI File using only track 0
|
||||
midi_length_in_seconds = mid.length
|
||||
display_file_list = '>> ' + '\n'.join([f for i, f in enumerate(filelist[now_playing_number:]) if i < 10])
|
||||
paused = cancelled = next_file = False
|
||||
######################### Loop through MIDI Messages ###########################
|
||||
while(True):
|
||||
start_playback_time = GetCurrentTime()
|
||||
port.reset()
|
||||
|
||||
for midi_msg_number, msg in enumerate(mid.play()):
|
||||
#################### GUI - read values ##################
|
||||
if not midi_msg_number % 4: # update the GUI every 4 MIDI messages
|
||||
t = (GetCurrentTime() - start_playback_time)//1000
|
||||
display_midi_len = '{:02d}:{:02d}'.format(*divmod(int(midi_length_in_seconds),60))
|
||||
display_string = 'Now Playing {} of {}\n{}\n {:02d}:{:02d} of {}\nPlaylist:'.\
|
||||
format(now_playing_number+1, len(filelist), midi_title, *divmod(t, 60), display_midi_len)
|
||||
# display list of next 10 files to be played.
|
||||
rc = pback.PlayerPlaybackGUIUpdate(display_string + '\n' + display_file_list)
|
||||
else: # fake rest of code as if GUI did nothing
|
||||
rc = PLAYER_COMMAND_NONE
|
||||
if paused:
|
||||
rc = PLAYER_COMMAND_NONE
|
||||
while rc == PLAYER_COMMAND_NONE: # TIGHT-ASS loop waiting on a GUI command
|
||||
rc = pback.PlayerPlaybackGUIUpdate(display_string)
|
||||
time.sleep(.25)
|
||||
|
||||
####################################### MIDI send data ##################################
|
||||
port.send(msg)
|
||||
|
||||
# ------- Execute GUI Commands after sending MIDI data ------- #
|
||||
if rc == PLAYER_COMMAND_EXIT:
|
||||
cancelled = True
|
||||
break
|
||||
elif rc == PLAYER_COMMAND_PAUSE:
|
||||
paused = not paused
|
||||
port.reset()
|
||||
elif rc == PLAYER_COMMAND_NEXT:
|
||||
next_file = True
|
||||
break
|
||||
elif rc == PLAYER_COMMAND_RESTART_SONG:
|
||||
break
|
||||
|
||||
if cancelled or next_file:
|
||||
break
|
||||
#------- DONE playing the song ------- #
|
||||
port.reset() # reset the midi port when done with the song
|
||||
|
||||
if cancelled:
|
||||
break
|
||||
exit(69)
|
||||
|
||||
# ---------------------------------------------------------------------- #
|
||||
# LAUNCH POINT -- program starts and ends here #
|
||||
# ---------------------------------------------------------------------- #
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
exit(69)
|
|
@ -290,7 +290,7 @@ class Listbox(Element):
|
|||
:param auto_size_text: True if should shrink field to fit the default text
|
||||
:param background_color: Color for Element. Text or RGB Hex '''
|
||||
self.Values = values
|
||||
self.TKListBox = None
|
||||
self.TKListbox = None
|
||||
if select_mode == LISTBOX_SELECT_MODE_BROWSE:
|
||||
self.SelectMode = SELECT_MODE_BROWSE
|
||||
elif select_mode == LISTBOX_SELECT_MODE_EXTENDED:
|
||||
|
@ -305,6 +305,12 @@ class Listbox(Element):
|
|||
fg = text_color if text_color is not None else DEFAULT_INPUT_TEXT_COLOR
|
||||
super().__init__(ELEM_TYPE_INPUT_LISTBOX, scale=scale, size=size, auto_size_text=auto_size_text, font=font, background_color=bg, text_color=fg, key=key)
|
||||
|
||||
def Update(self, values):
|
||||
self.TKListbox.delete(0, 'end')
|
||||
for item in values:
|
||||
self.TKListbox.insert(tk.END, item)
|
||||
self.TKListbox.selection_set(0, 0)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.TKListBox.__del__()
|
||||
|
@ -476,10 +482,15 @@ class Text(Element):
|
|||
super().__init__(ELEM_TYPE_TEXT, scale, size, auto_size_text, background_color=bg, font=font if font else DEFAULT_FONT, text_color=self.TextColor)
|
||||
return
|
||||
|
||||
def Update(self, NewValue):
|
||||
self.DisplayText=NewValue
|
||||
stringvar = self.TKStringVar
|
||||
stringvar.set(NewValue)
|
||||
def Update(self, new_value = None, background_color=None, text_color=None):
|
||||
if new_value is not None:
|
||||
self.DisplayText=new_value
|
||||
stringvar = self.TKStringVar
|
||||
stringvar.set(new_value)
|
||||
if background_color is not None:
|
||||
self.TKText.configure(background=background_color)
|
||||
if text_color is not None:
|
||||
self.TKText.configure(fg=text_color)
|
||||
|
||||
def __del__(self):
|
||||
super().__del__()
|
||||
|
@ -704,6 +715,14 @@ class Button(Element):
|
|||
self.ParentForm.TKroot.quit() # kick the users out of the mainloop
|
||||
return
|
||||
|
||||
def Update(self, new_text, button_color=(None, None)):
|
||||
try:
|
||||
self.TKButton.configure(text=new_text)
|
||||
if button_color != (None, None):
|
||||
self.TKButton.config(foreground=button_color[0], background=button_color[1])
|
||||
except:
|
||||
return
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.TKButton.__del__()
|
||||
|
@ -821,6 +840,9 @@ class Slider(Element):
|
|||
super().__init__(ELEM_TYPE_INPUT_SLIDER, scale=scale, size=size, font=font, background_color=background_color, text_color=text_color, key=key)
|
||||
return
|
||||
|
||||
def Update(self, value):
|
||||
self.TKIntVar.set(value)
|
||||
|
||||
def __del__(self):
|
||||
super().__del__()
|
||||
|
||||
|
@ -1026,8 +1048,6 @@ class FlexForm:
|
|||
if self.RootNeedsDestroying:
|
||||
self.TKroot.destroy()
|
||||
_my_windows.Decrement()
|
||||
# if self.ReturnValues[0] is not None: # keyboard events build their own return values
|
||||
# return self.ReturnValues
|
||||
if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None:
|
||||
return BuildResults(self, False, self)
|
||||
else:
|
||||
|
@ -1046,6 +1066,7 @@ class FlexForm:
|
|||
except:
|
||||
self.TKrootDestroyed = True
|
||||
_my_windows.Decrement()
|
||||
# return None, None
|
||||
return BuildResults(self, False, self)
|
||||
|
||||
def GetScreenDimensions(self):
|
||||
|
@ -1452,7 +1473,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form):
|
|||
stringvar = tk.StringVar()
|
||||
element.TKStringVar = stringvar
|
||||
stringvar.set(display_text)
|
||||
if auto_size_text:
|
||||
if element.AutoSizeText:
|
||||
width = 0
|
||||
if element.Justification is not None:
|
||||
justification = element.Justification
|
||||
|
@ -1463,16 +1484,18 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form):
|
|||
justify = tk.LEFT if justification == 'left' else tk.CENTER if justification == 'center' else tk.RIGHT
|
||||
anchor = tk.NW if justification == 'left' else tk.N if justification == 'center' else tk.NE
|
||||
tktext_label = tk.Label(tk_row_frame, textvariable=stringvar, width=width, height=height, justify=justify, bd=border_depth)
|
||||
# tktext_label = tk.Label(tk_row_frame,anchor=tk.NW, text=display_text, width=width, height=height, justify=tk.LEFT, bd=border_depth)
|
||||
# Set wrap-length for text (in PIXELS) == PAIN IN THE ASS
|
||||
wraplen = tktext_label.winfo_reqwidth() # width of widget in Pixels
|
||||
tktext_label.configure(anchor=anchor, font=font, wraplen=wraplen+40) # set wrap to width of widget
|
||||
wraplen = tktext_label.winfo_reqwidth()+40 # width of widget in Pixels
|
||||
if not auto_size_text:
|
||||
wraplen = 0
|
||||
# print("wraplen, width, height", wraplen, width, height)
|
||||
tktext_label.configure(anchor=anchor, font=font, wraplen=wraplen) # set wrap to width of widget
|
||||
if element.BackgroundColor is not None:
|
||||
tktext_label.configure(background=element.BackgroundColor)
|
||||
if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None:
|
||||
tktext_label.configure(fg=element.TextColor)
|
||||
tktext_label.pack(side=tk.LEFT)
|
||||
# print(f'Text element placed w = {width}, h = {height}, wrap = {wraplen}')
|
||||
element.TKText = tktext_label
|
||||
# ------------------------- BUTTON element ------------------------- #
|
||||
elif element_type == ELEM_TYPE_BUTTON:
|
||||
element.Location = (row_num, col_num)
|
||||
|
@ -1933,6 +1956,7 @@ def MsgBox(*args, button_color=None, button_type=MSG_BOX_OK, auto_close=False, a
|
|||
max_line_total = max(max_line_total, width_used)
|
||||
# height = _GetNumLinesNeeded(message, width_used)
|
||||
height = message_wrapped_lines
|
||||
# print('Msgbox width, height', width_used, height)
|
||||
form.AddRow(Text(message_wrapped, auto_size_text=True, size=(width_used, height)))
|
||||
total_lines += height
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ Browse for a filename that is populated into the input field.
|
|||
|
||||
import PySimpleGUI as sg
|
||||
|
||||
with sg.FlexForm('SHA-1 & 256 Hash', auto_size_text=True) as form:
|
||||
with sg.FlexForm('SHA-1 & 256 Hash') as form:
|
||||
form_rows = [[sg.Text('SHA-1 and SHA-256 Hashes for the file')],
|
||||
[sg.InputText(), sg.FileBrowse()],
|
||||
[sg.Submit(), sg.Cancel()]]
|
||||
|
@ -101,7 +101,7 @@ Example of nearly all of the widgets in a single form. Uses a customized color
|
|||
progress_meter_border_depth=0,
|
||||
scrollbar_color='#F7F3EC')
|
||||
|
||||
with sg.FlexForm('Everything bagel', auto_size_text=True, default_element_size=(40, 1)) as form:
|
||||
with sg.FlexForm('Everything bagel', default_element_size=(40, 1)) as form:
|
||||
layout = [
|
||||
[sg.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25))],
|
||||
[sg.Text('Here is some text.... and a place to enter text')],
|
||||
|
@ -141,7 +141,7 @@ Example of nearly all of the widgets in a single form. Uses a customized color
|
|||
progress_meter_border_depth=0,
|
||||
scrollbar_color='#F7F3EC')
|
||||
|
||||
form = sg.FlexForm('Everything bagel', auto_size_text=True, default_element_size=(40, 1))
|
||||
form = sg.FlexForm('Everything bagel', default_element_size=(40, 1))
|
||||
layout = [
|
||||
[sg.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25))],
|
||||
[sg.Text('Here is some text.... and a place to enter text')],
|
||||
|
@ -175,7 +175,7 @@ An async form that has a button read loop. A Text Element is updated periodical
|
|||
import PySimpleGUI as sg
|
||||
import time
|
||||
|
||||
form = sg.FlexForm('Running Timer', auto_size_text=True)
|
||||
form = sg.FlexForm('Running Timer')
|
||||
# create a text element that will be updated periodically
|
||||
text_element = sg.Text('', size=(10, 2), font=('Helvetica', 20), justification='center')
|
||||
|
||||
|
@ -210,7 +210,7 @@ Like the previous recipe, this form is an async form. The difference is that th
|
|||
import PySimpleGUI as sg
|
||||
import time
|
||||
|
||||
with sg.FlexForm('Running Timer', auto_size_text=True) as form:
|
||||
with sg.FlexForm('Running Timer') as form:
|
||||
text_element = sg.Text('', size=(10, 2), font=('Helvetica', 20), text_color='red', justification='center')
|
||||
layout = [[sg.Text('Non blocking GUI with updates', justification='center')],
|
||||
[text_element],
|
||||
|
@ -249,7 +249,7 @@ The architecture of some programs works better with button callbacks instead of
|
|||
# Create a standard form
|
||||
form = sg.FlexForm('Button callback example')
|
||||
# Layout the design of the GUI
|
||||
layout = [[sg.Text('Please click a button', auto_size_text=True)],
|
||||
layout = [[sg.Text('Please click a button')],
|
||||
[sg.ReadFormButton('1'), sg.ReadFormButton('2'), sg.Quit()]]
|
||||
# Show the form to the user
|
||||
form.Layout(layout)
|
||||
|
@ -593,3 +593,44 @@ To make it easier to see the Column in the window, the Column background has bee
|
|||
button, values = sg.FlexForm('Compact 1-line form with column').LayoutAndRead(layout)
|
||||
|
||||
sg.MsgBox(button, values, line_width=200)
|
||||
|
||||
|
||||
## Persistent Form With Text Element Updates
|
||||
|
||||
This simple program keep a form open, taking input values until the user terminates the program using the "X" button.
|
||||
|
||||
![math game](https://user-images.githubusercontent.com/13696193/44537842-c9444080-a6cd-11e8-94bc-6cdf1b765dd8.jpg)
|
||||
|
||||
|
||||
|
||||
import PySimpleGUI as sg
|
||||
|
||||
form = sg.FlexForm('Math')
|
||||
|
||||
output = sg.Txt('', size=(8,1))
|
||||
|
||||
layout = [ [sg.Txt('Enter values to calculate')],
|
||||
[sg.In(size=(8,1), key='numerator')],
|
||||
[sg.Txt('_' * 10)],
|
||||
[sg.In(size=(8,1), key='denominator')],
|
||||
[output],
|
||||
[sg.ReadFormButton('Calculate', bind_return_key=True)]]
|
||||
|
||||
form.Layout(layout)
|
||||
|
||||
while True:
|
||||
button, values = form.Read()
|
||||
|
||||
if button is not None:
|
||||
try:
|
||||
numerator = float(values['numerator'])
|
||||
denominator = float(values['denominator'])
|
||||
calc = numerator / denominator
|
||||
except:
|
||||
calc = 'Invalid'
|
||||
|
||||
output.Update(calc)
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue