1#!/usr/bin/env python
2
3# This is a sample implementation of an editor.
4
5import os
6
7import pygtk
8pygtk.require('2.0')
9import gtk
10import dialogs
11
12BLOCK_SIZE = 2048
13RESPONSE_FORWARD = 1
14
15class EditWindow(gtk.Window):
16    def __init__(self, quit_cb=None):
17        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
18        self.set_size_request(470, 300)
19        self.connect("delete_event", self.file_exit)
20        self.quit_cb = quit_cb
21        self.vbox = gtk.VBox()
22        self.add(self.vbox)
23        self.vbox.show()
24        hdlbox = gtk.HandleBox()
25        self.vbox.pack_start(hdlbox, expand=False)
26        hdlbox.show()
27        self.menubar, self.toolbar = self.create_menu()
28        hdlbox.add(self.menubar)
29        self.menubar.show()
30        self.vbox.pack_start(self.toolbar, expand=False)
31        self.scrolledwin = gtk.ScrolledWindow()
32        self.scrolledwin.show()
33        self.vbox.pack_start(self.scrolledwin)
34        self.text = gtk.TextView()
35        self.text.set_editable(True)
36        self.scrolledwin.add(self.text)
37        self.text.show()
38        self.buffer = self.text.get_buffer()
39        self.dirty = 0
40        self.file_new()
41        self.text.grab_focus()
42        self.clipboard = gtk.Clipboard(selection='CLIPBOARD')
43        self.dirname = None
44        self.search_string = None
45        self.last_search_iter = None
46        return
47    def load_file(self, fname):
48        try:
49            fd = open(fname)
50            self.buffer.set_text('')
51            buf = fd.read(BLOCK_SIZE)
52            while buf != '':
53                self.buffer.insert_at_cursor(buf)
54                buf = fd.read(BLOCK_SIZE)
55            self.text.queue_draw()
56            self.set_title(os.path.basename(fname))
57            self.fname = fname
58            self.dirname = os.path.dirname(self.fname)
59            self.buffer.set_modified(False)
60            self.new = 0
61        except:
62            dlg = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT,
63                                    gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
64                                    "Can't open " + fname)
65            resp = dlg.run()
66            dlg.hide()
67        return
68    def create_menu(self):
69        ui_string = """<ui>
70        <menubar>
71            <menu name='FileMenu' action='FileMenu'>
72                <menuitem action='FileNew'/>
73                <menuitem action='FileOpen'/>
74                <menuitem action='FileSave'/>
75                <menuitem action='FileSaveAs'/>
76                <separator/>
77                <menuitem action='FileExit'/>
78            </menu>
79            <menu name='EditMenu' action='EditMenu'>
80                <menuitem action='EditCut'/>
81                <menuitem action='EditCopy'/>
82                <menuitem action='EditPaste'/>
83                <menuitem action='EditClear'/>
84                <separator/>
85                <menuitem action='EditFind'/>
86                <menuitem action='EditFindNext'/>
87            </menu>
88            <placeholder name='OtherMenus'/>
89            <menu name='HelpMenu' action='HelpMenu'>
90                <menuitem action='HelpAbout'/>
91            </menu>
92        </menubar>
93        <toolbar>
94          <toolitem action='FileNew'/>
95          <toolitem action='FileOpen'/>
96          <toolitem action='FileSave'/>
97          <toolitem action='FileSaveAs'/>
98                <separator/>
99          <toolitem action='EditCut'/>
100          <toolitem action='EditCopy'/>
101          <toolitem action='EditPaste'/>
102          <toolitem action='EditClear'/>
103        </toolbar>
104        </ui>
105        """
106        actions = [
107            ('FileMenu', None, '_File'),
108            ('FileNew', gtk.STOCK_NEW, None, None, None, self.file_new),
109            ('FileOpen', gtk.STOCK_OPEN, None, None, None, self.file_open),
110            ('FileSave', gtk.STOCK_SAVE, None, None, None, self.file_save),
111            ('FileSaveAs', gtk.STOCK_SAVE_AS, None, None, None,
112             self.file_saveas),
113            ('FileExit', gtk.STOCK_QUIT, None, None, None, self.file_exit),
114            ('EditMenu', None, '_Edit'),
115            ('EditCut', gtk.STOCK_CUT, None, None, None, self.edit_cut),
116            ('EditCopy', gtk.STOCK_COPY, None, None, None, self.edit_copy),
117            ('EditPaste', gtk.STOCK_PASTE, None, None, None, self.edit_paste),
118            ('EditClear', gtk.STOCK_REMOVE, 'C_lear', None, None,
119             self.edit_clear),
120            ('EditFind', gtk.STOCK_FIND, None, None, None, self.edit_find),
121            ('EditFindNext', None, 'Find _Next', "F3", None,
122             self.edit_find_next),
123            ('HelpMenu', gtk.STOCK_HELP),
124            ('HelpAbout', None, 'A_bout', None, None, self.help_about),
125            ]
126        self.ag = gtk.ActionGroup('edit')
127        self.ag.add_actions(actions)
128        self.ui = gtk.UIManager()
129        self.ui.insert_action_group(self.ag, 0)
130        self.ui.add_ui_from_string(ui_string)
131        self.add_accel_group(self.ui.get_accel_group())
132        return (self.ui.get_widget('/menubar'), self.ui.get_widget('/toolbar'))
133
134    def chk_save(self):
135        if self.buffer.get_modified():
136            dlg = gtk.Dialog('Unsaved File', self,
137                             gtk.DIALOG_DESTROY_WITH_PARENT,
138                             (gtk.STOCK_YES, gtk.RESPONSE_YES,
139                              gtk.STOCK_NO, gtk.RESPONSE_NO,
140                              gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
141            lbl = gtk.Label((self.fname or "Untitled")+
142                            " has not been saved\n" +
143                            "Do you want to save it?")
144            lbl.show()
145            dlg.vbox.pack_start(lbl)
146            ret = dlg.run()
147            dlg.hide()
148            if ret == gtk.RESPONSE_NO:
149                return 0
150            if ret == gtk.RESPONSE_YES:
151                if self.file_save():
152                    return 0
153            return 1
154        return 0
155
156    def file_new(self, mi=None):
157        if self.chk_save(): return
158        self.buffer.set_text('')
159        self.buffer.set_modified(False)
160        self.fname = None
161        self.set_title("Untitled")
162        self.new = 1
163        return
164    def file_open(self, mi=None):
165        if self.chk_save(): return
166        fname = dialogs.OpenFile('Open File', self, self.dirname, self.fname)
167        if not fname: return
168        self.load_file(fname)
169        return
170    def file_save(self, mi=None):
171        if self.new:
172            return self.file_saveas()
173        ret = False
174        try:
175            start, end = self.buffer.get_bounds()
176            blockend = start.copy()
177            fd = open(self.fname, "w")
178            while blockend.forward_chars(BLOCK_SIZE):
179                buf = self.buffer.get_text(start, blockend)
180                fd.write(buf)
181                start = blockend.copy()
182            buf = self.buffer.get_text(start, blockend)
183            fd.write(buf)
184            fd.close()
185            self.buffer.set_modified(False)
186            ret = True
187        except:
188            dlg = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT,
189                                    gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
190                                    "Error saving file " + self.fname)
191            resp = dlg.run()
192            dlg.hide()
193        return ret
194    def file_saveas(self, mi=None):
195        fname = dialogs.SaveFile('Save File As', self, self.dirname,
196                                  self.fname)
197        if not fname: return False
198        self.fname = fname
199        self.dirname = os.path.dirname(self.fname)
200        self.set_title(os.path.basename(fname))
201        self.new = 0
202        return self.file_save()
203    def file_exit(self, mi=None, event=None):
204        if self.chk_save(): return True
205        self.hide()
206        self.destroy()
207        if self.quit_cb: self.quit_cb(self)
208        return False
209    def edit_cut(self, mi):
210        self.buffer.cut_clipboard(self.clipboard, True)
211        return
212    def edit_copy(self, mi):
213        self.buffer.copy_clipboard(self.clipboard)
214        return
215    def edit_paste(self, mi):
216        self.buffer.paste_clipboard(self.clipboard, None, True)
217        return
218    def edit_clear(self, mi):
219        self.buffer.delete_selection(True, True)
220        return
221    def _search(self, search_string, iter = None):
222        if iter is None:
223            start = self.buffer.get_start_iter()
224        else:
225            start = iter
226        i = 0
227        if search_string:
228            self.search_string = search_string
229            res = start.forward_search(search_string, gtk.TEXT_SEARCH_TEXT_ONLY)
230            if res:
231                match_start, match_end = res
232                self.buffer.place_cursor(match_start)
233                self.buffer.select_range(match_start, match_end)
234                self.text.scroll_to_iter(match_start, 0.0)
235                self.last_search_iter = match_end
236
237            else:
238                self.search_string = None
239                self.last_search_iter = None
240
241    def edit_find(self, mi):
242        def dialog_response_callback(dialog, response_id):
243            if response_id == gtk.RESPONSE_CLOSE:
244                dialog.destroy()
245                return
246            self._search(search_text.get_text(), self.last_search_iter)
247        search_text = gtk.Entry()
248        s = self.buffer.get_selection_bounds()
249        if len(s) > 0:
250            search_text.set_text(self.buffer.get_slice(s[0], s[1]))
251        dialog = gtk.Dialog("Search", self,
252                            gtk.DIALOG_DESTROY_WITH_PARENT,
253                            (gtk.STOCK_FIND, RESPONSE_FORWARD,
254                             gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
255        dialog.vbox.pack_end(search_text, True, True, 0)
256        dialog.connect("response", dialog_response_callback)
257        search_text.show()
258        search_text.grab_focus()
259        dialog.show_all()
260        response_id = dialog.run()
261    def edit_find_next(self, mi):
262        self._search(self.search_string, self.last_search_iter)
263    def help_about(self, mi):
264        dlg = gtk.MessageDialog(self, gtk.DIALOG_DESTROY_WITH_PARENT,
265                                gtk.MESSAGE_INFO, gtk.BUTTONS_OK,
266                                "Copyright (C)\n"
267                                "1998 James Henstridge\n"
268                                "2004 John Finlay\n"
269                                "This program is covered by the GPL>=2")
270        dlg.run()
271        dlg.hide()
272        return
273
274def edit(fname, mainwin=False):
275    if mainwin: quit_cb = lambda w: gtk.main_quit()
276    else:       quit_cb = None
277    w = EditWindow(quit_cb=quit_cb)
278    w.load_file(fname)
279    w.show()
280    w.set_size_request(0,0)
281    if mainwin: gtk.main()
282    return
283
284if __name__ == '__main__':
285    import sys
286    edit(sys.argv[-1], mainwin=True)
287