Create a simple game menu with pygame pt. 2 – Recognising the mouse

Please note that I used python 2.7 for this tutorial. If you are using python3.x, this code may not work. There have been many changes that do not allow backwards compatibility. If you run into problems, these websites could be useful: http://docs.python.org/3/whatsnew/3.0.html ; http://www.python.org/dev/peps/pep-3113/ ; https://wiki.python.org/moin/Python2orPython3

Ok, folks it’s been awhile since I wrote my first part of Games Menu tutorial with pygame, but serendipity didn’t kiss me since then. I am trying to make up for this with the second part of my tutorial. We will cover the recognition of mouse movement. Again, like last time, the entire code is at the end of this blog entry.

The last time, we wrote the options we wanted to the screen and centred them. We used a list of list to do this. Today, we will start off by changing this list of list for a list of objects (MenuItems). This makes the entire thing easier to handle and clearer.

So just add this piece of code in between your imports and the GameMenu class.

class MenuItem(pygame.font.Font):
    def __init__(self, text, font=None, font_size=30,
                 font_color=(255, 255, 255), (pos_x, pos_y)=(0, 0)):
        pygame.font.Font.__init__(self, font, font_size)
        self.text = text
        self.font_size = font_size
        self.font_color = font_color
        self.label = self.render(self.text, 1, self.font_color)
        self.width = self.label.get_rect().width
        self.height = self.label.get_rect().height
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.position = pos_x, pos_y

    def set_position(self, x, y):
        self.position = (x, y)
        self.pos_x = x
        self.pos_y = y

and replace this section:

        for index, item in enumerate(items):
            label = self.font.render(item, 1, font_color)

            width = label.get_rect().width
            height = label.get_rect().height

            posx = (self.scr_width / 2) - (width / 2)
            # t_h: total height of text block
            t_h = len(items) * height
            posy = (self.scr_height / 2) - (t_h / 2) + (index * height)

            self.items.append([item, label, (width, height), (posx, posy)])

for the following

        for index, item in enumerate(items):
            menu_item = MenuItem(item)

            # t_h: total height of text block
            t_h = len(items) * menu_item.height
            pos_x = (self.scr_width / 2) - (menu_item.width / 2)
            # This line includes a bug fix by Ariel (Thanks!)
            # Please check the comments section for an explanation
            pos_y = (self.scr_height / 2) - (t_h / 2) + ((index * 2) + index * menu_item.height)

            menu_item.set_position(pos_x, pos_y)
            self.items.append(menu_item)

and finally in the while mainloop: loop

            for name, label, (width, height), (posx, posy) in self.items:
                self.screen.blit(label, (posx, posy))

for

            for item in self.items:
                self.screen.blit(item.label, item.position)

Please have a look at what has been exchanged and you will find that it is essentially still all the same. All I did, was move all the info for the Font class into a separate class called MenuItem.
If you have questions about this change, please leave a comment with the sections, that need clarification.

The next point will be the recognition of whether the mouse pointer hovers over one of our menu items or not. To do this all we need to is figure out if the position of the mouse is within the boundaries. To this end, we will introduce a new method to our MenuItem class, called is_mouse_selection().

    def is_mouse_selection(self, (posx, posy)):
        if (posx >= self.pos_x and posx <= self.pos_x + self.width) and \
            (posy >= self.pos_y and posy <= self.pos_y + self.height):
                return True
        return False

This may look like much, but essentially we are just asking if mouse coordinates x and y are between the left and right border and the top and bottom border of MenuItem, respectively.

Furthermore, we obviously want something to happen, so that we know that something has been selected. This can be anything you want. I chose to change the color of the font. This is quite easy. We just need to write a short setter method and append it to the MenuItem class once more.

    def set_font_color(self, rgb_tuple):
        self.font_color = rgb_tuple
        self.label = self.render(self.text, 1, self.font_color)

