You are not logged in.

#1 2016-04-22 13:02:12

Veon
Member
Registered: 2016-04-22
Posts: 5

Script MAME Launcher

Hello everybody smile

I just wanted to share a python script I made to launch MAME roms.
This is not a frontend, you can just browse your roms, with snapshot displayed, and launch them trough MAME. You can create rom lists to hold, let's say, your favorites.
I made this script because I didn't find a frontend for Linux without QT dependencies or without a complicated interface. I'm not a programmer, I just copied python lines of code from the internet. I'm sure there are a lot of programs to manage MAME way better then this script.

Here is a readme for the script and the script itself follows it.

MAME Launcher Script
It's a python script that displays a list with roms detected from the rompath
specified in MAME and the associated snapshot, if present, in the snapshot_directory,
also specified in MAME, using the command mame -sc, which in most cases reads
mame.ini configuration file. Read MAME documentation if you wish to know more
about mame.ini.

IMPORTANT: this is not a frontend, it just launches MAME with the selected rom.
This script is provided as is, without any express, implied and/or statutory
warranties. Use at your own risk, the author takes no responsibility. Also
remember that to use roms protected by copyright you must have permission by
the copyright holder, or own the original machine.

Before you attempt to run MAME Launcher, you should make sure you can start
MAME by itself, using the command line mame <rom name>.
A MAME logo picture must exist in the same directory as the script, or any
other picture of your choice: you can configure the picture name via the
configuration file, or you can rename your custom picture to mame.png.


Dependecies
This script uses tkinter for the gui, so you have to install python tcl/tk
bindings for it to work.
For Ubuntu/Debian install packages python-tk and python-pil.imagetk.
Obviously you have to install MAME,  as said before.


Structure
By default, three files are created in the same diretory as the MAME Launcher
script:
- mame_launcher_descs.cfg
  When MAME Launcher is first started, it dumps out from the command mame -ll
  a full list of the supported roms and relative description, e.g.:
  Car Polo;carpolo
  Star Fire 2;starfir2
  The Adventures of Robby Roto!;robby
  It's just a csv file, with ";" separator.
  Later executions of MAME Launcher will read this file to retrieve the
  corresponding description for roms.
- mame_launcher_all_games.cfg
  After the creation of the previous file, MAME Launcher writes to this file
  the list of roms found in the rompath retrieved from MAME configuration,
  using the command mame -sc, which in most cases reads mame.ini configuration
  file. In this file you'll find just rom names, like
  carpolo
  starfir2
  robby
  this rom list is not alphabetically ordered, nor it needs to be.
  When the gui starts, the description file will be read and for each rom the
  associated description is displayed, ordered alphabetically. This is a special
  rom group, they're discussed below.
- mame_launcher_last_roms.cfg
  This file is for writing the last rom selected in a rom group, and to track
  the last group displayed, so the next time you select a rom group the last rom
  selected will be active, or, the next time you start MAME Launcher, the last
  rom group will be displayed, with the last rom selected, activated.


Rom Groups
You can create rom groups using the button in the gui, this simply will create a
file in the same directory as MAME Launcher, named mame_launcher_<name>.cfg.
The first line will be a special formatted line to define the group name as will
be displayed in the combobox. The line is:
game group title: <name of group>
Once the group is created, you can select roms in another rom group, let's say
all games, and add them to your newly created group. This means a line will be
written in the mame_launcher_<name>.cfg file. This cfg file contains simply the
rom names like the mame_launcher_all_games.cfg. You can easily create a file by
hand, just remember the first line and to insert rom names one per line.
In this cfg files, the roms are not alphabetically ordered, nor they need to be.
When you select a rom group, the corresponding cfg file will be read and
descriptions will be retrieved from the mame_launcher_descs.cfg, populating the
games list, alphabetically ordered by description.
There are two special groups:
- the All Games group (mentioned before), it will be always present and contains
  all games found in the rompath specified in mame configuration. You can't
  modify or delete this group
- the Working games group, you can create it by clicking the "Scan for working
  games" button in the gui, this means that every rom present in All Games group
  will be tested with the command mame -verifyroms <rom name>, and if the rom
  passes the test will be added to the Working games group. The rom list will be
  written to mame_launcher_working_games.cfg. You can modify or delete this
  group. Please note: testing every rom you have on your disk can be a very long
  process, depending on how many roms you have, you can monitor the script work
  viewing the output in the shell window


Configuration file
Optionally, you can create a general configuration file, to set few parameters.
You can create the default configuration file with the command:
python mame_launcher.py -wc
this creates the default configuration file, mame_launcher.cfg, in the same
directory the MAME Launcher script is. In this configuration file you can set:
- mame_exe: the location of the mame executable,
- mame_logo: the file name for a mame logo, or whatever picture you want
  displayed when the selected rom has no snapshot available, it has to be in the
  same directory of MAME Launcher script,
- all_games_name: a different name for the All Games rom group: please note that
  this name will be written in the all games cfg file the first time you create
  it, but, just for this group, the name you specify in mame_launcher.cfg
  configuration file will be displayed in the combobox, in MAME Launcher gui.
  Other groups will be displayed in the combobox with the name specified in
  their respective cfg file.
- working_games_name: a different name for the working games group: remember
  that the first time you create the working games group, the name you specified
  in mame_launcher.cfg will be written in mame_launcher_working_games.cfg, and
  then every time you start MAME Launcher the name specified in
  mame_launcher_working_games.cfg will be displayed in the combobox.
- last_group: a cfg file name to load when MAME Launcher starts, or you can
  leave it empty to load the last active group saved in
  mame_launcher_last_roms.cfg


Launching games
Select a game in the listbox and press Enter, or double click it, or click the
button Play. Eventually, you can specify additional parameters for MAME
executable, using the labeled entry field near the Play button.


