Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/studio/static/doc/myosa/ch009_inherit-from-sugaractivityactivity.xhtml
blob: 94c0a022d2a465954249ce6e227db0f46037a70f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><body><h1>Inherit From sugar.activity.Activity
</h1>
<h2>Object Oriented Python
  <br/></h2>
<p>Python supports two styles of programming:<strong> procedural</strong> and <strong>object oriented</strong>. Procedural programming is when you have some input data, do some processing on it, and produce an output. If you want to calculate all the prime numbers under a hundred or convert a Word document into a plain text file you'll probably use the procedural style to do that.
</p>
<p class="western">Object oriented programs are built up from units called <strong>objects</strong>. An object is described as a collection of fields or attributes containing data along with methods for doing things with that data. In addition to doing work and storing data objects can send messages to one another.
</p>
<p>Consider a word processing program. It doesn't have just one input, some process, and one output. It can receive input from the keyboard, from the mouse buttons, from the mouse traveling over something, from the clipboard, etc. It can send output to the screen, to a file, to a printer, to the clipboard, etc. A word processor can edit several documents at the same time too. Any program with a GUI is a natural fit for the object oriented style of programming.
</p>
<p>Objects are described by <em>classes</em>. When you create an object you are creating an <em>instance</em> of a class.
</p>
<p>There's one other thing that a class can do, which is to<strong> inherit</strong> methods and attributes from another class. When you define a class you can say it <strong>extends</strong> some class, and by doing that in effect your class has the functionality of the other class plus its own functionality. The extended class becomes its parent.
  <br/></p>
<p>All Sugar Activities extend a Python class called <strong>sugar.activity.Activity</strong>. This class provides methods that all Activities need. In addition to that, there are methods that you can override in your own class that the parent class will call when it needs to. For the beginning Activity writer three methods are important:
</p>
<p><em>__init__()</em>
</p>
<p>This is called when your Activity is started up. This is where you will set up the user interface for your Activity, including toolbars.
</p>
<p><em>read_file(self, file_path)</em>
</p>
<p>This is called when you resume an Activity from a Journal entry. It is called after the <em>__init__()</em> method is called. The file_path parameter contains the name of a temporary file that is a copy of the file in the Journal entry. The file is deleted as soon as this method finishes, but because Sugar runs on Linux if you open the file for reading your program can continue to read it even after it is deleted and it the file will not actually go away until you close it.
</p>
<p><em>write_file(self, file_path)</em>
</p>
<p>This is called when the Activity updates the Journal entry. Just like with <em>read_file()</em> your Activity does not work with the Journal directly. Instead it opens the file named in file_path for output and writes to it. That file in turn is copied to the Journal entry.
</p>
<p>There are three things that can cause <em>write_file()</em> to be executed:
</p>
<ul><li>Your Activity closes.</li>
  <li>Someone presses the <strong>Keep</strong> button in the Activity toolbar.</li>
  <li>Your Activity ceases to be the active Activity, or someone moves from the Activity View to some other View.</li>
</ul><p>In addition to updating the file in the Journal entry the <em>read_file()</em> and <em>write_file()</em> methods are used to read and update the metadata in the Journal entry.
</p>
<p> When we convert our standalone Python program to an Activity we'll take out much of the code we wrote and replace it with code inherited from the sugar.activity.Activity&#160; class.
</p>
<h2>Extending The Activity Class
</h2>
<p>Here's a version of our program that extends Activity.&#160; You'll find it in the Git repository in the directory <strong>Inherit_From_sugar.activity.Activity</strong> under the name <strong>ReadEtextsActivity.py</strong>:
</p>
<pre>import sys
import os
import zipfile
import pygtk
import gtk
import pango
from sugar.activity import activity
from sugar.graphics import style

page=0
PAGE_SIZE = 45

