Merge pull request #46 from MikeTheWatchGuy/Dev-latest

Dev latest
This commit is contained in:
MikeTheWatchGuy 2018-08-24 08:07:37 -04:00 committed by GitHub
commit 4a1b8a4aac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 311 additions and 18 deletions

228
Demo_MIDI_Player.py Normal file
View File

@ -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)

View File

@ -290,7 +290,7 @@ class Listbox(Element):
:param auto_size_text: True if should shrink field to fit the default text :param auto_size_text: True if should shrink field to fit the default text
:param background_color: Color for Element. Text or RGB Hex ''' :param background_color: Color for Element. Text or RGB Hex '''
self.Values = values self.Values = values
self.TKListBox = None self.TKListbox = None
if select_mode == LISTBOX_SELECT_MODE_BROWSE: if select_mode == LISTBOX_SELECT_MODE_BROWSE:
self.SelectMode = SELECT_MODE_BROWSE self.SelectMode = SELECT_MODE_BROWSE
elif select_mode == LISTBOX_SELECT_MODE_EXTENDED: 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 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) 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): def __del__(self):
try: try:
self.TKListBox.__del__() 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) 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 return
def Update(self, NewValue): def Update(self, new_value = None, background_color=None, text_color=None):
self.DisplayText=NewValue if new_value is not None:
stringvar = self.TKStringVar self.DisplayText=new_value
stringvar.set(NewValue) 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): def __del__(self):
super().__del__() super().__del__()
@ -704,6 +715,14 @@ class Button(Element):
self.ParentForm.TKroot.quit() # kick the users out of the mainloop self.ParentForm.TKroot.quit() # kick the users out of the mainloop
return 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): def __del__(self):
try: try:
self.TKButton.__del__() 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) super().__init__(ELEM_TYPE_INPUT_SLIDER, scale=scale, size=size, font=font, background_color=background_color, text_color=text_color, key=key)
return return
def Update(self, value):
self.TKIntVar.set(value)
def __del__(self): def __del__(self):
super().__del__() super().__del__()
@ -1026,8 +1048,6 @@ class FlexForm:
if self.RootNeedsDestroying: if self.RootNeedsDestroying:
self.TKroot.destroy() self.TKroot.destroy()
_my_windows.Decrement() _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: if self.LastKeyboardEvent is not None or self.LastButtonClicked is not None:
return BuildResults(self, False, self) return BuildResults(self, False, self)
else: else:
@ -1046,6 +1066,7 @@ class FlexForm:
except: except:
self.TKrootDestroyed = True self.TKrootDestroyed = True
_my_windows.Decrement() _my_windows.Decrement()
# return None, None
return BuildResults(self, False, self) return BuildResults(self, False, self)
def GetScreenDimensions(self): def GetScreenDimensions(self):
@ -1452,7 +1473,7 @@ def PackFormIntoFrame(form, containing_frame, toplevel_form):
stringvar = tk.StringVar() stringvar = tk.StringVar()
element.TKStringVar = stringvar element.TKStringVar = stringvar
stringvar.set(display_text) stringvar.set(display_text)
if auto_size_text: if element.AutoSizeText:
width = 0 width = 0
if element.Justification is not None: if element.Justification is not None:
justification = element.Justification 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 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 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, 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 # Set wrap-length for text (in PIXELS) == PAIN IN THE ASS
wraplen = tktext_label.winfo_reqwidth() # width of widget in Pixels wraplen = tktext_label.winfo_reqwidth()+40 # width of widget in Pixels
tktext_label.configure(anchor=anchor, font=font, wraplen=wraplen+40) # set wrap to width of widget 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: if element.BackgroundColor is not None:
tktext_label.configure(background=element.BackgroundColor) tktext_label.configure(background=element.BackgroundColor)
if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None: if element.TextColor != COLOR_SYSTEM_DEFAULT and element.TextColor is not None:
tktext_label.configure(fg=element.TextColor) tktext_label.configure(fg=element.TextColor)
tktext_label.pack(side=tk.LEFT) tktext_label.pack(side=tk.LEFT)
# print(f'Text element placed w = {width}, h = {height}, wrap = {wraplen}') element.TKText = tktext_label
# ------------------------- BUTTON element ------------------------- # # ------------------------- BUTTON element ------------------------- #
elif element_type == ELEM_TYPE_BUTTON: elif element_type == ELEM_TYPE_BUTTON:
element.Location = (row_num, col_num) 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) max_line_total = max(max_line_total, width_used)
# height = _GetNumLinesNeeded(message, width_used) # height = _GetNumLinesNeeded(message, width_used)
height = message_wrapped_lines height = message_wrapped_lines
# print('Msgbox width, height', width_used, height)
form.AddRow(Text(message_wrapped, auto_size_text=True, size=(width_used, height))) form.AddRow(Text(message_wrapped, auto_size_text=True, size=(width_used, height)))
total_lines += height total_lines += height

View File

@ -57,7 +57,7 @@ Browse for a filename that is populated into the input field.
import PySimpleGUI as sg 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')], form_rows = [[sg.Text('SHA-1 and SHA-256 Hashes for the file')],
[sg.InputText(), sg.FileBrowse()], [sg.InputText(), sg.FileBrowse()],
[sg.Submit(), sg.Cancel()]] [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, progress_meter_border_depth=0,
scrollbar_color='#F7F3EC') 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 = [ layout = [
[sg.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25))], [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')], [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, progress_meter_border_depth=0,
scrollbar_color='#F7F3EC') 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 = [ layout = [
[sg.Text('All graphic widgets in one form!', size=(30, 1), font=("Helvetica", 25))], [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')], [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 PySimpleGUI as sg
import time 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 # create a text element that will be updated periodically
text_element = sg.Text('', size=(10, 2), font=('Helvetica', 20), justification='center') 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 PySimpleGUI as sg
import time 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') 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')], layout = [[sg.Text('Non blocking GUI with updates', justification='center')],
[text_element], [text_element],
@ -249,7 +249,7 @@ The architecture of some programs works better with button callbacks instead of
# Create a standard form # Create a standard form
form = sg.FlexForm('Button callback example') form = sg.FlexForm('Button callback example')
# Layout the design of the GUI # 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()]] [sg.ReadFormButton('1'), sg.ReadFormButton('2'), sg.Quit()]]
# Show the form to the user # Show the form to the user
form.Layout(layout) 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) button, values = sg.FlexForm('Compact 1-line form with column').LayoutAndRead(layout)
sg.MsgBox(button, values, line_width=200) 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