Ending note
I'm not a programmer, I just wanted to play with MAME on my Linux box, and I
didn't find a fronted suited to my needs. Luckly, python is easy and a lot of
documentation can be found on the internet. You can modify the script as you
wish.
Also, sorry for my english.

And here is the python script

## MAME Launcher Script

import os, tkMessageBox, ImageTk, PIL, subprocess, csv, ttk, re, ConfigParser, platform
from sys import exit, argv
from PIL import Image
from Tkinter import *

# https://code.activestate.com/recipes/578894-mousewheel-multiplatform-tkinter/
# adds mouse wheel binding to scroll
class ScrollingArea(object):
    OS = platform.system()
    
    def __init__(self, root, factor = 2):
        
        self.activeArea = None
        
        if type(factor) == int:
            self.factor = factor
        else:
            raise Exception("Factor must be an integer.")

        if self.OS == "Linux" :
            root.bind_all('<4>', self.onMouseWheel,  add='+')
            root.bind_all('<5>', self.onMouseWheel,  add='+')
        else:
            # Windows and MacOS
            root.bind_all("<MouseWheel>", self.onMouseWheel,  add='+')

    def onMouseWheel(self,event):
        if self.activeArea:
            self.activeArea.onMouseWheel(event)

    def mouseWheel_bind(self, widget):
        self.activeArea = widget

    def mouseWheel_unbind(self):
        self.activeArea = None

    def build_function_onMouseWheel(self, widget, orient, factor = 1):
        view_command = getattr(widget, orient+'view')
        
        if self.OS == 'Linux':
            def onMouseWheel(event):
                if event.num == 4:
                    view_command("scroll",(-1)*factor,"units" )
                elif event.num == 5:
                    view_command("scroll",factor,"units" ) 
                
        elif self.OS == 'Windows':
            def onMouseWheel(event):        
                view_command("scroll",(-1)*int((event.delta/120)*factor),"units" ) 
        
        elif self.OS == 'Darwin':
            def onMouseWheel(event):        
                view_command("scroll",event.delta,"units" )             
        
        return onMouseWheel

    def add_scrolling(self, scrollingArea, xscrollbar=None, yscrollbar=None):
        if yscrollbar or not yscrollbar is None:
            #scrollingArea.configure(xscrollcommand=xscrollbar.set)
            scrollingArea.configure(yscrollcommand=yscrollbar.set)
            yscrollbar['command']=scrollingArea.yview

        if xscrollbar or not xscrollbar is None:
            #scrollingArea.configure(yscrollcommand=yscrollbar.set)
            scrollingArea.configure(xscrollcommand=xscrollbar.set)
            xscrollbar['command']=scrollingArea.xview
        
        scrollingArea.bind('<Enter>',lambda event: self.mouseWheel_bind(scrollingArea))
        scrollingArea.bind('<Leave>', lambda event: self.mouseWheel_unbind())

        if xscrollbar and not hasattr(xscrollbar, 'onMouseWheel'):
            xscrollbar.onMouseWheel = self.build_function_onMouseWheel(scrollingArea,'x', self.factor)

        if yscrollbar and not hasattr(yscrollbar, 'onMouseWheel'):
            yscrollbar.onMouseWheel = self.build_function_onMouseWheel(scrollingArea,'y', self.factor)

        main_scrollbar = yscrollbar or xscrollbar
        
        if main_scrollbar:
            scrollingArea.onMouseWheel = main_scrollbar.onMouseWheel

        for scrollbar in (xscrollbar, yscrollbar):
            if scrollbar:
                scrollbar.bind('<Enter>', lambda event, scrollbar=scrollbar: self.mouseWheel_bind(scrollbar) )
                scrollbar.bind('<Leave>', lambda event: self.mouseWheel_unbind())

# sets right-click with copy-cut-paste actions
# http://stackoverflow.com/questions/4266566/stardand-context-menu-in-python-tkinter-text-widget-when-mouse-right-button-is-p
def rClicker(e):
    ## right click context menu for all Tk Entry and Text widgets
    try:
        def rClick_Copy(e, apnd=0):
            e.widget.event_generate('<Control-c>')
        def rClick_Cut(e):
            e.widget.event_generate('<Control-x>')
        def rClick_Paste(e):
            e.widget.event_generate('<Control-v>')
        e.widget.focus()

        list_cmd=[
               ('Cut', lambda e=e: rClick_Cut(e)),
               ('Copy', lambda e=e: rClick_Copy(e)),
               ('Paste', lambda e=e: rClick_Paste(e)),
               ]

        rmenu = Menu(None, tearoff=0, takefocus=0)
        for (txt, cmd) in list_cmd:
            rmenu.add_command(label=txt, command=cmd)
        rmenu.tk_popup(e.x_root+40, e.y_root+10,entry="0")
        #rmenu.post(e.x_root+40, e.y_root+10)
    except TclError:
        print ' - rClick menu, something wrong'
        pass
    return "break"

def rClickbinder(r):
    try:
        for b in ['Text', 'Entry', 'Listbox', 'Label']:
            r.bind_class(b, sequence='<Button-3>', func=rClicker, add='')
    except TclError:
        print ' - rClickbinder, something wrong'
        pass

# purge all special characters from a string
# http://stackoverflow.com/questions/23586728/django-trying-to-split-and-urlify-tags-in-models-but-code-isnt-working
def urlify(str):
    s = str.strip()
    # Remove all non-word characters (everything except numbers and letters)
    s = re.sub(r"[^\w\s]", '', s)
    # Replace all runs of whitespace with a single dash
    s = re.sub(r"\s+", '-', s)
    return s

# takes the first item in a list and returns it to use it in a sort function
def by_zero(a):
    return a[0].lower()