I hope you can see by now, why I chose to go away from a list of values to an actual object. It makes it all this little bit easier 😉 Now we only need to put this all together and see the great effect. I chose red as a color and for the fun of it on selection, I let the font be italic, too. This section is put into the while mainloop loop. I am sure you can figure out in which section (Otherwise, see below)

            for item in self.items:
                if item.is_mouse_selection(pygame.mouse.get_pos()):
                    item.set_font_color((255, 0, 0))
                    item.set_italic(True)
                self.screen.blit(item.label, item.position)

If you have tried this out, you will have sure noticed that once you hovered over an item it will stay red and italic. That’s not really what we want, right? We obviously have to return to neutral, too.

                else:
                    item.set_font_color((255, 255, 255))
                    item.set_italic(False)

And there you go! We now recognise the menu items by mouse 🙂 Simple, isn’t it.

——————————————–
The entire code:

#!/usr/bin/python

import pygame

pygame.init()


class MenuItem(pygame.font.Font):
    def __init__(self, text, font=None, font_size=30,
                 font_color=(255, 255, 255), (pos_x, pos_y)=(0, 0)):
        pygame.font.Font.__init__(self, font, font_size)
        self.text = text
        self.font_size = font_size
        self.font_color = font_color
        self.label = self.render(self.text, 1, self.font_color)
        self.width = self.label.get_rect().width
        self.height = self.label.get_rect().height
        self.dimensions = (self.width, self.height)
        self.pos_x = pos_x
        self.pos_y = pos_y
        self.position = pos_x, pos_y

    def set_position(self, x, y):
        self.position = (x, y)
        self.pos_x = x
        self.pos_y = y

    def set_font_color(self, rgb_tuple):
        self.font_color = rgb_tuple
        self.label = self.render(self.text, 1, self.font_color)

    def is_mouse_selection(self, (posx, posy)):
        if (posx >= self.pos_x and posx <= self.pos_x + self.width) and \
            (posy >= self.pos_y and posy <= self.pos_y + self.height):
                return True
        return False


class GameMenu():
    def __init__(self, screen, items, bg_color=(0,0,0), font=None, font_size=30,
                    font_color=(255, 255, 255)):
        self.screen = screen
        self.scr_width = self.screen.get_rect().width
        self.scr_height = self.screen.get_rect().height

        self.bg_color = bg_color
        self.clock = pygame.time.Clock()

        self.items = []
        for index, item in enumerate(items):
            menu_item = MenuItem(item)#, '/home/nebelhom/.fonts/SHOWG.TTF')

            # t_h: total height of text block
            t_h = len(items) * menu_item.height
            pos_x = (self.scr_width / 2) - (menu_item.width / 2)
            # This line includes a bug fix by Ariel (Thanks!)
            # Please check the comments section for an explanation
            pos_y = (self.scr_height / 2) - (t_h / 2) + ((index * 2) + index * menu_item.height)

            menu_item.set_position(pos_x, pos_y)
            self.items.append(menu_item)

    def run(self):
        mainloop = True
        while mainloop:
            # Limit frame speed to 50 FPS
            self.clock.tick(50)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    mainloop = False

            # Redraw the background
            self.screen.fill(self.bg_color)

            for item in self.items:
                if item.is_mouse_selection(pygame.mouse.get_pos()):
                    item.set_font_color((255, 0, 0))
                    item.set_italic(True)
                else:
                    item.set_font_color((255, 255, 255))
                    item.set_italic(False)
                self.screen.blit(item.label, item.position)

            pygame.display.flip()


if __name__ == "__main__":
    # Creating the screen
    screen = pygame.display.set_mode((640, 480), 0, 32)

    menu_items = ('Start', 'Quit')

    pygame.display.set_caption('Game Menu')
    gm = GameMenu(screen, menu_items)
    gm.run()
Advertisements

Create a simple game menu with pygame pt. 1 – Writing the menu options to the screen

Please note that I used python 2.7 for this tutorial. If you are using python3.x, this code may not work. There have been many changes that do not allow backwards compatibility. If you run into problems, these websites could be useful: http://docs.python.org/3/whatsnew/3.0.html ; http://www.python.org/dev/peps/pep-3113/ ; https://wiki.python.org/moin/Python2orPython3