class ReadEtextsActivity(activity.Activity):
    def __init__(self, handle):
        "The entry point to the Activity"
        global page
        activity.Activity.__init__(self, handle)

        toolbox = activity.ActivityToolbox(self)
        activity_toolbar = toolbox.get_activity_toolbar()
        activity_toolbar.keep.props.visible = False
        activity_toolbar.share.props.visible = False
        self.set_toolbox(toolbox)

        toolbox.show()
        self.scrolled_window = gtk.ScrolledWindow()
        self.scrolled_window.set_policy(gtk.POLICY_NEVER,
            gtk.POLICY_AUTOMATIC)
        self.scrolled_window.props.shadow_type = \
            gtk.SHADOW_NONE

        self.textview = gtk.TextView()
        self.textview.set_editable(False)
        self.textview.set_cursor_visible(False)
        self.textview.set_left_margin(50)
        self.textview.connect("key_press_event",
            self.keypress_cb)

        self.scrolled_window.add(self.textview)
        self.set_canvas(self.scrolled_window)
        self.textview.show()
        self.scrolled_window.show()
        page = 0
        self.textview.grab_focus()
        self.font_desc = pango.FontDescription("sans %d" %
            style.zoom(10))
        self.textview.modify_font(self.font_desc)

    def keypress_cb(self, widget, event):
        "Respond when the user presses one of the arrow keys"
        keyname = gtk.gdk.keyval_name(event.keyval)
        print keyname
        if keyname == 'plus':
            self.font_increase()
            return True
        if keyname == 'minus':
            self.font_decrease()
            return True
        if keyname == 'Page_Up' :
            self.page_previous()
            return True
        if keyname == 'Page_Down':
            self.page_next()
            return True
        if keyname == 'Up' or keyname == 'KP_Up' \
                or keyname == 'KP_Left':
            self.scroll_up()
            return True
        if keyname == 'Down' or keyname == 'KP_Down' \
                or keyname == 'KP_Right':
            self.scroll_down()
            return True
        return False

    def page_previous(self):
        global page
        page=page-1
        if page &lt; 0: page=0
        self.show_page(page)
        v_adjustment = \
            self.scrolled_window.get_vadjustment()
        v_adjustment.value = v_adjustment.upper -\
            v_adjustment.page_size

    def page_next(self):
        global page
        page=page+1
        if page &gt;= len(self.page_index): page=0
        self.show_page(page)
        v_adjustment = \
            self.scrolled_window.get_vadjustment()
        v_adjustment.value = v_adjustment.lower

    def font_decrease(self):
        font_size = self.font_desc.get_size() / 1024
        font_size = font_size - 1
        if font_size &lt; 1:
            font_size = 1
        self.font_desc.set_size(font_size * 1024)
        self.textview.modify_font(self.font_desc)

    def font_increase(self):
        font_size = self.font_desc.get_size() / 1024
        font_size = font_size + 1
        self.font_desc.set_size(font_size * 1024)
        self.textview.modify_font(self.font_desc)

    def scroll_down(self):
        v_adjustment = \
            self.scrolled_window.get_vadjustment()
        if v_adjustment.value == v_adjustment.upper - \
                v_adjustment.page_size:
            self.page_next()
            return
        if v_adjustment.value &lt; v_adjustment.upper -\
            v_adjustment.page_size:
            new_value = v_adjustment.value +\
                v_adjustment.step_increment
            if new_value &gt; v_adjustment.upper -\
                v_adjustment.page_size:
                new_value = v_adjustment.upper -\
                    v_adjustment.page_size
            v_adjustment.value = new_value

    def scroll_up(self):
        v_adjustment = \
            self.scrolled_window.get_vadjustment()
        if v_adjustment.value == v_adjustment.lower:
            self.page_previous()
            return
        if v_adjustment.value &gt; v_adjustment.lower:
            new_value = v_adjustment.value - \
                v_adjustment.step_increment
            if new_value &lt; v_adjustment.lower:
                new_value = v_adjustment.lower
            v_adjustment.value = new_value

    def show_page(self, page_number):
        global PAGE_SIZE, current_word
        position = self.page_index[page_number]
        self.etext_file.seek(position)
        linecount = 0
        label_text = '\n\n\n'
        textbuffer = self.textview.get_buffer()
        while linecount &lt; PAGE_SIZE:
            line = self.etext_file.readline()
            label_text = label_text + unicode(line,
                'iso-8859-1')
            linecount = linecount + 1
        label_text = label_text + '\n\n\n'
        textbuffer.set_text(label_text)
        self.textview.set_buffer(textbuffer)

    def save_extracted_file(self, zipfile, filename):
        "Extract the file to a temp directory for viewing"
        filebytes = zipfile.read(filename)
        outfn = self.make_new_filename(filename)
        if (outfn == ''):
            return False
        f = open(os.path.join(self.get_activity_root(),
            'instance',  outfn),  'w')
        try:
            f.write(filebytes)
        finally:
            f.close

    def read_file(self, filename):
        "Read the Etext file"
        global PAGE_SIZE

        if zipfile.is_zipfile(filename):
            self.zf = zipfile.ZipFile(filename, 'r')
            self.book_files = self.zf.namelist()
            self.save_extracted_file(self.zf,
                self.book_files[0])
            currentFileName = os.path.join(
                self.get_activity_root(),
                'instance', self.book_files[0])
        else:
            currentFileName = filename

        self.etext_file = open(currentFileName,"r")
        self.page_index = [ 0 ]
        linecount = 0
        while self.etext_file:
            line = self.etext_file.readline()
            if not line:
                break
            linecount = linecount + 1
            if linecount &gt;= PAGE_SIZE:
                position = self.etext_file.tell()
                self.page_index.append(position)
                linecount = 0
        if filename.endswith(".zip"):
            os.remove(currentFileName)
        self.show_page(0)

    def make_new_filename(self, filename):
        partition_tuple = filename.rpartition('/')
        return partition_tuple[2]