# change the snapshot associated to a rom name
def change_img(e):
    #print "change_img"
    num = listbox.curselection()[0]
    try:
        rom = games_list[num]
    except IndexError:
        print "Invalid rom"
        img = mame_logo
        game_desc.configure(text = "...")
    else:
        for p in img_path:
            p = os.path.expandvars(p)
            #print p
            if os.path.isfile(os.path.join(p, rom[1] + ".png")):
                img = os.path.join(p, rom[1] + ".png")
            else:
                if os.path.isfile(os.path.join(p, rom[1] + ".jpg")):
                    img = os.path.join(p, rom[1] + ".jpg")
                else:
                    img = mame_logo
        #print img
        cfg_files_lists[group_titles[group.current()][1]][2] = rom[1]
        write_last_roms_file()
        game_desc.configure(text = rom[0] + "\n" + rom[1] + "\n")
    game_desc.update()
    img1 = ImageTk.PhotoImage(PIL.Image.open(img))
    lbl.configure(image = img1)
    lbl.image = img1
    lbl.update()

# for the button "play", to play the selected rom in the listbox
def game_run():
    try:
        rom = cfg_files_lists[group_titles[group.current()][1]][2]
    except (KeyError, ValueError):
        print "Invalid rom"
    else:
        par = parameters.get().strip().lower().split()
        #print "par", par
        com = [mame_exe]
        com.extend(par)
        com.append(rom)
        #print com
        try:
            print "Game run", com
            subprocess.check_call(com, shell=False)
        except subprocess.CalledProcessError as e1:
            print"MAME error with", rom, e1

# parses the mame configuration for the rompath e snapshot_directory values
def retrieve_paths():
    out = subprocess.check_output((mame_exe, "-sc"), shell=False)
    #out = subprocess.check_output(("ls", "-l"), shell=False)
    check = [0,0]
    for line in out.strip().split("\n"):
        #print line
            if "rompath" in line.strip().lower():
                g = line.strip().split()
                g_path = g[1].split(";")
                #print "Games",g_path
                check[0] = 1
            if "snapshot_directory" in line.strip().lower():
                s = line.strip().split()
                s_path = s[1].split(";")
                #print "Snap",s_path
                check[1] = 1
            if check == [1,1]:
                return (g_path, s_path)
    else:
        if check[0] == 0 or len(g_path) == 0:
            print("rompath not found in MAME configuration")
        if check[1] == 0 or len(s_path) == 0:
            print("snapshot_directory not found in MAME configuration")
        exit(1)

# builds, or reads, the main files with all the roms and associated description and all the roms found in the rompath retrieved earlier
def first_games_listing(force):
    # dictionary with roms name as keys, and every key is a list of rom descrition and rom name
    global descs_list
    # dictionary with cfg files as keys, and every key is a list with: list of rom description and rom name found in the cfg file, only rom names, the last rom selected
    global cfg_files_lists
    global last_group
    gl = []
    descs_list = {}

    if os.path.isfile(descs_file) and not force == "force":
        with open(descs_file, "rb") as r1:
            rc = csv.reader(r1, delimiter=";")
            descs_list = {x[1]: x for x in rc}
        if os.path.isfile(all_games_file):
            with open(all_games_file ,"rb") as r1:
                roms = r1.read().splitlines()
            del roms[0]
            gl = [descs_list[i] for i in roms]
        if not os.path.isfile(all_games_file):
            print "Building all games file..."
            roms = []
            with open(all_games_file, "wb") as w:
                w.write(all_games_title)
                for p in games_path:
                    p = os.path.expandvars(p)
                    if os.path.isdir(p):
                        for f in os.listdir(p):
                            r = os.path.splitext(f)
                            rom = r[0]
                            if rom in descs_list and not rom in roms:
                                gl.append(descs_list[rom])
                                roms.append(rom)
                                w.write("\n" + rom)

    if not os.path.isfile(descs_file) or force == "force":
        print "Building descriptions and roms main file..."
        try:
            out = subprocess.check_output((mame_exe, "-ll"), shell=False)
        except subprocess.CalledProcessError:
            print "Error generating list"
            exit(1)
        else:
            r = out.strip().splitlines()
            del r[0]
            #descs_list = []
            with open(descs_file, "wb") as w:
                wr = csv.writer(w, delimiter=";")
                for l in r:
                    #print l
                    l = l.strip().split()
                    #print l
                    d = l[1:]
                    d = " ".join(d)
                    #print d
                    d = d[1:-1]
                    rom = [d, l[0].strip()]
                    descs_list[rom[1]] = rom
                    wr.writerow(rom)
            if os.path.isfile(all_games_file) and not force == "force":
                print "Building all games file..."
                with open(all_games_file, "rb") as r:
                    roms = r.read().splitlines()
                del roms[0]
                gl = [descs_list[i] for i in roms]
            if not os.path.isfile(all_games_file) or force == "force":
                print "Building all games file..."
                roms = []
                with open(all_games_file, "wb") as w:
                    w.write(all_games_title)
                    for p in games_path:
                        p = os.path.expandvars(p)
                        if os.path.isdir(p):
                            for f in os.listdir(p):
                                r = os.path.splitext(f)
                                rom = r[0]
                                #print rom
                                if rom in descs_list and not rom in roms:
                                    gl.append(descs_list[rom])
                                    roms.append(rom)
                                    w.write("\n" + rom)

    if len(gl) > 0:
        gl.sort(key=by_zero)
        cfg_files_lists = {all_games_file: [gl, roms, gl[0][1]]}
    else:
        cfg_files_lists = {all_games_file: [gl, roms, "empty"]}

    if os.path.isfile(last_roms_file):
        with open (last_roms_file, "rb") as r1:
            rc = csv.reader(r1, delimiter=";")
            cfg = [x[1] for x in group_titles]
            for i in rc:
                if os.path.isfile(i[0]):
                    if last_group is None or len(last_group) == 0 or not last_group in cfg:
                        try:
                            if i[2] == "active":
                                if os.path.isfile(i[0]):
                                    last_group = i[0]
                                    print i[0], "active"
                        except IndexError:
                            #print i[0], "not active"
                            pass
                    if not i[0] == all_games_file:
                        #print "read", i[0]
                        read_conf(i[0], None, "y")
                        #print "list", cfg_files_lists[i[0]]
                    cfg_files_lists[i[0]][2] = i[1]