On my quest to write my own game using the pygame library, I noticed that there is not a single tutorial on how to write your own Game Menu interface. There are two separate modules (python-pygame-menu and KezMenu) that allow you to create a menu, but I wanted to have a learning curve and decided to write it myself.

I share my solution in an attempt to get the ball rolling again. I am aware that my solution may not be elegant in places (or in fact good code), but this is what I came up with. If you think you can do better, write your own tutorial and link it in the comments, I am more than happy to read it and learn a thing or two. This is my first tutorial of any kind, so hopefully I can give it a nice structure. Ok, enough waffling, let’s hit it!

Tutorial

First things first. I will assume that you are familiar with python and with pygame. If not, best come back when you worked through a bunch of basic tutorials (Sorry for the link. I had to :D).

For those of you who learn best by reading code, the entire code for this segment is given below.

We shall start by setting the basic scene, i.e. create a black window that doesn’t terminate due to an endless loop. You will hopefully have seen all this already in some of the pygame beginner’s tutorials.

#!/usr/bin/python

import pygame

pygame.init()

class GameMenu():
    def __init__(self, screen, bg_color=(0,0,0)):

        self.screen = screen
        self.bg_color = bg_color
        self.clock = pygame.time.Clock()

    def run(self):
        mainloop = True
        while mainloop:
            # Limit frame speed to 50 FPS
            self.clock.tick(50)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    mainloop = False

            # Redraw the background
            self.screen.fill(self.bg_color)
            pygame.display.flip()

if __name__ == "__main__":
    # Creating the screen
    screen = pygame.display.set_mode((640, 480), 0, 32)
    pygame.display.set_caption('Game Menu')
    gm = GameMenu(screen)
    gm.run()

This is our starting point, from there we need to think, what we actually need in a bog standard Game Menu. Generally, there are options such as Start, Settings and Quit. In order to do that, we will need to make them appear on the screen. For this, we shall use the pygame’s Font module.

So, we define a tuple of strings (here Start and Quit) that should be in the menu. We shall do this in the “if __name__ …” block before the definition of the gm variable. Other conceivable options could be Settings or Highscore, for example.
Of course, we shall then pass this to the GameMenu class.

class GameMenu():
    def __init__(self, screen, items, bg_color=(0,0,0)):

        ...
        self.items = items

...
if __name__ == '__main__':
    ...
    menu_items = ('Start', 'Quit')
    ...
    gm = GameMenu(screen, menu_items)

Now that this is settled, we can finally use the pygame.font module to do what we came here for. So, let’s get to it, to add flexibility, I will give the user the option to define a font, the font colour and a font size, but set it to a default.

class GameMenu():
    def __init__(self, screen, items, bg_color=(0,0,0), font=None, font_size=30,
                    font_color=(255, 255, 255)):

        ...
        self.font = pygame.font.SysFont(font, font_size)
        self.font_color = font_color

Now let’s get to the part, where we make it appear on screen. For this, we need to make use of the font module’s render() method. Let’s just do this in a line below self.font_color.

class GameMenu():
    def __init__(self, screen, items, bg_color=(0,0,0), font=None, font_size=30,
                    font_color=(255, 255, 255)):

        ...
        self.items = []
        for item in items:
            label = self.font.render(item, 1, font_color)
            self.items.append(label)

Now in order to get these texts to the screen, we need the blit() method. Let’s try that.

class GameMenu():
    ...
    def run(self):
        mainloop = True
        while mainloop:
            ...
            # Redraw the background
            self.screen.fill(self.bg_color)

            for label in self.items:
                self.screen.blit(label, (100, 100))

            pygame.display.flip()

I am sure, you have already spotted the problem. All the texts are rendered to the same spot. We do want them centred in all aspects. This requires a little maths and the dimensions of the self.screen and of the rendered Font objects. As you may have already guessed, we will make use of the get_rect() method to obtain both width and height. The rest, I am sure, you will figure out for yourselves.
To store all these values in place, we shall use a list of lists. I found it good practice to store general values such as name and dimensions, because I very often noticed that I needed them later, anyway. In addition, adding the string of the Font object is very useful for debugging.

