3

I am trying to code an application that consists of various windows (e.g., generic message dialog, login dialog, main interface, etc.) and am having trouble getting the gtk.main_quit function to be called: either I get a complaint about the call being outside the main loop, or the function doesn't get called at all.

I am a newbie to both Python and GTK+, but my best guess as to how to get this to work is to have a "root" window, which is just a placeholder that is never seen, but controls the application's GTK+ loop. My code, so far, is as follows:

import pygtk
pygtk.require("2.0")
import gtk

class App(gtk.Window):
  _exitStatus = 0

  # Generic message box
  def msg(self, title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
    # Must always have a button
    if buttons == gtk.BUTTONS_NONE:
      buttons = gtk.BUTTONS_OK

    dialog = gtk.MessageDialog(None, 0, type, buttons, title)
    dialog.set_title(title)
    dialog.set_geometry_hints(min_width = 300)
    dialog.set_resizable(False)
    dialog.set_deletable(False)
    dialog.set_position(gtk.WIN_POS_CENTER)
    dialog.set_modal(True)
    dialog.format_secondary_text(text)

    response = dialog.run()
    dialog.destroy()

    return response

  def nuke(self, widget, data):
    gtk.main_quit()
    exit(self._exitStatus)

  def __init__(self):
    super(App, self).__init__()
    self.connect('destroy', self.nuke)

    try:
      raise Exception()
    except:
      self.msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
      self._exitStatus = 1
      self.destroy()

    if self.msg('OK', 'Everything worked fine') == gtk.RESPONSE_OK:
      self.destroy()

# Let's go!
App()
gtk.main()

The nuke function never gets called, despite the explicit calls to destroy.


DIFF On @DonQuestion's advice:

- self.destroy()
+ self.emit('destroy')

- App()
+ app = App()

This didn't solve the problem...


UPDATE Accepted @jku's answer, but also see my own answer for extra information...

Xophmeister
  • 8,884
  • 4
  • 44
  • 87
  • This has little to do with you rquestion but the idea of an invisible window just to get a main loop sounds wrong. I don't see why App needs to be a gtk.Window – Jussi Kukkonen Dec 12 '13 at 15:15
  • ...Did I mention that I don't really know what I'm doing?! I can't work out how you do any kind of control flow outside of watching for signals. I think my approach, in general, is fundamentally wrong... – Xophmeister Dec 12 '13 at 15:33

2 Answers2

2

First, there is a bit of a test problem with the code: You call Gtk.main_quit() from the App initialization: this happens before main loop is even running so signals probably won't work.

Second, you'll probably get a warning on destroy(): 'destroy' handler only takes two arguments (self plus one) but yours has three...

Also with regards to your comment about control flow: You don't need a Window to get signals as they're a GObject feature. And for your testing needs you could write a App.test_except() function and use glib.idle_add (self.test_except) in the object initialization -- this way test_except() is called when main loop is running.

Jussi Kukkonen
  • 13,857
  • 1
  • 37
  • 54
  • Thanks, @jku: So `gtk.main_quit` can't be called at initialisation? That makes sense, insofar as the loop hasn't even been spun up until afterwards. However, what if your application encounters an exception at initialisation time?... Anyway, I think your point about the ordering is probably what was causing the problem, so I have accepted yours :) However, I've also provided my own, with some extra findings... – Xophmeister Dec 12 '13 at 20:19
  • 1
    @Xophmeister: If your application encounters an exception *WHILE* initializing the UI, chances are pretty high, that your UI would neither show nor accept anything. I would highly advice to separate the UI from the business logic of your application. You improve testability and portability. E.g. it would become easier to support other UI concepts like web-services. In this regard, your class might be better called UI rather then App – Don Question Dec 13 '13 at 16:40
1

I think @jku's answer identifies my key error, so I have marked it accepted, but while playing around, I found that the MessageDialog does not need to run within the GTK+ loop. I don't know if this is as designed, but it works! So, I broke my generic message dialog out into its own function and then kept the main app altogether in a class of its own, which respects the main loop as I was expecting:

import pygtk
pygtk.require("2.0")
import gtk

def msg(title, text, type = gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK):
  # Only allowed OK, Close, Cancel, Yes/No and OK/Cancel buttons
  # Otherwise, default to just OK
  if buttons not in [gtk.BUTTONS_OK, gtk.BUTTONS_CLOSE, gtk.BUTTONS_CANCEL, gtk.BUTTONS_YES_NO, gtk.BUTTONS_OK_CANCEL]:
    buttons = gtk.BUTTONS_OK

  dialog = gtk.MessageDialog(None, 0, type, buttons, title)
  dialog.set_title(title)
  dialog.set_geometry_hints(min_width = 300)
  dialog.set_resizable(False)
  dialog.set_deletable(False)
  dialog.set_position(gtk.WIN_POS_CENTER)
  dialog.set_modal(True)
  dialog.format_secondary_text(text)

  response = dialog.run()
  dialog.destroy()
  return response

class App:
 def __init__(self):
   # Build UI
   # Connect signals
   # Show whatever

 def appQuit(self, widget):
   gtk.main_quit()

 def signalHandler(self, widget, data = None):
   # Handle signal
   # We can call msg here, when the main loop is running

# Load some resource
# We can call msg here, despite not having invoked the main loop
try:
  # Load resource
except:
  msg('OMFG!', 'WTF just happened!?', gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE)
  exit(1)

# n.b., Calls to msg work even without the following code
App()
gtk.main()
exit(0)
Xophmeister
  • 8,884
  • 4
  • 44
  • 87