# reads a conf file and adds the roms found in the cfg_files_lists dict, or reads the cfg_files_lists dict if already existent
def read_conf(file, rom, desc):
    try:
        gl = cfg_files_lists[file][0]
        gl.sort(key=by_zero)
        roms = cfg_files_lists[file][1]
    except KeyError:
        with open(file, "rb") as r:
            roms = r.read().splitlines()
        #print roms[0]
        del roms[0]
        #gl = [descs_list[only_roms_list.index(i)] for i in roms if i in only_roms_list]
        gl = [descs_list[i] for i in roms]
        gl.sort(key=by_zero)
        if len(gl) > 0:
            cfg_files_lists[file] = [gl, roms, gl[0][1]]
        else:
            cfg_files_lists[file] = [gl, roms, "empty"]
    if not rom is None:
        if rom in roms:
            return True
        return False
    if desc == "y":
        return gl
    return roms

# activated with combobox selection, to change the games list displayed in the listbox
def change_listbox():
    global games_list
    global last_group
    print "change_listbox", group_var.get()
    try:
        gl = cfg_files_lists[group_titles[group.current()][1]]
        #print gl
        n = gl[0].index(descs_list[gl[2]])
        #print n
    except (KeyError, ValueError):
        #print "no index"
        n = 0
    if active_group(last_group):
        #print "Group already active"
        #listbox.select_set(cfg_files_lists[group_titles[group.current()][1]][2])
        listbox.select_set(n)
        listbox.activate(n)
    else:
        #print "Loading", group_var.get()
        cur = group_titles[group.current()][1]
        games_list = read_conf(cur, None, "y")
        #print cfg_files_lists[group_titles[group.current()][1]]
        listbox.delete(0, END)
        if len(games_list) > 0:
            for i in games_list:
                listbox.insert(END, i[0])
            #n = cfg_files_lists[group_titles[group.current()][1]][2]
            listbox.see(n)
            listbox.activate(n)
            listbox.select_set(n)
            listbox.event_generate("<<ListboxSelect>>")
            #write_last_roms_file()
            l = len(games_list)
            if l == 1:
                len_roms = "1 rom"
            else:
                len_roms = str(l) + " roms"
        else:
            #listbox.insert(END, "- empty -")
            img = ImageTk.PhotoImage(PIL.Image.open(mame_logo))
            lbl.configure(image = img)
            lbl.image = img
            lbl.update()
            game_desc.configure(text = "...")
            game_desc.update()
            len_roms = str(len(games_list)) + " roms"
        last_group = cur
        search_ent.delete(0, END)
        search_lbl.configure(text = "    ")
        search_lbl.update()
        rom_lbl.configure(text = len_roms)
        rom_lbl.update()
    listbox.focus_set()

# started by the Add to group button
# if there's no other group then all games group, it opens the entry field to insert a new rom group
# if there's only one group other then tha all games group, the rom selected is automatically added to that one group
def adding_rom(rom):
    if len(group_titles) == 1:
        new_group_win()
    if len(group_titles) == 2:
        add_to_group(1, rom)
    if len(group_titles) > 2:
        add_to_group_win(rom)

def add_to_group(num, rom):
    p = group_titles[num][1]
    #print "add_to_group", p
    if read_conf(p, rom[1], None):
        print("\"" + rom[0] + "\" already in group " + group_titles[num][0])
    else:
        with open(p, "ab") as write:
            write.write("\n" + rom[1])
        cfg_files_lists[p][0].append(rom)
        cfg_files_lists[p][1].append(rom[1])
        print rom[1], "added to", p
    if len(group_titles) > 2:
        win_close(agw)
    listbox.focus_set()
    listbox.select_set(games_list.index(rom))
    #listbox.event_generate("<<ListboxSelect>>")
    global last_add_to_group
    last_add_to_group = num - 1

# window with a listbox with rom groups
def add_to_group_win(rom):
    global agw
    global last_add_to_group
    
    try:
        last_add_to_group
    except NameError:
        last_add_to_group = 0

    agw = Toplevel(root)
    agw.grab_set()
    agw.wm_title(w_title)
    agw.protocol('WM_DELETE_WINDOW', lambda: win_close(agw))

    frame = Frame(agw, width=90, height=50, bd=1)
    frame.pack()

    lb = Listbox(frame, height=10, width=20)
    for i in group_titles[1:]:
        lb.insert(END, i[0])
    lb.pack(side=LEFT, fill=X, padx=5)
    lb.bind('<Return>', (lambda event: add_to_group(lb.curselection()[0] + 1, rom)))
    lb.bind('<KP_Enter>', (lambda event: add_to_group(lb.curselection()[0] + 1, rom)))
    lb.bind('<Double-1>', (lambda event: add_to_group(lb.curselection()[0] + 1, rom)))

    sb = Scrollbar(frame, orient=VERTICAL, command=lb.yview)
    sb.pack(side=LEFT, fill=Y)
    lb.configure(yscrollcommand=sb.set)

    lb.focus_set()
    lb.activate(last_add_to_group)
    lb.select_set(last_add_to_group)
    lb.see(last_add_to_group)

    btn = Button(frame, text="OK", command=lambda: add_to_group(lb.curselection()[0] + 1, rom))
    btn.pack(side=BOTTOM, fill=Y)
    btn.bind('<Return>', (lambda event: add_to_group(lb.curselection()[0] + 1, rom)))
    btn.bind('<KP_Enter>', (lambda event: add_to_group(lb.curselection()[0] + 1, rom)))

    agw.mainloop()