class GameMenu():
    def __init__(self, screen, items, bg_color=(0,0,0), font=None, font_size=30,
                    font_color=(255, 255, 255)):
        self.screen = screen
        self.scr_width = self.screen.get_rect().width
        self.scr_height = self.screen.get_rect().height

        ...

        self.items = []
        for index, item in enumerate(items):
            label = self.font.render(item, 1, font_color)

            width = label.get_rect().width
            height = label.get_rect().height

            posx = (self.scr_width / 2) - (width / 2)
            # t_h: total height of text block
            t_h = len(items) * height
            posy = (self.scr_height / 2) - (t_h / 2) + (index * height)

            self.items.append(item, [label, (width, height), (posx, posy)])

Now, before this works, we need to, of course, readjust our blit() method.

class GameMenu():
    ...
    def run(self):
        mainloop = True
        while mainloop:
            ...
            for name, label, (width, height), (posx, posy) in self.items:
                self.screen.blit(label, (posx, posy))

And now, we successfully brought our two menu options to the screen. In the next part of this tutorial series, I will implement how to recognise the mouse cursor and highlight the choice.

I hope this section was helpful. If you have any comments, suggestions or improvements, please feel free to comment below. I am always happy to hear constructive criticism.

——————————————————

Here’s the entire code:

#!/usr/bin/python

import pygame

pygame.init()


class GameMenu():
    def __init__(self, screen, items, bg_color=(0,0,0), font=None, font_size=30,
                    font_color=(255, 255, 255)):
        self.screen = screen
        self.scr_width = self.screen.get_rect().width
        self.scr_height = self.screen.get_rect().height

        self.bg_color = bg_color
        self.clock = pygame.time.Clock()

        self.items = items
        self.font = pygame.font.SysFont(font, font_size)
        self.font_color = font_color

        self.items = []
        for index, item in enumerate(items):
            label = self.font.render(item, 1, font_color)

            width = label.get_rect().width
            height = label.get_rect().height

            posx = (self.scr_width / 2) - (width / 2)
            # t_h: total height of text block
            t_h = len(items) * height
            posy = (self.scr_height / 2) - (t_h / 2) + (index * height)

            self.items.append([item, label, (width, height), (posx, posy)])

    def run(self):
        mainloop = True
        while mainloop:
            # Limit frame speed to 50 FPS
            self.clock.tick(50)

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    mainloop = False

            # Redraw the background
            self.screen.fill(self.bg_color)

            for name, label, (width, height), (posx, posy) in self.items:
                self.screen.blit(label, (posx, posy))

            pygame.display.flip()


if __name__ == "__main__":
    # Creating the screen
    screen = pygame.display.set_mode((640, 480), 0, 32)

    menu_items = ('Start', 'Quit')

    pygame.display.set_caption('Game Menu')
    gm = GameMenu(screen, menu_items)
    gm.run()

Writing a basic Vocabulary Trainer in 30 minutes with Python

(Original post from 31. May 2012)

Below is a vocabulary trainer for my effort of improving my French. It took me about 30 minutes to write and another 30 minutes with some good music on the headphones to write the relevant XML structure. I will probably try to expand this list as I read books and articles etc.

To start, you just need to call it from command line with the correct parameters. For example to test general vocabulary with English as the given language and French as the one to be tested you would need to type in (this is for linux):

./main.py general en fr

#!usr/bin/env python
# -*- coding: iso-8859-1 -*-

import random
import string
import sys

from lxml import etree

vocab = {'chem': 'chemvocab.xml',
         'general': 'general.xml'}

def load_xml2dict(fname, lang1, lang2):
    """Load xml data from file into dict depending on flag"""

    d = {}
    with open(fname) as xml:
        tree = etree.parse(xml)
    root = tree.getroot()

    for elem in root:
        l1 = []
        l2 = []
        for child in elem:
            if child.tag == lang1:
                l1.append(child.text)
            elif child.tag == lang2:
                l2.append(child.text)
        d[tuple(l1)] = l2
    return d


