~tony-badwolf/quickly/threaded_application

« back to all changes in this revision

Viewing changes to data/templates/threaded-ubuntu-application/project_root/bin/project_name

  • Committer: tony
  • Date: 2010-09-16 19:55:40 UTC
  • Revision ID: tony@tony-laptop-20100916195540-i9lgho9h5m9bjl8b
Threads are useful in gui applications. Without them slow processes freeze the GUI. This uses the node concept from erlang (quickly uses couchdb, couchdb uses erlang, I wondered why). It is implemented using python's Queue module. There is a bug catcher for exceptions in other threads. Contains example code to drive a progress bar, disable buttons when busy and show exception in threaded model. Demo code is clearly marked and can be removed to make a clean project. All threading code is "hidden" in helpers.py. Implemented as new template based on ubuntu_application.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
 
3
### BEGIN LICENSE
 
4
# This file is in the public domain
 
5
### END LICENSE
 
6
 
 
7
import sys
 
8
import os
 
9
import gtk
 
10
import time
 
11
import gobject
 
12
# necessary for other threads to talk to gtk main loop
 
13
gtk.gdk.threads_init()
 
14
 
 
15
import gettext
 
16
from gettext import gettext as _
 
17
gettext.textdomain('project_name')
 
18
 
 
19
# optional Launchpad integration
 
20
# this shouldn't crash if not found as it is simply used for bug reporting
 
21
try:
 
22
    import LaunchpadIntegration
 
23
    launchpad_available = True
 
24
except:
 
25
    launchpad_available = False
 
26
 
 
27
# Add project root directory (enable symlink, and trunk execution).
 
28
PROJECT_ROOT_DIRECTORY = os.path.abspath(
 
29
    os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
 
30
 
 
31
if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'python_name'))
 
32
    and PROJECT_ROOT_DIRECTORY not in sys.path):
 
33
    sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
 
34
    os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY) # for subprocesses
 
35
 
 
36
from python_name import (
 
37
    Aboutcamel_case_nameDialog, Preferencescamel_case_nameDialog, models)
 
38
from python_name.helpers import get_builder, Taskqueue
 
39
#Try adding AppIndicator. Will work after "qucikly add indicator"
 
40
try:
 
41
    from python_name import indicator
 
42
except:
 
43
    indicator = False
 
44
 
 
45
 
 
46
class camel_case_nameWindow(gtk.Window):
 
47
    __gtype_name__ = "camel_case_nameWindow"
 
48
    
 
49
    # To construct a new instance of this method, the following notable 
 
50
    # methods are called in this order:
 
51
    # __new__(cls)
 
52
    # __init__(self)
 
53
    # finish_initializing(self, builder)
 
54
    # __init__(self)
 
55
    #
 
56
    # For this reason, it's recommended you leave __init__ empty and put
 
57
    # your inialization code in finish_intializing
 
58
    
 
59
    def __new__(cls):
 
60
        """Special static method that's automatically called by Python when 
 
61
        constructing a new instance of this class.
 
62
        
 
63
        Returns a fully instantiated camel_case_nameWindow object.
 
64
        """
 
65
        builder = get_builder('camel_case_nameWindow')
 
66
        new_object = builder.get_object("python_name_window")
 
67
        new_object.finish_initializing(builder)
 
68
        return new_object
 
69
 
 
70
    def finish_initializing(self, builder):
 
71
        """Called while initializing this instance in __new__
 
72
 
 
73
        finish_initalizing should be called after parsing the UI definition
 
74
        and creating a camel_case_nameWindow object with it in order to finish
 
75
        initializing the start of the new camel_case_nameWindow instance.
 
76
        
 
77
        Put your initilization code in here and leave __init__ undefined.
 
78
        """
 
79
        # Get a reference to the builder and set up the signals.
 
80
        self.builder = builder
 
81
        self.builder.connect_signals(self)
 
82
 
 
83
        global launchpad_available
 
84
        if launchpad_available:
 
85
            # see https://wiki.ubuntu.com/UbuntuDevelopment/Internationalisation/Coding for more information
 
