Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/studio/static/doc/myosa/ch008_a-standalone-python-program-for-reading-etexts.xhtml
blob: 4afdb216193aa69ae8da1ab7a0a4dc82b3bc4b20 (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
<?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> A Standalone Python Program For Reading Etexts
</h1>
<h2>The Program
  <br/></h2>
<p>Our example program is based on the first Activity I wrote, <strong>Read Etexts</strong>.&#160; This is a program for reading free e-books.
  <br/></p>
<p>The oldest and best source of free e-books is a website called <em>Project Gutenberg</em> <a href="http://www.gutenberg.org/wiki/Main_Page).">(</a><a href="http://www.gutenberg.org/wiki/Main_Page" target="_top">http://www.gutenberg.org/wiki/Main_Page</a>).&#160; They create books in plain text format, in other words the kind of file you could make if you typed a book into Notepad and hit the Enter key at the end of each line.&#160; They have thousands of books that are out of copyright, including some of the best ever written.&#160; Before you read further go to that website and pick out a book that interests you.&#160; Check out the "Top 100" list to see the most popular books and authors.
</p>
<p>The program we're going to create will read books in plain text format only.
</p>
<p>There is a Git repository containing all the code examples in this book.&#160; Once you have Git installed you can copy the repository to your computer with this command:
</p>
<pre>git clone git://git.sugarlabs.org/\
myo-sugar-activities-examples/mainline.git</pre>
<p>The code for our standalone Python program will be found in the directory <strong>Make_Standalone_Python</strong> in a file named <strong>ReadEtexts.py</strong>.&#160; It looks like this:
  <br/></p>
<pre>#! /usr/bin/env python
import sys
import os
import zipfile
import pygtk
import gtk
import getopt
import pango

page=0
PAGE_SIZE = 45

class ReadEtexts():

    def keypress_cb(self, widget, event):
        "Respond when the user presses one of the arrow keys"
        keyname = gtk.gdk.keyval_name(event.keyval)
        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;&#8286;= 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)
        f = open("/tmp/" + filename, '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 = "/tmp/" + 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)

    def destroy_cb(self, widget, data=None):
        gtk.main_quit()

    def main(self, file_path):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect("destroy", self.destroy_cb)
        self.window.set_title("Read Etexts")
        self.window.set_size_request(640, 480)
        self.window.set_border_width(0)
        self.read_file(file_path)
        self.scrolled_window = gtk.ScrolledWindow(
            hadjustment=None, vadjustment=None)
        self.textview = gtk.TextView()
        self.textview.set_editable(False)
        self.textview.set_left_margin(50)
        self.textview.set_cursor_visible(False)
        self.textview.connect("key_press_event",
            self.keypress_cb)
        buffer = self.textview.get_buffer()
        self.font_desc = pango.FontDescription("sans 12")
        font_size = self.font_desc.get_size()
        self.textview.modify_font(self.font_desc)
        self.show_page(0)
        self.scrolled_window.add(self.textview)
        self.window.add(self.scrolled_window)
        self.textview.show()
        self.scrolled_window.show()
        v_adjustment = \
            self.scrolled_window.get_vadjustment()
        self.window.show()
        gtk.main()

if __name__ == "__main__":
    try:
        opts, args = getopt.getopt(sys.argv[1:], "")
        ReadEtexts().main(args[0])
    except getopt.error, msg:
        print msg
        print "This program has no options"
        sys.exit(2)
</pre>
<h2> Running The Program
</h2>
<p>To run the program you should first make it executable.&#160; You only need to do this once:
</p>
<pre>chmod 755 ReadEtexts.py</pre>
<p>For this example I downloaded the file for <em>Pride and Prejudice</em>.&#160; The program will work with either of the Plain text formats, which are either uncompressed text or a Zip file.&#160; The zip file is named <strong>1342.zip</strong>, and we can read the book by running this from a terminal:
</p>
<pre>./ReadEtexts.py 1342.zip</pre>
<p>This is what the program looks like in action:
</p>
<p><img alt="The standalone Read Etexts program in action." src="static/ActivitiesGuideSugar-ReadEtexts_01_1-en.jpg" width="646" height="503"/></p>
<p>You can use the <em>Page Up, Page Down, Up, Down, Left</em>, and <em>Right</em> keys to navigate through the book and the '+' and '-' keys to adjust the font size.
</p>
<div class="objavi-forcebreak">
</div>
<h2>How The Program Works
</h2>
<p>This program reads through the text file containing the book and divides it into pages of 45 lines each.&#160; We need to do this because the <strong>gtk.TextView</strong> component we use for viewing the text would need a lot of memory to scroll through the whole book and that would hurt performance.&#160; A second reason is that we want to make reading the e-book as much as possible like reading a regular book, and regular books have pages.&#160; If a teacher assigns reading from a book she might say "read pages 35-50 for tommorow".&#160; Finally, we want this program to remember what page you stopped reading on and bring you back to that page again when you read the book next time.&#160; (The program we have so far doesn't do that yet).
</p>
<p>To page through the book we use <strong>random access</strong> to read the file.&#160; To understand what random access means to a file, consider a VHS tape and a DVD.&#160; To get to a certain scene in a VHS tape you need to go through all the scenes that came before it, in order.&#160; Even though you do it at high speed you still have to look at all of them to find the place you want to start watching.&#160; This is <strong>sequential access</strong>.&#160; On the other hand a DVD has chapter stops and possibly a chapter menu.&#160; Using a chapter menu you can look at any scene in the movie right away, and you can skip around as you like.&#160; This is random access, and the chapter menu is like an <strong>index</strong>.&#160; Of course you can access the material in a DVD sequentially too.
</p>
<p>We need random access to skip to whatever page we like, and we need an index so that we know where each page begins.&#160; We make the index by reading the entire file one line at a time.&#160; Every 45 lines we make a note of how many characters into the file we've gotten and store this information in a Python list.&#160; Then we go back to the beginning of the file and display the first page.&#160; When the program user goes to the next or previous page we figure out what the new page number will be and look in the list entry for that page.&#160; This tells us that page starts 4,200 characters into the file.&#160; We use seek() on the file to go to that character and then we read 45 lines starting at that point and load them into the TextView.
</p>
<p>When you run this program notice how fast it is.&#160; Python programs take longer to run a line of code than a compiled language would, but in this program it doesn't matter because the heavy lifting in the program is done by the TextView, which was created in a compiled language.&#160; The Python parts don't do that much so the program doesn't spend much time running them.
</p>
<p>Sugar uses Python a lot, not just for Activities but for the Sugar environment itself.&#160; You may read somewhere that using so much Python is "a disaster" for performance.&#160; Don't believe it.
</p>
<p>There are no slow programming languages, only slow programmers.
  <br/></p></body></html>