def main(fname, lang1, lang2):
    d = load_xml2dict(fname, lang1, lang2)

    while True:
        choice = random.choice(d.keys())
        print '[%s]: %s' % (lang1.upper(), ', '.join(choice))
        raw_input('Press Enter for the answer')
        print '[%s]: %s' % (lang2.upper(), ', '.join(d[choice]))

        end = raw_input('Do you want to continue Enter/N: ')
        if end == "N" or end == "n":
            break

if __name__ == "__main__":
    args = sys.argv
    main(vocab[args[1]], args[2], args[3])

The XML structure is fairly straightforward and is designed with possible expansion in mind and further filtering. It looks something like this:

<vocabulary>
    <word type="verb">
        <en>to agree</en>
        <de>jmdm./etw. zustimmen</de>
        <de>in etw. einwilligen</de>
        <fr>acquiescer à qn./qc.</fr>
    </word>
    <word type="adj">
        <en>horrible</en>
        <de>schrecklich</de>
        <de>fürchterlich</de>
        <fr>épouvantable</fr>
        <fr>terrible</fr>
    </word>
    <word type="noun">
        <en>murmur</en>
        <de gender="n">Murmeln</de>
        <fr gender="m">frémissement</fr>
    </word>
</vocabulary>

Note the type attribute for . It will hopefully allow me to filter by type of word. The gender attribute is only necessary for German and French and I only really put it in for French as there are cases where it is not clear, e.g. l’émission. If I really expand on this thing, I may use it to put the gender in square brackets after the word.

Of course, this is nothing to write home about, but maybe, I will expand it a little and create a GUI for it with some filtering options or abandon python altogether and put this on my website with some jquery. We shall see. I thought it was worth sharing.

Using Gstreamer with wxPython – Two MainLoops and the Law of Delimiter

(Original post from 19. May 2012)

Yesterday, while writing on the new version of my super duper honking RPG MusicPlayer, I came across a very interesting problem. Interesting, because it made me learn new things about Python in particular and programming in general.

Gstreamer uses the gobject.MainLoop() to communicate events via messages to its playbin instance. The example below is directly from the tutorial. Note the bus instance and the on_message method. This all runs via the gobject.MainLoop()

        # bus is used to report messages in playtime
        # Taken from tutorial, also the methods
        self.bus = self.player.get_bus()
        self.bus.add_signal_watch()
        self.bus.enable_sync_message_emission()
        self.bus.connect("message", self.on_message)

    def on_message(self, bus, message):
        """
        Taken from gstreamer tutorial.
        http://pygstdocs.berlios.de/pygst-tutorial
        
        Used in:
        - connects to bus in class.
        
        """
        
        t = message.type
        if t == gst.MESSAGE_EOS: # track is finished
            self.player.set_state(gst.STATE_NULL)
        elif t == gst.MESSAGE_ERROR:
            self.player.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug

So far, I had no problems not running the gobject.MainLoop, because the wx.App.MainLoop() did all the necessary things to keep my songs running. But at one point, I noticed a bug. When the song came naturally to an end, it would just stop and not play the next song. This was, of course, due to the fact, that the End of Song Message never reached the Playbin, because there was no system to relay said message

At this point, I had the uncomfortable situation of needing two MainLoops to run at the same time. Yes, I know, I should have learned to use pyGTK instead of sticking with wxPython, but I like wxPython and I was lazy. Also, that way I learned loads more funky stuff

Anyways, so how to solve that problem… Well, I used Python’s threading module to help me out here. Without threading, I was not able to start both MainLoops at the same time, because of Python’s procedural nature. Because I had no idea about threading, I had several mishaps on the way, but I came up with a minimal example such as shown at the end. I had to look for awhile, because I found most “simple” threading tutorials a bit over the top for my liking, but taking little bits and bobs from everywhere, I was able to come up with a solution.

Most of it really is just syntactic sugar. The key part about this, is the GobjectThread class. This class starts the MainLoop in a separate thread and after the wx.App.MainLoop is finished, the thread is stopped, too. Luckily, I only have to start and stop the gobject.MainLoop. I can imagine, these sort of things can very quickly become very confusing and cluttered. The only other important thing to note in this code is the close event. It is important to set the Gstreamer elements to gst.STATE_NULL, otherwise there are several Critical Errors thrown, when terminating the MainLoop

