3
\$\begingroup\$

I instantiate GtkInfobars a lot in my GTK+ application in order to communicate with the user. There are various types of infobars, depending on the message. Basically, any infobar could be a combination of the 4 different infobar message types and 5 different icons (which are painted on the left side of the infobar).

Initially, I would call my custom infobar function like this:

infobar("Message. Woo.", type=gtk.MESSAGE_INFO, icon=gtk.STOCK_DIALOG_WARNING, timeout=5)

After a while I decided I wanted to simplify all the creation calls... so I modified my infobar function so that I could do this:

infobar("Message. Woo.", type=(1,3), timeout=3)

I feel like the second way is better and that it's worth the code obfuscation, but I suspect not everyone will agree with me.

def infobar(self, msg=None, type=(1,1), timeout=3, vbox=None):
    """Popup a new auto-hiding InfoBar."""

    # List of possible infobar message types
    msgtypes = [0,
                gtk.MESSAGE_INFO,      # 1
                gtk.MESSAGE_QUESTION,  # 2
                gtk.MESSAGE_WARNING,   # 3
                gtk.MESSAGE_ERROR]     # 4

    # List of possible images to show in infobar
    imgtypes = [gtk.STOCK_APPLY,            # 0
                gtk.STOCK_DIALOG_INFO,      # 1
                gtk.STOCK_DIALOG_QUESTION,  # 2
                gtk.STOCK_DIALOG_WARNING,   # 3
                gtk.STOCK_DIALOG_ERROR]     # 4

    ibar                    = gtk.InfoBar()
    ibar.set_message_type   (msgtypes[type[0]])
    if vbox:
        # If specific vbox requested: assume ibar for filemode, add cancel button
        ibar.add_button     (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        ibar.connect        ('response', self.cleanup_filemode)
    else:
        # If no specific vbox requested: do normal ibar at the top of message area
        vbox = self.vbox_ibar
        ibar.add_button     (gtk.STOCK_OK, gtk.RESPONSE_OK)
        ibar.connect        ('response', lambda *args: ibar.destroy())
    vbox.pack_end           (ibar, False, False)
    content                 = ibar.get_content_area()
    img                     = gtk.Image()
    img.set_from_stock      (imgtypes[type[1]], gtk.ICON_SIZE_LARGE_TOOLBAR)
    content.pack_start      (img, False, False)
    img.show                ()
    if msg:
        # If msg was specified, show it, but change the default color
        label               = gtk.Label()
        label.set_markup    ("<span foreground='#2E2E2E'>{}</span>".format(msg))
        content.pack_start  (label, False, False)
        label.show          ()
    # FIXME: Why doesn't Esc trigger this close signal?
    ibar.connect            ('close', lambda *args: ibar.destroy())
    ibar.show()
    if timeout:
        glib.timeout_add_seconds(timeout, ibar.destroy)
    return ibar
\$\endgroup\$
4
  • \$\begingroup\$ What do you think you've saved by doing this? I.e. to your mind, what's the purpose of being concise? \$\endgroup\$ Feb 27, 2012 at 15:33
  • \$\begingroup\$ @WinstonEwert: Hello again. Well, I call this function a lot.. with multiline messages containing custom .format() substitution and all combinations of the 5 icons, 4 message types, and various timeouts. It just makes the code much simpler-looking, not to mention makes it easier to stick to Pythonic rules of line-width. But seeing the reaction from you and the one answerer... \$\endgroup\$
    – rsaw
    Feb 27, 2012 at 20:25
  • \$\begingroup\$ @ryan, I think you are seeing a valid problem but I'm not sure you've solved it in the best way. Can you share more example of calling the function that show how its giving you trouble? \$\endgroup\$ Feb 27, 2012 at 21:47
  • \$\begingroup\$ @Winston: You could search for self.infobar on this page to see all cases where it's called, but I'm gonna change it. It's not really giving me trouble--I mean.. I guess I saw a problem (and came up with a crazy "solution") where there wasn't really a problem. \$\endgroup\$
    – rsaw
    Feb 27, 2012 at 22:42

2 Answers 2

4
\$\begingroup\$

I'd move all of those message into an external resource file something like:

<message id="file.open.fail" type="warning" icon="error" delay="5">
    <b>Error. Could not open file:<i><tt><small>{filename}</small></tt></i></b>
</message>

Then I'd simply pop the message using:

self.infobar('file.open.fail', filename = filename)

That should cleanup the calls to infobar, and make it easier to do things like provide strings for a different language.

EDIT:

Quick hack of an implementation

from xml.etree.ElementTree import XML, tostring

# of course, you'd want to load this from an external file
message_file = XML("""
<messages>
    <message id="file.open.fail" type="warning" icon="error" delay="5">
        <b>Error. Could not open file:<i><tt><small>{filename}</small></tt></i></b>
    </message>
    <message id="file.open.good" type="warning" icon="error" delay="5">
        <b>File opened!</b>
    </message>
</messages>
""")

messages = {}
for message in message_file.findall("message"):
    messages[message.attrib['id']] = message

def message(id, **kwargs):
    message_detail = messages[id]
    text = ''.join( tostring(node) for node in message_detail)
    return text.strip().format(**kwargs)

print message('file.open.fail', filename = 'file.txt')
print message('file.open.good', filename = 'file.txt')

Doesn't try to handle type/icon/delay. You'll probably also want to consider how you want to do whitespace.

\$\endgroup\$
10
  • \$\begingroup\$ Ohhh I like it. At some point I had that idea -- to move all the messages into a single place, but scrapped it because of (1) inability to envision an elegant way to deal with the variable stuff like filenames and (2) the feeling that there were just too many messages. Hmmmm.... External file you say? XML too? Hmm. Innnteresting. Never would have thought of that. Already just went through a intro-tutorial on xml.etree.ElementTree (tho couldn't get it to import--weird), but before I reeally start trying to figure out implementation: why is xml the best solution for storage of this stuff? \$\endgroup\$
    – rsaw
    Feb 28, 2012 at 4:09
  • \$\begingroup\$ @ryan, I went for xml because the message content was already xml. \$\endgroup\$ Feb 28, 2012 at 4:24
  • \$\begingroup\$ Agh. So I've read through the ElementTree page in the docs, have gone through a few minimal tutorials, and have even gone through a few questions here and on stackoverflow (even one where you answered), but I'm having trouble doing the most basic thing--finding a message by id and returning the text. Can you point me to any resources Winston? \$\endgroup\$
    – rsaw
    Feb 28, 2012 at 14:19
  • \$\begingroup\$ @ryan, your best bet is probably to load all of the messages on startup and then store them in a dictionary by the id. To load a tag by the id directly from the xml, you'd need something like XPath, but thats probably not in your base python install. \$\endgroup\$ Feb 28, 2012 at 17:26
  • \$\begingroup\$ Hmm. Well I like the idea, but honestly this just leaves me back at square one -- I thought of this weeks ago but dismissed it because I couldn't see how to implement it. And I still don't. Oh well. In any case, thanks again for your time Winston. \$\endgroup\$
    – rsaw
    Feb 28, 2012 at 20:36
2
\$\begingroup\$

It's less readable. You made two surprising changes:

  • switching from a constant to an integer: this really impairs readability: you cannot know what it means without looking it up. You only save a few characters, not even a line or so.
  • switching from two parameters to a tuple: this makes little sense, since those are really two parameters. At least come up with a different name if you want to group them.

Did you consider implementing an infobar builder? If some common infobars are often built, you could create a wrapper to infobar, eg. infobar_info, which would accept the message and an enum saying which infobox you want to show. You can use "semantic names", eg. 'warn_user' will use a given timeout, message, icon and type that you always use to warn users, which will improve the consistency of the dialogs in your application.

\$\endgroup\$
8
  • \$\begingroup\$ I'm not familiar with what you mean by an infobar builder, but I think I get the idea. The problem is that there are so many various message, icon, type combinations. I'm slightly OCD with my coding so I've also tweaked all the timeouts--past a certain point, the timeouts are different depending on how long the message is. That said, I really appreciate your feedback and suspect a bunch of search & replace is in my future. \$\endgroup\$
    – rsaw
    Feb 27, 2012 at 20:30
  • \$\begingroup\$ I'm not seeing the switch from two parameters to a dictionary \$\endgroup\$ Feb 28, 2012 at 1:19
  • \$\begingroup\$ tuple, sorry. I prefer your answer anyway. \$\endgroup\$ Feb 28, 2012 at 7:48
  • \$\begingroup\$ Bit of nitpick but since you've changed dictionary to tuple, the bit about reinventing named parameters doesn't make a lot of sense. \$\endgroup\$ Feb 28, 2012 at 21:30
  • \$\begingroup\$ @Cygal: Due to your comments and inspiration from Winston I've come up with a different way. I'd be curious to hear where you think it stands in terms of Pythonic style. \$\endgroup\$
    – rsaw
    Feb 28, 2012 at 22:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.