# started by the New Group button, it's just an entry field were it's possible to write a new group name
def new_group_win():
    global ngw
    ngw = Toplevel(root)
    ngw.grab_set()
    ngw.wm_title(w_title)
    ngw.protocol('WM_DELETE_WINDOW', lambda: win_close(ngw))

    frame = Frame(ngw, width=90, height=50, bd=1)
    frame.pack()

    label = Label(frame, text="New Group Name:")
    label.grid(row=0, column=0, padx=5, pady=2)

    entry = Entry(frame, background="white", width=20)
    entry.grid(row=10, column=0, columnspan=2, padx=3, pady=3)
    entry.focus_set()
    entry.bind('<Button-3>',rClicker, add='')
    entry.bind('<Return>', (lambda event: new_group(entry.get())))
    entry.bind('<KP_Enter>', (lambda event: new_group(entry.get())))

    btn = Button(frame, text="OK", command=lambda: new_group(entry.get()))
    btn.grid(row=20, column=10, pady=3, padx=3)
    btn.bind('<Return>', (lambda event: new_group(entry.get())))
    btn.bind('<KP_Enter>', (lambda event: new_group(entry.get())))

    ngw.mainloop()

# takes what's written in the entry field and creates a new file and a new entry in the combobox list
def new_group(name):
    gt = [x[0].lower().strip() for x in group_titles]
    if not name.lower().strip() in gt:
        n = urlify(name)
        f = os.path.join(script_path, "mame_launcher_" + n + ".cfg")
        num = 0
        while os.path.isfile(f):
            str_num = str(num).zfill(3)
            #print "str_num", str_num
            f = os.path.join(script_path, n + "_" + str_num + ".cfg")
            num = num + 1
        with open(f, "wb") as w:
            w.write(title_group_file + " " + name)
        print f, "created"
        group_titles.append([name, f])
        group_titles.remove([all_games_name, all_games_file])
        group_titles.sort(key=by_zero)
        group_titles.insert(0, [all_games_name, all_games_file])
        group['values'] = [x[0] for x in group_titles]
        group.update()
    else:
        print("Group " + name + " already exists")
    win_close(ngw)
    listbox.focus_set()

def win_close(w):
    w.quit()
    w.destroy()

# populates the combobox list
def group_titles_combobox():
    n = []
    gt = []
    for f in os.listdir(script_path):
        cf = os.path.join(script_path, f)
        if os.path.isfile(cf) and cf.lower().endswith(".cfg") and not cf.lower() == all_games_file.lower():
            with open(cf, "rb") as r:
                try:
                    first = next(r)
                except StopIteration:
                    #print "empty file"
                    continue
                else:
                    if first.strip().lower().startswith(title_group_file):
                        cf_t = first.split(":")
                        t = cf_t[1].strip()
                        if not t in n:
                            gt.append([t, cf])
                            n.append(t)
    if len(gt) > 0:
        gt.sort(key=by_zero)
    gt.insert(0, [all_games_name, all_games_file])
    print "Rom groups:", gt
    return gt

# repeats the initial scan for roms like the main description and all games files were not present
def rescan_all_games():
    first_games_listing("force")
    #print group_var.get()
    global last_group
    last_group = None
    change_listbox()

# runs the -verifyroms command for each rom present in all games group and creates a new group
def working_games_scan():
    global last_group
    gl = cfg_files_lists[all_games_file][1]
    if len(gl) > 0:
        try:
            del cfg_files_lists[working_games_file]
        except KeyError:
            print "Working games not present"
        with open(working_games_file, "wb") as w:
            w.write(working_games_title)
            for rom in gl:
                try:
                    subprocess.check_call((mame_exe, "-verifyroms", rom), shell=False)
                except subprocess.CalledProcessError:
                    #print "not valid"
                    gl.remove(rom)
                else:
                    #print "rom ok"
                    w.write("\n" + rom)
        group_titles.append([working_games_name, working_games_file])
        group_titles.remove([all_games_name, all_games_file])
        group_titles.sort(key=by_zero)
        group_titles.insert(0, [all_games_name, all_games_file])
        group['values'] = [x[0] for x in group_titles]
        num = group_titles.index([working_games_name, working_games_file])
        group.current(num)
        group.update()
        last_group = None
        change_listbox()
    else:
        print "No roms to verify"

# checks if the active group in the combobox is associated with the path provided
def active_group(path):
    #print "act",group_var.get()
    #print path
    for i in group_titles:
        #print i
        if group_var.get() == i[0] and path == i[1]:
            return True
    else:
        return False

def delete_group():
    if group_var.get() == all_games_name or group_titles[group.current()][1] == all_games_file:
        print("\"" + all_games_name + "\" can\'t be deleted")
    else:
        global last_group
        n = group_var.get()
        ask = tkMessageBox.askquestion(parent=root, title=w_title, message="Delete group " + n + "?")
        if ask == "yes":
            p = group_titles[group.current()][1]
            try:
                del cfg_files_lists[p]
            except KeyError:
                print p, "not present in cfg_files_lists"
            group_titles.remove([n, p])
            group['values'] = [x[0] for x in group_titles]
            last_group = None
            group.current(0)
            group.update()
            change_listbox()
            try:
                os.remove(p)
            except (IOError, OSError) as e:    
                print e