86
            # about LaunchpadIntegration
 
87
            helpmenu = self.builder.get_object('helpMenu')
 
88
            if helpmenu:
 
89
                LaunchpadIntegration.set_sourcepackagename('project_name')
 
90
                LaunchpadIntegration.add_items(helpmenu, 0, False, True)
 
91
            else:
 
92
                launchpad_available = False
 
93
            
 
94
        
 
95
        #AppIndicator support
 
96
        #see http://owaislone.org/quickly-add-indicator/ 
 
97
        # use 'quickly add indicator' to get started
 
98
        # self is passed so methods of this class can be called from indicator.py
 
99
        # Comment to disable appindicator
 
100
        if indicator:
 
101
            self.indicator = indicator.new_application_indicator(self)
 
102
        # self.indicator is an appindicator instance.
 
103
        # learn more about it here http://LINK-to-AppIndicator-Docs
 
104
        
 
105
        
 
106
        # Uncomment the following code to read in preferences at start up.
 
107
        #dlg = Preferencescamel_case_nameDialog.Preferencescamel_case_nameDialog()
 
108
        #self.preferences = dlg.get_preferences()
 
109
 
 
110
        # timeout value (50) determines the progressbar pulse speed
 
111
        gobject.timeout_add(50, self.heartbeat)
 
112
        self.pulse = True
 
113
 
 
114
        # Code for other initialization actions should be added here.
 
115
 
 
116
    def about(self, widget, data=None):
 
117
        """Display the about box for project_name."""
 
118
        about = Aboutcamel_case_nameDialog.Aboutcamel_case_nameDialog()
 
119
        response = about.run()
 
120
        about.destroy()
 
121
 
 
122
    def preferences(self, widget, data=None):
 
123
        """Display the preferences window for project_name."""
 
124
        prefs = Preferencescamel_case_nameDialog.Preferencescamel_case_nameDialog()
 
125
        response = prefs.run()
 
126
        if response == gtk.RESPONSE_OK:
 
127
            # Make any updates based on changed preferences here.
 
128
            pass
 
129
        prefs.destroy()
 
130
 
 
131
    def quit(self, widget, data=None):
 
132
        """Signal handler for closing the camel_case_nameWindow."""
 
133
        self.destroy()
 
134
 
 
135
    def on_destroy(self, widget, data=None):
 
136
        """Called when the camel_case_nameWindow is closed."""
 
137
        # Clean up code for saving application state should be added here.
 
138
        gtk.main_quit()
 
139
 
 
140
    def inbox(self, sender, message, data=None):
 
141
        logging.debug('inbox: %s: %s' % (sender, message))
 
142
        # warning this method runs outside of the main loop
 
143
        # don't add code here
 
144
        # hasattr and getattr read methods in main thread
 
145
        # so don't change methods while the program runs
 
146
        # changing methods is deep magic anyway
 
147
        
 
148
        attr_name = 'on_%s_%s' % (sender, message)
 
149
        if hasattr(self, attr_name):
 
150
            handler = getattr(self, attr_name)
 
151
            gobject.idle_add(handler, data)
 
152
        else:
 
153
            logging.error('no attribute: %s' % attr_name)
 
154
            pass
 
155
 
 
156
    def heartbeat(self):
 
157
        if self.pulse:
 
158
            self.builder.get_object("progressbar1").pulse()
 
159
        # add other timed code here
 
160
        return True
 
161
 
 
162
# begin demo code ######################################################
 
163
# begin button handlers ################################################
 
164
    def on_btn_error_clicked(self, widget, data=None):
 
165
        logging.debug('on_btn_error_clicked')
 
166
        widget.set_sensitive(False)
 
167
        taskqueue.add_task(models.divide, 'abacus', self.inbox, [2, 0])
 
168
        
 
169
    def on_btn_fraction_clicked(self, widget, data=None):
 
170
        logging.debug('on_btn_fraction_clicked')
 
171
        widget.set_sensitive(False)
 
172
        status = self.builder.get_object('status')
 