</pre>
<p>This program has some significant differences from the standalone version.&#160; First, note that this line:
</p>
<pre>#! /usr/bin/env python
</pre>
<p>has been removed.&#160; We are no longer running the program directly from the Python interpreter.&#160; Now Sugar is running it as an Activity.&#160; Notice that much (but not all) of what was in the main() method has been moved to the <em>__init__()</em> method and the <em>main()</em> method has been removed.
</p>
<p>Notice too that the <em>class</em> statement has changed:
</p>
<p>
</p>
<pre>class ReadEtextsActivity(activity.Activity)
</pre>
<p>This statement now tells us that class ReadEtextsActivity extends the class <strong>sugar.activity.Activity</strong>.&#160;&#160; As a result it inherits the code that is in that class.&#160; Therefore we no longer need a GTK main loop, or to define a window.&#160; The code in this class we extend will do that for us.
</p>
<p> While we gain much from this inheritance, we lose something too: a title bar for the main window.&#160; In a graphical operating environment a piece of software called a <em>window manager</em> is responsible for putting borders on windows, making them resizeable, reducing them to icons, maximizing them, etc.&#160; Sugar uses a window manager named Matchbox which makes each window fill the whole screen and puts no border, title bar, or any other window decorations on the windows.&#160;&#160; As a result of that we can't close our application by clicking on the "X" in the title bar as before.&#160; To make up for this we need to have a toolbar that contains a Close button.&#160; Thus every Activity has an Activity toolbar that contains some standard controls and buttons.&#160; If you look at the code you'll see I'm hiding a couple of controls which we have no use for yet.
</p>
<p>The <em>read_file()</em> method is no longer called from the main() method and doesn't seem to be called from anywhere in the program.&#160; Of course it does get called, by some of the Activity code we inherited from our new parent class.&#160; Similarly the <em>__init__() </em>and <em>write_file() </em>methods (if we had a <em>write_file()</em> method) get called by the parent Activity class.
</p>
<p>If you're especially observant you might have noticed another change.&#160; Our original standalone program created a temporary file when it needed to extract something from a Zip file.&#160; It put that file in a directory called /tmp.&#160; Our new Activity still creates the file but puts it in a different directory, one specific to the Activity.
</p>
<p>
</p>All writing to the file system is restricted to subdirectories of the path given by <em>self.get_activity_root()</em>.&#160; This method will give you a directory that belongs to your Activity alone.&#160; It will contain three subdirectories with different policies:
<dl><dt><strong>data</strong></dt>
  <dd> This directory is used for data such as configuration files.&#160; Files stored here will survive reboots and OS upgrades.</dd>
</dl><dl><dt><strong>tmp</strong></dt>
  <dd> This directory is used similar to the /tmp directory, being backed by RAM. It may be as small as 1 MB. This directory is deleted when the activity exits.</dd>
</dl><dl><dt><strong>instance</strong></dt>
  <dd> This directory is similar to the <strong>tmp</strong> directory, being backed by the computer's drive rather than by RAM. It is unique per instance. It is used for transfer to and from the Journal. This directory is deleted when the activity exits.</dd>
</dl><p>
</p>
<p>Making these changes to the code is not enough to make our program an Activity.&#160; We have to do some packaging work and get it set up to run from the Sugar emulator.&#160; We also need to learn how to run the Sugar emulator.&#160; That comes next!
  <br/></p></body></html>