# deletes a single entry from a rom group
def delete_from_group():
    if group_var.get() == all_games_name or group_titles[group.current()][1] == all_games_file:
        print("\"" + all_games_name + "\" can\'t be modified")
    else:
        #l = len(games_list)
        if len(games_list) > 0:
            n = listbox.curselection()[0]
            #print n
            rom = games_list[n]
            #print rom
            p = group_titles[group.current()][1]
            listbox.delete(n, n)
            del games_list[n]
            # deleting the item in games_list causes the item to disapper from the correspondig list in the dictionary associated, i think because they're the same list.
            # I didn't know this. Anyhow, I leave the try because I'm not sure it works every time.
            try:
                cfg_files_lists[p][0].remove(rom)
            except ValueError:
                print rom, "not in rom group"
            try:
                cfg_files_lists[p][1].remove(rom[1])
            except ValueError:
                print rom, "not in rom group"
            with open(p, "rb") as inp:
                input = inp.read().splitlines()
            input.remove(rom[1])
            with open(p, "wb") as output:
                for line in input:
                    output.write(line)
                    if not input.index(line) == len(input) - 1:
                        output.write("\n")
            l = len(games_list)
            if l == 1:
                len_roms = "1 rom"
            else:
                len_roms = str(l) + " roms"
            rom_lbl.configure(text = len_roms)
            rom_lbl.update()
            if l > 0:
                if n == listbox.size():
                    n = n - 1
                listbox.focus_set()
                listbox.select_set(n)
                listbox.activate(n)
                listbox.event_generate("<<ListboxSelect>>")

# searches the string entered in the current rom group
def search():
    global games_list
    if len(games_list) > 0:
        ins = search_ent.get().strip()
        if len(ins) > 0:
            print ins
            temp_games_list = games_list
            games_list = []
            listbox.delete(0, END)
            ins_s = ins.split()
            for i in temp_games_list:
                c = 0
                for y in ins_s:
                    if y.lower() in i[0].lower():
                        c += 1
                if c == len(ins_s):
                    #print i
                    games_list.append(i)
                    listbox.insert(END, i[0])
                    listbox.itemconfig(END, {'fg': 'blue'})
            if len(games_list) == 1:
                search_lbl.configure(text = "- Search: 1 result -")
                search_lbl.update()
            else:
                search_lbl.configure(text = "- Search: " + str(len(games_list)) + " results -")
                search_lbl.update()
        #print games_list
        #print temp_games_list
        listbox.see(0)
        listbox.focus_set()
        listbox.select_set(0)
        listbox.activate(0)
        #listbox.event_generate("<<ListboxSelect>>")

# resets the search entry and displays the current rom group in full
def search_reset():
    global last_group
    last_group = None
    change_listbox()

# writes a file with the cfg files path and the last rom selected for each path, tracks the last active group
def write_last_roms_file():
    with open(last_roms_file, "wb") as w:
        wc = csv.writer(w, delimiter=";")
        for i in group_titles:
            p = i[1]
            try:
                n = cfg_files_lists[p][2]
            except KeyError:
                #print p,"not listed yet"
                continue
            else:
                if active_group(p):
                    wc.writerow([p, n, "active"])
                else:
                    wc.writerow([p, n])

###################################  Begin  ###################################
w_title = "Python - MAME Launcher"
print w_title

# full path of the mame launcher script directory
script_path = os.path.dirname(os.path.realpath(__file__))
print("Script path: " + script_path)

# main cfg file for all roms detected in the rompath
all_games = "mame_launcher_all_games.cfg"
all_games_file = os.path.join(script_path, all_games)

# main cfg file with all rom names and descriptions, extracted from the mame executable with the command mame -ll
descs = "mame_launcher_descs.cfg"
descs_file = os.path.join(script_path, descs)

# cfg file for working roms only
working_games = "mame_launcher_working_games.cfg"
working_games_file = os.path.join(script_path, working_games)

cf = os.path.join(script_path, "mame_launcher.cfg")
# writing default configuration file if parameter is present
try:
    if argv[1].lower() == "-wc" or argv[1].lower() == "--writeconf":
        if os.path.isfile(cf):
            print "Configuration file", cf, "already present."
            while True:
                ask = raw_input("Do you want to overwrite it?  (y/n): ")
                ask = ask.strip().lower()
                if ask == "y":
                    break
                if ask == "n":
                    print "Not overwriting"
                    exit(0)
                print "Insert y or n"
            print "Writing new configuration file...", cf
            with open(cf, "wb") as conf:
                conf.write("# some parameters for MAME launcher\n"
                           "# keep the formatting\n"
                           "[mame launcher]\n"
                           "# location of mame executable\n"
                           "mame_exe = /usr/games/mame\n"
                           "# name of picture to display as MAME logo, it has to be in the same directory as the MAME Launcher script\n"
                           "mame_logo = mame.png\n"
                           "# name of rom group with all games found in rompath, it will be displayed in the combobox\n"
                           "all_games_name = All Games\n"
                           "# name of rom group with all working games found in rompath, they're selected trough the -verifyroms command line\n"
                           "working_games_name = Working\n"
                           "# leave blank for loading the last group displayed the last time MAME Launcher was used\n"
                           "# or you can set a rom group cfg file name to always load it when MAME launcher starts\n"
                           "# e.g. last_group = mame_launcher_favorites.cfg\n"
                           "last_group")
            print "Default configuration file written"
            exit(0)
    else:
        print "\nUsage:\n"
        print "Easy: with no arguments the script starts the MAME Launcher gui\n"
        print "-wc, --writeconf : writes the standard configuration file, mame_launcher.cfg, in the current directory\n"
        print "There\'s only the -wc argument available"
        exit(1)
except IndexError:
    #print "no arguments"
    pass

print " "