173
        status.set_text('')
 
174
        progressbar = self.builder.get_object('progressbar1')
 
175
        progressbar.set_visible(True)
 
176
        taskqueue.add_task(models.show_fraction, 'percent', self.inbox)
 
177
        
 
178
    def on_btn_hello_clicked(self, widget, data=None):
 
179
        logging.debug('on_btn_hello_clicked')
 
180
        widget.set_sensitive(False)
 
181
        self.pulse = True
 
182
        status = self.builder.get_object('status')
 
183
        status.set_text('')
 
184
        progressbar = self.builder.get_object('progressbar1')
 
185
        progressbar.set_visible(True)
 
186
        taskqueue.add_task(models.greet, 'hello_world', self.inbox)
 
187
        
 
188
# end button handlers ##################################################
 
189
# begin model event handlers ###########################################
 
190
    def on_abacus_finished(self, data=None):
 
191
        logging.debug('on_abacus_finished')
 
192
        widget = self.builder.get_object('btn_error')
 
193
        widget.set_sensitive(True)
 
194
        # do nothing with data because we intend to force an exception
 
195
        # in the "abacus" model
 
196
 
 
197
    def on_abacus_error(self, response=None):
 
198
        logging.debug('on_abacus_error')
 
199
        imageURI = 'file://' + '/usr/share/doc/python-notify/examples/applet-critical.png'
 
200
        import pynotify
 
201
        pynotify.init('someName')
 
202
        n = pynotify.Notification("Don't let your end user see this",
 
203
        response['reason'], imageURI)
 
204
        n.show()
 
205
        self.on_abacus_finished()        
 
206
        
 
207
    def on_hello_world_char(self, data=None):
 
208
        logging.debug('on_hello_world_char')
 
209
        status = self.builder.get_object('status')
 
210
        old_text = status.get_text()
 
211
        new_text = old_text + data
 
212
        status.set_text(new_text)
 
213
        
 
214
    def on_hello_world_finished(self, data=None):
 
215
        logging.debug('on_hello_world_finished')
 
216
        progressbar = self.builder.get_object('progressbar1')
 
217
        progressbar.set_visible(False)
 
218
        widget = self.builder.get_object('btn_hello')
 
219
        widget.set_sensitive(True)
 
220
 
 
221
    def on_percent_char(self, data=None):
 
222
        logging.debug('on_percent_char')
 
223
        status = self.builder.get_object('status')
 
224
        old_text = status.get_text()
 
225
        new_text = old_text + data
 
226
        status.set_text(new_text)
 
227
 
 
228
    def on_percent_fraction(self, data=None):
 
229
        logging.debug('on_percent_fraction')
 
230
        self.pulse = False
 
231
        self.builder.get_object("progressbar1").set_fraction(data)
 
232
 
 
233
    def on_percent_finished(self, data=None):
 
234
        logging.debug('on_percent_finished')
 
235
        progressbar = self.builder.get_object('progressbar1')
 
236
        progressbar.set_visible(False)
 
237
        widget = self.builder.get_object('btn_fraction')
 
238
        widget.set_sensitive(True)
 
239
 
 
240
# end model event handlers #############################################
 
241
# end demo code ########################################################
 
242
 
 
243
if __name__ == "__main__":
 
244
    # Support for command line options.
 
245
    import logging
 
246
    import optparse
 
247
    parser = optparse.OptionParser(version="%prog %ver")
 
248
    parser.add_option(
 
249
        "-v", "--verbose", action="store_true", dest="verbose",
 
250
        help=_("Show debug messages"))
 
251
    (options, args) = parser.parse_args()
 
252
 
 
253
    # Set the logging level to show debug messages.
 
254
    if options.verbose:
 
255
        logging.basicConfig(level=logging.DEBUG)
 
256
        logging.debug('logging enabled')
 
257
 
 
258
    # Run the application.
 
259
    taskqueue = Taskqueue()
 
260
    window = camel_case_nameWindow()
 
261
    window.show()
 
262
    gtk.main()