Also, as a little side note, while discussing this in the German Python Forum, I was told about Delimiter’s Law. I very often have things like self.player.player.get_state(), which according to Delimiter’s Law is a big “NoNo!”. Essentially, when disregarding Delimiter, too closely knit classes are a consequence. This can make it hard to debug or replace certain parts of the code, because the other classes take too much of the original architecture for granted. Knowing this now, I have a lot more work ahead of me 🙂

#!usr/bin/python

import os
import urllib
from threading import Thread

import wx
import pygst
pygst.require("0.10")
import gst
import gobject


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        
        ###############
        ## Threading ##
        ###############
        self.player = GstPlayer()
        
        # Close Event
        self.Bind(wx.EVT_CLOSE, self.on_close)

        self.__set_properties()
        self.__do_layout()

    def on_close(self, event):
        # If state not set to NULL, Critical Error occurs on gobject quit
        state = self.player.get_state()
        if state[1] == gst.STATE_PLAYING:
            self.player.set_state(gst.STATE_NULL)
        self.Destroy()

    def __set_properties(self):
        self.SetTitle("Test")

    def __do_layout(self):
        self.Layout()


class GstPlayer():
    def __init__(self):
        # This is almost straight out of the tutorial
        p = os.path.join(os.getcwd(), "tracks", "1. Shaent Blathanna.mp3")
        path = urllib.pathname2url(p)
        
        self.player = gst.element_factory_make("playbin2", "player")
        fakesink = gst.element_factory_make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        
        self.player.set_property("uri", "file:" + path)
        self.player.set_state(gst.STATE_PLAYING)
        
    def get_state(self):
        return self.player.get_state()
    
    def set_state(self, state):
        self.player.set_state(state)
        
class GobjectThread(Thread):
    def __init__(self):
        Thread.__init__(self)
        ##############################
        self.loop = gobject.MainLoop() # The mainloop to be separated from wx
        ##############################
    
    def run(self): # Called on Thread.start()
        self.loop.run()
        
    def quit(self):
        # Stops the gobject Mainloop
        self.loop.quit()

if __name__ == "__main__":
    gobject.threads_init()
    
    t = GobjectThread()
    t.start()
    
    # wxGlade's my Friend here
    # Just basics
    app = wx.PySimpleApp(0)
    wx.InitAllImageHandlers()
    frame = MyFrame(None, -1, "")
    app.SetTopWindow(frame)
    frame.Show()
    app.MainLoop()
    
    t.quit() #End the gobject.MainLoop()

wxPython: XRC and wx.Dialogs

This is a post that arose from a question I submitted to the wxpython user group. I was just not able to call a custom dialog that I created in an XRC file. As a side note, what I find most amazing is, that it is so easy to get in touch with the main guys of the project. In fact, Robin Dunn, the guy who started wxpython, answered my question… I wasn’t surprised when it worked out of the box 😉

Basically, I didn’t understand how to call a wxDialog from an XRC file. I was falling over myself left, right and centre trying to figure it out and at the end I was just plain confused. My answer came in the form of a 2-Stage Creation Process (<– really do read that link) that allows a "load on self" approach. This approach opens up a whole load of new options and makes using XRC really easy. Especially the part about the subclass, I like. Anyhow, I go off in tangents. Ok code. This is how it was done in the end:

class ConfigDialog(wx.Dialog):
def __init__(self, parent):
self.res = xrc.XmlResource("config_dialog.xrc")
pre = wx.PreDialog()
self.res.LoadOnDialog(pre, parent, "ConfigDlg")
self.PostCreate(pre)

As you can see, there is hardly any magic involved. You create the PreDialog (or PreFrame etc.). Load the Dialog and any other flags you may want to add to it, if the need arises. Once that is done you can use PostCreate and everything's peachy. I like it.

wxPython: Inserting an unknown control in XRC to use a custom widget