# reading general configuration file if exists
if os.path.isfile(cf):
    print "Parsing configuration file..."
    c = ConfigParser.RawConfigParser(allow_no_value=True)
    try:
        c.read(cf)
    except (ConfigParser.MissingSectionHeaderError, ConfigParser.ParsingError) as e1:
        print "Configuration file error:", cf, e1
        exit(1)
    else:
        try:
            print "MAME executable..."
            mame_exe = c.get('mame launcher', 'mame_exe')
            print "OK:", mame_exe
        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e2:
            print e2
            mame_exe = "/usr/games/mame"
            print "Set default:", mame_exe
        try:
            print "MAME logo picture..."
            mame_logo = c.get('mame launcher', 'mame_logo')
            print "OK:", mame_logo
        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e3:
            print e3
            mame_logo = os.path.join(script_path, "mame.png")
            print "Set default:", mame_logo
        try:
            print "All Games name..."
            all_games_name = c.get('mame launcher', 'all_games_name')
            print "OK:", all_games_name
        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e4:
            print e4
            all_games_name = "All games"
            print "Set default:", all_games_name
        try:
            print "Working games name..."
            working_games_name = c.get('mame launcher', 'working_games_name')
            print "OK:", working_games_name
        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e5:
            print e5
            working_games_name = "Working"
            print "Set default:", working_games_name
        try:
            print "Load roms group..."
            last_group = os.path.join(script_path, c.get('mame launcher', 'last_group'))
            print "OK:", last_group
        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e6:
            print e6
            last_group = None
            print "Set default: dinamically selected from last_roms file"
        except AttributeError:
            last_group = None
            print "Dinamically selected from last_roms file"
else:
    print "Setting defaults..."
    mame_exe = "/usr/games/mame"
    print "MAME executable:", mame_exe
    mame_logo = os.path.join(script_path, "mame.png")
    print "MAME logo picture:", mame_logo
    all_games_name = "All games"
    print "All Games name:", all_games_name
    working_games_name = "Working"
    print "Working games name:", working_games_name
    last_group = None
    print "Load roms group: dinamically selected from last_roms file"

print " "
games_path, img_path = retrieve_paths()
print "Roms", games_path
print "Snapshots", img_path

# string to be written on the first line of every cfg file for rom group
title_group_file = "game group title:"

all_games_title = title_group_file + " " + all_games_name
working_games_title = title_group_file + " " + working_games_name

# this file contains the index number of the last game selcted and relative group
last_roms = "mame_launcher_last_roms.cfg"
last_roms_file = os.path.join(script_path, last_roms)
print " "
group_titles = group_titles_combobox()
print " "
first_games_listing(None)

cfg = [x[1] for x in group_titles]
if last_group is None or len(last_group) == 0 or not last_group in cfg:
    last_group = all_games_file

#print last_group
games_list = cfg_files_lists[last_group][0]
games_list.sort(key=by_zero)
#print str(len(games_list)) + " roms"

########################### Beginning of main window ##########################
root = Tk()
root.wm_title(w_title)
root.geometry("760x650+300+200")

frame_left = Frame(root)
frame_left.pack(fill=Y, side=LEFT)

frame_right = Frame(root)
frame_right.pack(fill=BOTH, side=LEFT, expand=TRUE)

frame_search = Frame(frame_left, relief=SUNKEN, bd=1)
frame_search.pack(side=TOP, padx=4, pady=4)

search_ent = Entry(frame_search, background="white", width=20)
search_ent.grid(row=0, column=0, padx=4, pady=2)
search_ent.bind('<Return>', (lambda event: search()))
search_ent.bind('<KP_Enter>', (lambda event: search()))
search_ent.bind('<Button-3>',rClicker, add='')

search_btn = Button(frame_search, text='Search', command=search)
search_btn.grid(row=0, column=10, padx=4, pady=2)
search_btn.bind('<Return>', (lambda event: search()))
search_btn.bind('<KP_Enter>', (lambda event: search()))

reset_btn = Button(frame_search, text='Reset', command=search_reset)
reset_btn.grid(row=0, column=20, padx=4, pady=2)
reset_btn.bind('<Return>', (lambda event: search_reset()))
reset_btn.bind('<KP_Enter>', (lambda event: search_reset()))

search_lbl = Label(frame_search, text="    ")
search_lbl.grid(row=10, column=0, padx=4, pady=2)

frame_list = Frame(frame_left, relief=SUNKEN, bd=1)

listbox = Listbox(frame_list, height=60, width=36)

if len(games_list) > 0:
    for i in games_list:
        listbox.insert(END, i[0])

listbox.pack(side=LEFT, fill=BOTH, padx=5)
sb = Scrollbar(frame_list, orient=VERTICAL, command=listbox.yview)
sb.pack(side=LEFT, fill=Y)
listbox.configure(yscrollcommand=sb.set)
listbox.bind('<<ListboxSelect>>', change_img)
listbox.bind('<Return>', (lambda event: game_run()))
listbox.bind('<Double-1>', (lambda event: game_run()))
listbox.bind('<KP_Enter>', (lambda event: game_run()))

ScrollingArea(root).add_scrolling(listbox, yscrollbar=sb)

frame_list.pack(side=TOP, padx=4, pady=4)

# http://tkinter.unpythonic.net/wiki/VerticalScrolledFrame  I adapted it to my situation
# create a canvas object and a vertical and horizontal scrollbar for scrolling it
vscrollbar = Scrollbar(frame_right, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
hscrollbar = Scrollbar(frame_right, orient=HORIZONTAL)
hscrollbar.pack(fill=X, side=BOTTOM, expand=FALSE)
canvas = Canvas(frame_right, bd=0, highlightthickness=0, yscrollcommand=vscrollbar.set, xscrollcommand=hscrollbar.set)
canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
vscrollbar.config(command=canvas.yview)
hscrollbar.config(command=canvas.xview)

# reset the view
canvas.xview_moveto(0)
canvas.yview_moveto(0)

# create a frame inside the canvas which will be scrolled with it
interior = Frame(canvas)
interior.pack(side=LEFT, expand=TRUE)
interior_id = canvas.create_window(0, 0, window=interior, anchor=NW)

# updates the scrollbars to match the size of the inner frame
def configure_scroll(event):
    can = ((root.winfo_width() - frame_left.winfo_width())-13, root.winfo_height()-13)
    #print "can",can
    canvas.config(width=can[0])
    canvas.config(height=can[1])
    size = (interior.winfo_reqwidth(), interior.winfo_reqheight())
    #print "size",size
    if size[0] >= can[0] and size[1] >= can[1]:
        canvas.config(scrollregion=[0, 0, size[0], size[1]])
    if size[0] > can[0] and size[1] < can[1]:
        canvas.config(scrollregion=[0, 0, size[0], can[1]])
    if size[0] < can[0] and size[1] > can[1]:
        canvas.config(scrollregion=[0, 0, can[0], size[1]])
    if size[0] <= can[0] and size[1] <= can[1]:
        canvas.config(scrollregion=[0, 0, can[0], can[1]])
    interior.update_idletasks()
    canvas.update_idletasks()
interior.bind('<Configure>', configure_scroll)
canvas.bind('<Configure>', configure_scroll)

ScrollingArea(root).add_scrolling(canvas, xscrollbar=hscrollbar, yscrollbar=vscrollbar)

#frame_img = Frame(frame_right.interior, relief=GROOVE, bd=1)
frame_img = Frame(interior, relief=GROOVE, bd=1)
frame_img.pack(fill=X, side=TOP, padx=4, pady=4)
#frame_img.pack(side=TOP, padx=4, pady=4)

img = ImageTk.PhotoImage(PIL.Image.open(mame_logo))
lbl = Label(frame_img, image=img)
lbl.image = img
lbl.pack(side=TOP, padx=4, pady=4)

game_desc = Label(frame_img, text="...")
game_desc.pack(side=TOP, padx=4, pady=4)

#frame_group = Frame(frame_right.interior, relief=GROOVE, bd=1)
frame_group = Frame(interior, relief=GROOVE, bd=1)
frame_group.pack(fill=X, side=TOP, padx=4, pady=4)
#frame_group.pack(side=TOP, padx=4, pady=4)

add_btn = Button(frame_group, text='Add to group...', command=lambda: adding_rom(games_list[listbox.curselection()[0]]))
add_btn.grid(row=0, column=0, padx=5, pady=5)
add_btn.bind('<Return>', (lambda event: adding_rom(games_list[listbox.curselection()[0]])))
add_btn.bind('<KP_Enter>', (lambda event: adding_rom(games_list[listbox.curselection()[0]])))

group_var = StringVar()
group_var.set(all_games_name)
group = ttk.Combobox(frame_group, textvariable=group_var, width=20)
group.bind("<<ComboboxSelected>>", (lambda event: change_listbox()))
group.grid(row=0, column=10, padx=5, pady=5)
group.configure(state="readonly")
group['values'] = [x[0] for x in group_titles]
group.update()

new_btn = Button(frame_group, text='New group...', command=new_group_win)
new_btn.grid(row=0, column=20, padx=5, pady=5)
new_btn.bind('<Return>', (lambda event: new_group_win()))
new_btn.bind('<KP_Enter>', (lambda event: new_group_win()))

del_rom_btn = Button(frame_group, text='Delete from group', command=delete_from_group)
del_rom_btn.grid(row=10, column=0, padx=5, pady=5)
del_rom_btn.bind('<Return>', (lambda event: delete_from_group()))
del_rom_btn.bind('<KP_Enter>', (lambda event: delete_from_group()))

rom_lbl = Label(frame_group, text=str(len(games_list)) + " roms")
rom_lbl.grid(row=10, column=10, padx=4, pady=5, sticky=N)

del_btn = Button(frame_group, text='Delete group', command=delete_group)
del_btn.grid(row=10, column=20, padx=5, pady=5)
del_btn.bind('<Return>', (lambda event: delete_group()))
del_btn.bind('<KP_Enter>', (lambda event: delete_group()))

#frame_btn = Frame(frame_right.interior, relief=GROOVE, bd=1)
frame_btn = Frame(interior, relief=GROOVE, bd=1)
frame_btn.pack(fill=X, side=TOP, padx=4, pady=4)
#frame_btn.pack(side=TOP, padx=4, pady=4)

paramters_info = Label(frame_btn, text="Additional parameters\nfor executing MAME")
paramters_info.grid(row=0, column=0, padx=3, pady=3)

clear_btn = Button(frame_btn, text='Rescan all games', command=rescan_all_games)
clear_btn.grid(row=0, column=10, padx=5, pady=3)
clear_btn.bind('<Return>', (lambda event: rescan_all_games()))
clear_btn.bind('<KP_Enter>', (lambda event: rescan_all_games()))

parameters = Entry(frame_btn, background="white", width=15)
parameters.grid(row=10, column=0, padx=3, pady=3)
parameters.bind('<Return>', (lambda event: game_run()))
parameters.bind('<KP_Enter>', (lambda event: game_run()))
parameters.bind('<Button-3>',rClicker, add='')

working_btn = Button(frame_btn, text='Scan for working games', command=working_games_scan)
working_btn.grid(row=10, column=10, padx=8, pady=3, sticky=E)
working_btn.bind('<Return>', (lambda event: working_games_scan()))
working_btn.bind('<KP_Enter>', (lambda event: working_games_scan()))

run_btn = Button(frame_btn, text='Play', command=game_run)
run_btn.grid(row=20, column=0, padx=3, pady=6)
run_btn.bind('<Return>', (lambda event: game_run()))
run_btn.bind('<KP_Enter>', (lambda event: game_run()))

for i in group_titles:
    if i[1] == last_group:
        group.current(group_titles.index(i))
        group.update()

try:
    n = games_list.index(descs_list[cfg_files_lists[last_group][2]])
except (ValueError, KeyError):
    n = 0
listbox.focus_set()
listbox.see(n)
listbox.select_set(n)
listbox.activate(n)
listbox.event_generate("<<ListboxSelect>>")

root.mainloop()

write_last_roms_file()
exit(0)

Offline

Board footer

Powered by FluxBB