I’ve been quite busy lately with getting my application done and it has improved by leaps and bounds (I may go into the intricacies of lxml, but I think the tutorials are fairly comprehensive). Most of it was basic stuff that just needed to be pieced together, so there was no real reason to post any code. All of it is documented fairly well in the wxpython wiki and its many, many tutorials (I’m too lazy to mention them this time, just do a google search).

However, recently I came across an issue that annoyed me a little and I finally got round to addressing it. I wanted to have a bitmap button in toggle state (2-state). Unfortunately, wxpython does not allow bitmap buttons to have a toggle state 😦 The answer to this was the use of a PlateButton (Here’s a good introductory tutorial). But since this is a custom widget of the wxpython library, I am not able to use it in XRC, since it is based on wxWidgets.

So what to do? After asking many a lovely person on the interwebs, I finally found a satisfactory but not complete answer. The solution lay with the XmlResource.AttachUnknownControl function (Tutorial).

It allows you to enter a placeholder within the XRC file which can be later filled using the above function. The code I used goes as follows:

First the placeholder was inserted with the following code within the XRC file:


This was followed by the insertion of the following code into my python file:

class MyFrame(wx.Frame):
    def __init__(self, parent):
        self.res = xrc.XmlResource("my_xrc_file.xrc")
        self.frame = self.res.LoadFrame("MyFrame")

        imageFile = "/a/path/to/your/file"
        png = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()

        self.MyBtn = wx.lib.platebtn.PlateButton(self.frame, bmp=png,
                style=platebtn.PB_STYLE_TOGGLE|platebtn.PB_STYLE_DEFAULT)
        self.PlateBtn = self.res.AttachUnknownControl("MyBtn",
                                        self.MyBtn, self.frame)
 

There really is nothing exciting about the code in this example apart from the fact that you need to know that the possibility exists. Self.PlateBtn returns a bool so you need to bind your functions to self.MyBtn. Very useful to know this.

So far, I have not been able to affect the platebutton after I initialised it, that is a bit of a downside and unfortunately it looks entirely different to the rest of the buttons. I will probably end up changing all of the buttons to platebuttons, just for consistency. As a consequence, the XRC file is a bit useless, but hey, thats life.

RPG Music’N’SFX – How to filter using tags

I’ve been pondering over the same question for the best part of a day today. It is a very simple question, but difficult to answer, because it depends on habits.

My filtering system relies on tags for each file, i.e. every file has a potentially unlimited amount of tags you can add to it. But the big question I have is: how should I filter it?

There are two possibilities that I can see; Say you are searching for the tags “safe” and… Dunno “arabic”. Does that now mean that only the files that have “both” those tags should show up or should all files that have at least one of them show up…

Talking python code, for the former option, I would imagine that I would have a dictionary of the tags as keys and lists of filepaths. These would feed into a set as a tracklist pool a bit like the following:

tag_dict = {"safe" : ["an/example/file.mp3", "another/file.wav"],
           "travel" : ["an/example/file.mp3",
                       "unrelated/other/file.ogg"]}

# this would lead to playlist with both tags present looking
# as follows:

playlist = set(["an/example/file.mp3",
                "another/file.wav",
               "unrelated/other/file.ogg"])

In the latter case, I would probably just have a dictionary of the filepath as the key and its tags as a list in the values. Then whenever another tag is added or removed, I would iterate over the dictionary and check if all tags are present in the list using set.intersection (That is something I learnt from here). If that is the case, it will be added to the playlist. A bit like so:

track_dict = {"an/example/file.mp3" : ["safe", "travel"],
              "another/file.wav" : ["safe"],
              "unrelated/other/file.ogg" : ["travel"]}
 
playlist = []

choices = set(["safe", "travel"])
# i.e. only the first should be in the playlist

for key in track_dict:
    tags = set(track_dict[key])
    intersect = list(tags.intersection(choices))
    if intersect == list(choices):
        playlist.append(key)

print playlist

My tendency, user-wise, goes towards the latter as it seems more logical to me, but I have noticed on several separate occasions that this means, in fact, nothing. My brain tends not realise the most “common” modus operandi.

I am tempted to try and implement both and give the user the choice…