~ubuntu-branches/ubuntu/utopic/pida/utopic

« back to all changes in this revision

Viewing changes to pida/editors/culebraedit.py

  • Committer: Bazaar Package Importer
  • Author(s): Jan Luebbe
  • Date: 2007-04-17 16:08:06 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20070417160806-3ttlb6igf94x9i03
Tags: 0.4.4-1
* New upstream release (closes: #419129)
* Add dependency on python-glade2 (closes: #418716)
* Update copyright

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
from pida.pidagtk import contentview
30
30
from pida.core import actions
31
31
from pida.core import service
32
 
from pida.utils.culebra import edit, sensitive, common
33
 
 
 
32
from pida.utils.culebra import edit, sensitive, common, interfaces
34
33
 
35
34
defs = service.definitions
36
 
types = service.types
37
 
 
38
 
#temporary
39
 
C = gtk.gdk.CONTROL_MASK
40
 
SC = gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK
 
35
 
 
36
from pida.model import attrtypes as types
 
37
 
 
38
from gtk import keysyms
 
39
from gtk.gdk import CONTROL_MASK, SHIFT_MASK
 
40
 
 
41
control = lambda x: (getattr(keysyms, x), CONTROL_MASK)
 
42
control_shift = lambda x: (getattr(keysyms, x), CONTROL_MASK|SHIFT_MASK)
41
43
 
42
44
KEYMAP = {
43
 
    'DocumentSave': (115, C),
44
 
    'documenttypes+document+undo': (122, C),
45
 
    'documenttypes+document+redo': (122, SC),
46
 
    'documenttypes+document+cut': (120, C),
47
 
    'documenttypes+document+copy': (99, C),
48
 
    'documenttypes+document+paste': (118, C),
 
45
    'DocumentSave': control("s"),
 
46
    'documenttypes+document+undo': control("z"),
 
47
    'documenttypes+document+redo': control_shift("z"),
 
48
    'documenttypes+document+cut': control("x"),
 
49
    'documenttypes+document+copy': control("c"),
 
50
    'documenttypes+document+paste': control("v"),
 
51
    'culebraedit+cut_line': control("d"),
 
52
    'culebraedit+select_line': control("l"),
 
53
    'culebraedit+select_word': control("h"),
 
54
    'culebraedit+cut_word': control("j"),
 
55
    'culebraedit+select_sentence': control("i"),
 
56
    'culebraedit+cut_sentence': control("u"),
 
57
    'culebraedit+select_paragraph': control("p"),
49
58
}
50
59
 
51
 
class culebra_view(contentview.content_view):
 
60
del control, control_shift
 
61
 
 
62
class CulebraView(contentview.content_view):
52
63
 
53
64
    HAS_CONTROL_BOX = False
54
65
    HAS_TITLE = False
55
66
 
56
 
    def init(self, filename=None, action_group=None, background_color=None, font_color=None):
 
67
    def init(self, action_group=None, background_color=None,
 
68
                   font_color=None, font_text=None, document=None):
57
69
        self.widget.set_no_show_all(True)
58
 
        widget, editor = edit.create_widget(filename, action_group)
59
 
        editor.set_background_color(background_color)
60
 
        editor.set_font_color(font_color)
61
 
        self.__editor = editor
62
 
        self.buffer.connect('can-undo', self.cb_can_undo)
63
 
        self.buffer.connect('can-redo', self.cb_can_redo)
64
 
        widget.show()
65
 
        self.widget.add(widget)
66
 
 
67
 
    def get_editor(self):
68
 
        return self.__editor
69
 
        
70
 
    editor = property(get_editor)
71
 
 
72
 
    def get_buffer(self):
73
 
        return self.__editor.get_buffer()
74
 
        
75
 
    buffer = property(get_buffer)
76
 
 
 
70
        
 
71
        self.provider = edit.create_editor(action_group=action_group)
 
72
        
 
73
        
 
74
        self.set_action_group = self.provider.get_service(interfaces.IActionGroupController).set_action_group
 
75
        
 
76
        self.editor = self.provider.get_service('view')
 
77
        self.buffer = self.editor.get_buffer()
 
78
#        editor.set_background_color(background_color)
 
79
#        editor.set_font_color(font_color)
 
80
        self.editor.set_font(font_text)
 
81
#        self.__editor = editor
 
82
        try:
 
83
            self.buffer.connect('can-undo', self.cb_can_undo)
 
84
            self.buffer.connect('can-redo', self.cb_can_redo)
 
85
        except TypeError:
 
86
            # In this case we are not dealling with sourceview
 
87
            pass
 
88
        file_ops = self.provider.get_service(interfaces.IFileOperations)
 
89
        file_ops.set_encoding(document.encoding)
 
90
        file_ops.set_filename(document.filename)
 
91
        file_ops.set_mimetype(document.mimetype)
 
92
        file_ops.load()
 
93
        self._real_widget.show()
 
94
        self.widget.add(self._real_widget)
 
95
 
 
96
    _real_widget = property(lambda self: self.provider.get_service(interfaces.IWidget))
 
97
    
 
98
    def get_file_ops(self):
 
99
        return self.provider.get_service(interfaces.IFileOperations)
 
100
 
 
101
    def get_carret(self):
 
102
        return self.provider.get_service(interfaces.ICarretController)
 
103
    
77
104
    def can_undo(self):
78
105
        return self.buffer.can_undo()
79
106
 
87
114
        self.service.cb_can_redo(self, can)
88
115
 
89
116
    def connect_keys(self):
90
 
        self.__editor.add_events(gtk.gdk.KEY_PRESS_MASK)
91
 
        self.__editor.connect('key-press-event', self.cb_keypress)
 
117
        self._real_widget.add_events(gtk.gdk.KEY_PRESS_MASK)
 
118
        self._real_widget.connect('key-press-event', self.cb_keypress)
92
119
 
93
120
    def cb_keypress(self, widg, event):
94
121
        key, mod = event.keyval, event.state
95
122
        return self.service.received_key(key, mod)
96
 
        
 
123
 
 
124
class CulebraConfig:
 
125
    __order__ = ['general']
 
126
    class general(defs.optiongroup):
 
127
        """Options for the culebra text editor"""
 
128
  
 
129
        class font(defs.option):
 
130
            """Change the font used in Culebra."""
 
131
            rtype = types.font
 
132
            default = 'monospace 12'
 
133
 
 
134
 
 
135
    def __markup__(self):
 
136
        return 'Culebra text editor'
 
137
 
97
138
 
98
139
class culebra_editor(service.service):
99
140
 
100
141
    display_name = 'Culebra Text Editor'
101
142
 
102
 
    multi_view_type = culebra_view
103
 
    multi_view_book = 'edit'
 
143
    config_definition = CulebraConfig
 
144
 
 
145
    class Culebra(defs.View):
 
146
        view_type = CulebraView
 
147
        book_name = 'edit'
104
148
    
105
 
    class general(defs.optiongroup):
106
 
        class background_color(defs.option):
107
 
            """Change the background color"""
108
 
            default = "#FFFFFF"
109
 
            rtype = types.color
110
 
        
111
 
        class font_color(defs.option):
112
 
            """Change the font color"""
113
 
            default = "#000000"
114
 
            rtype = types.color
115
 
            
116
149
    ############
117
150
    # Service related methods
118
151
    def init(self):
119
 
        
120
 
        self.__files = {}
 
152
        self.__documents = {}
121
153
        self.__views = {}
122
154
        self.__keymods = {}
123
155
        self.__undoacts = {}
 
156
        self._save_action = None
 
157
 
 
158
    @classmethod
 
159
    def get_sanity_errors(cls):
 
160
        """No sanity checks are needed"""
 
161
        
124
162
 
125
163
    def reset(self):
126
 
        self._get_actions()
127
 
        for view in self.__files.values():
128
 
            view.editor.set_background_color(self.get_background_color())
129
 
            view.editor.set_font_color(self.get_font_color())
130
 
    
131
 
    #############
132
 
    # Methods
133
 
    def get_background_color(self):
134
 
        color = self.opt("general", "background_color")
135
 
        return gdk.color_parse(color)
136
 
    
137
 
    def get_font_color(self):
138
 
        color = self.opt("general", "font_color")
139
 
        return gdk.color_parse(color)
140
 
        
141
 
    def __load_file(self, filename):
142
 
        view = self.create_multi_view(
143
 
            filename=filename,
144
 
            action_group=self.action_group,
145
 
            background_color = self.get_background_color(),
146
 
            font_color = self.get_font_color(),
147
 
        )
148
 
        self.__files[filename] = view
149
 
        self.__views[view.unique_id] = filename
150
 
        view.connect_keys()
151
 
 
152
 
    def __view_file(self, filename):
153
 
        self.current_view = self.__files[filename]
154
 
        self.__files[filename].raise_page()
155
 
    
156
 
    ####################
157
 
    # get_action
158
 
    _save_action = None
159
 
 
160
 
    def _get_actions(self):
161
 
        actions = self.boss.call_command("documenttypes", "get_document_actions")
162
 
        for action in actions:
 
164
        self.__bind_document_actions()
 
165
    
 
166
    
 
167
    def cb_general__font(self, font):
 
168
        try:
 
169
            for view in self.__views.values():
 
170
                view.editor.set_font(font)
 
171
        except AttributeError:
 
172
            pass
 
173
 
 
174
    def __bind_document_actions(self):
 
175
        doctypes = self.boss.get_service("documenttypes")
 
176
        for action in doctypes.cmd_get_document_actions():
163
177
            actname = action.get_name()
164
178
            if actname in KEYMAP:
165
179
                self.__keymods[KEYMAP[actname]] = action
171
185
                self.__revertact = action
172
186
            elif actname == 'DocumentSave':
173
187
                self._save_action = action
 
188
        for act in self.action_group.list_actions():
 
189
            actname = act.get_name()
 
190
            if actname in KEYMAP:
 
191
                self.__keymods[KEYMAP[actname]] = act
 
192
        
 
193
    def __load_document(self, document):
 
194
        view = self.create_view('Culebra',
 
195
            action_group=self.action_group,
 
196
            background_color = None,
 
197
            font_color = None,
 
198
            font_text = self.opts.general__font,
 
199
            document = document,
 
200
        )
 
201
        self.__views[document.unique_id] = view
 
202
        self.__documents[view.unique_id] = document
 
203
        view.connect_keys()
 
204
        self.show_view(view=view)
 
205
 
 
206
    def __view_document(self, document):
 
207
        view = self.__views[document.unique_id]
 
208
        view.raise_page()
 
209
        # Use subscription pattern
 
210
        if self.current_view is not None:
 
211
            self.current_view.set_action_group(None)
 
212
            
 
213
        self.current_view = view
 
214
        self.__set_action_sensitivities(document)
 
215
 
 
216
    def __set_action_sensitivities(self, document):
 
217
        save_action = self.get_save_action()
 
218
        if document.is_new:
 
219
            save_action.set_sensitive(True)
 
220
            self.__revertact.set_sensitive(False)
 
221
 
 
222
        else:
 
223
            buff = self.current_view.editor.get_buffer()
 
224
            self.current_view.set_action_group(self.action_group)
 
225
            
 
226
            self.linker = sensitive.SaveLinker(buff, save_action)
 
227
            self.linker2 = sensitive.SaveLinker(buff, self.__revertact)
 
228
         # undo redo
 
229
        self.cb_can_undo(self.current_view, self.current_view.can_undo())
 
230
        self.cb_can_redo(self.current_view, self.current_view.can_redo())
 
231
 
 
232
    def get_background_color(self):
 
233
        color = self.opts.general__background_color
 
234
        return gdk.color_parse(color)
 
235
    
 
236
    def get_font_color(self):
 
237
        color = self.opts.general__font_color
 
238
        return gdk.color_parse(color)
174
239
    
175
240
    def get_save_action(self):
176
 
        if self._save_action is None:
177
 
            self._get_actions()
 
241
        #if self._save_action is None:
 
242
        #    self._get_actions()
178
243
        return self._save_action
179
244
 
 
245
    def get_window(self):
 
246
        return self.boss.get_main_window()
 
247
 
180
248
    # keyboard bindings
181
249
    
182
250
    def received_key(self, key, mod):
 
251
        # make sure we have no extra mods
 
252
        mod &= (CONTROL_MASK | SHIFT_MASK)
183
253
        if (key, mod) in self.__keymods:
184
254
            self.__keymods[key, mod].emit('activate')
185
255
            return True
186
256
        else:
187
257
            return False
188
 
    
189
 
    def get_window(self):
190
 
        return self.boss.get_main_window()
191
258
 
192
259
    # Undo/redo
193
260
 
202
269
    #############
203
270
    # Commands
204
271
    current_view = None
205
 
    def cmd_edit(self, filename=None):
206
 
        if self.current_view is not None:
207
 
            self.current_view.editor.set_action_group(None)
208
 
        if filename not in self.__files:
209
 
            self.__load_file(filename)
210
 
        self.__view_file(filename)
211
 
        save_action = self.get_save_action()
212
 
        assert save_action is not None
213
 
        buff = self.current_view.editor.get_buffer()
214
 
        self.current_view.editor.set_action_group(self.action_group)
215
 
        self.linker = sensitive.SaveLinker(buff, save_action)
216
 
        self.linker2 = sensitive.SaveLinker(buff, self.__revertact)
217
 
        # undo redo
218
 
        self.cb_can_undo(self.current_view, self.current_view.can_undo())
219
 
        self.cb_can_redo(self.current_view, self.current_view.can_redo())
220
 
            
 
272
    def cmd_edit(self, document):
 
273
        # temporary to preserve old filename behaviour
 
274
        #filename = document.filename
 
275
        #if self.current_view is not None:
 
276
        #    self.current_view.editor.set_action_group(None)
 
277
        if document.unique_id not in self.__views:
 
278
            self.__load_document(document)
 
279
        self.__view_document(document)
 
280
    
 
281
    def _file_op(self, oper, attr, label):
 
282
        try:
 
283
            getattr(oper, attr)()
 
284
            return True
 
285
        except IOError, err:
 
286
            hig.error("There was an error trying to %s the document" % label,
 
287
                      str(err), parent=self.get_window())
 
288
        return False
 
289
 
221
290
    def cmd_revert(self):
222
291
        if self.current_view is None:
223
292
            return
224
293
            
225
 
        buff = self.current_view.editor.get_buffer()
226
 
        if buff.is_new:
 
294
        buff = self.current_view.get_file_ops()
 
295
 
 
296
        if buff.get_is_new():
227
297
            return
228
298
        
229
299
        # Ok the buffer is not new and exists
230
 
        reply = hig.dialog_ok_cancel(
 
300
        reply = hig.ok_cancel(
231
301
            "Revert the document to saved contents",
232
302
            ("Even if your document was just saved it could be altered by a "
233
303
             "third party. By reverting to its real contents you will loose "
235
305
            parent = self.get_window(),
236
306
            ok_button = gtk.STOCK_REVERT_TO_SAVED,
237
307
        )
238
 
        if reply == gtk.RESPONSE_OK:
239
 
            buff.load_from_file()
 
308
        if reply == gtk.RESPONSE_OK and self._file_op(buff, "load", "revert"):
240
309
            self.boss.call_command('buffermanager', 'reset_current_document')
 
310
                    
241
311
 
242
312
    def cmd_start(self):
243
313
        self.get_service('editormanager').events.emit('started')
244
314
 
245
315
    def cmd_goto_line(self, linenumber):
246
 
        view = self.current_view.editor
247
 
        buff = self.current_view.buffer
248
 
        
249
 
        # Get line iterator
250
 
        line_iter = buff.get_iter_at_line(linenumber - 1)
251
 
        # Move scroll to the line iterator
252
 
        view.scroll_to_iter(line_iter, 0.25)
253
 
        # Place the cursor at the begining of the line
254
 
        buff.place_cursor(line_iter)
 
316
        if self.current_view is None:
 
317
            return
 
318
        carret = self.current_view.get_carret()
 
319
        carret.goto_line(linenumber - 1)
255
320
        
256
321
    def cmd_save(self):
257
 
        self.current_view.buffer.save()
258
 
        self.boss.call_command('buffermanager', 'reset_current_document')
 
322
        file_ops = self.current_view.get_file_ops()
 
323
        if self._file_op(file_ops, "save", "save"):
 
324
            self.boss.call_command('buffermanager', 'reset_current_document')
 
325
 
 
326
    def cmd_save_as(self, filename):
 
327
        buf = self.current_view.get_file_ops()
 
328
        old_filename = buf.get_filename()
 
329
        buf.set_filename(filename)
 
330
        if not self._file_op(buf, "save", "save"):
 
331
            buf.set_filename(old_filename)
259
332
 
260
333
    def cmd_undo(self):
261
334
        self.current_view.editor.emit('undo')
272
345
    def cmd_paste(self):
273
346
        self.current_view.editor.emit('paste-clipboard')
274
347
    
275
 
    def cmd_close(self, filename):
276
 
        # example implementation
277
 
        view = self.__files[filename]
278
 
        if self.confirm_multi_view_controlbar_clicked_close(view):
279
 
            view.remove()
280
 
            self.cb_multi_view_closed(view)
 
348
    def cmd_close(self, document):
 
349
        view = self.__views[document.unique_id]
 
350
        #view = self.current_view
 
351
        self.close_view(view)
 
352
        self.current_view = None
281
353
 
282
354
    def cmd_can_close(self):
283
 
        buffs = [view.buffer for view in self.__files.values() if view.buffer.get_modified()]
 
355
        buffs = map(lambda val: val.get_file_ops(), self.__views.values())
 
356
        buffs = filter(lambda val: val.get_modified(), buffs)
 
357
 
284
358
        # If we have no buffers to save, go on
285
359
        if len(buffs) == 0:
286
360
            return True
287
361
            
288
 
        filenames = dict(map(lambda buff: (buff.filename, buff), buffs))
 
362
        filenames = dict(map(lambda buff: (buff.get_filename(), buff), buffs))
289
363
        parent = self.get_window()
290
364
        files, response = hig.save_changes(filenames.keys(), parent=parent)
291
365
        # Save the files that need to be saved
 
366
        errors = []
292
367
        for filename in files:
293
368
            # XXX: handle new files
294
 
            filenames[filename].save()
 
369
            try:
 
370
                filenames[filename].save()
 
371
            except IOError, err:
 
372
                errors.append(filename)
 
373
        if len(errors) > 0:
 
374
            # XXX: handle errors
 
375
            pass
295
376
        return response in (gtk.RESPONSE_CLOSE, gtk.RESPONSE_OK)
296
377
        
 
378
    def cmd_select_line(self):
 
379
        buf = self.current_view.editor.get_buffer()
 
380
        si = buf.get_iter_at_mark(buf.get_insert())
 
381
        ei = si.copy()
 
382
        si.set_line_offset(0)
 
383
        ei.forward_line()
 
384
        buf.select_range(si, ei)
 
385
        
 
386
    def cmd_cut_line(self):
 
387
        self.cmd_select_line()
 
388
        self.cmd_cut()
 
389
        
 
390
    def cmd_select_word(self):
 
391
        buf = self.current_view.editor.get_buffer()
 
392
        si = buf.get_iter_at_mark(buf.get_insert())
 
393
        if si.inside_word():
 
394
            ei = si.copy()
 
395
            if not si.starts_word():
 
396
                si.backward_word_start()
 
397
            if not ei.ends_word():
 
398
                ei.forward_word_end()
 
399
            buf.select_range(si, ei)
 
400
            return True
 
401
        else:
 
402
            return False
 
403
                
 
404
    def cmd_cut_word(self):
 
405
        if self.cmd_select_word():
 
406
            self.cmd_cut()
 
407
            
 
408
    def cmd_select_sentence(self):      
 
409
        buf = self.current_view.editor.get_buffer()
 
410
        si = buf.get_iter_at_mark(buf.get_insert())
 
411
        if si.inside_sentence():
 
412
            ei = si.copy()
 
413
            if not si.starts_sentence():
 
414
                si.backward_sentence_start()
 
415
            if not ei.ends_sentence():
 
416
                ei.forward_sentence_end()
 
417
            buf.select_range(si, ei)
 
418
            return True
 
419
        else:
 
420
            return False
 
421
            
 
422
    def cmd_cut_sentence(self):
 
423
        if self.cmd_select_sentence():
 
424
            self.cmd_cut()
 
425
    
 
426
    def cmd_select_paragraph(self):
 
427
        buf = self.current_view.editor.get_buffer()
 
428
        si = buf.get_iter_at_mark(buf.get_insert())
 
429
        if not si.get_chars_in_line() > 1:
 
430
            return
 
431
        ei = si.copy()
 
432
        while si.get_chars_in_line() > 1 and not si.is_start():
 
433
            si.backward_line()
 
434
            si.set_line_offset(0)
 
435
        if not si.is_start():
 
436
            si.forward_lines(1)
 
437
        while ei.get_chars_in_line() > 1 and not ei.is_end():
 
438
            ei.forward_line()
 
439
        buf.select_range(si, ei)
 
440
        
297
441
    ###############
298
442
    # Callbacks
299
 
    def cb_multi_view_closed(self, view):
300
 
        if view.unique_id in self.__views:
301
 
            filename = self.__views[view.unique_id]
302
 
            del self.__views[view.unique_id]
303
 
            del self.__files[filename]
304
 
            self.boss.call_command('buffermanager', 'file_closed',
305
 
                                   filename=filename)
 
443
    def view_closed(self, view):
 
444
        if view.unique_id in self.__documents:
 
445
            doc = self.__documents[view.unique_id]
 
446
            del self.__documents[view.unique_id]
 
447
            del self.__views[doc.unique_id]
 
448
            self.boss.call_command('buffermanager', 'document_closed',
 
449
                                   document=doc)
306
450
 
307
 
    def confirm_multi_view_controlbar_clicked_close(self, view):
308
 
        buff = view.buffer
 
451
    def view_confirm_close(self, view):
 
452
        buff = view.get_file_ops()
309
453
        
310
454
        # If buffer was not modified you can safely close it
311
455
        if not buff.get_modified():
313
457
        
314
458
        # When the buffer is new then we need to show the save dialog instead
315
459
        # of a save changes dialog:
316
 
        if buff.is_new:
 
460
        if buff.get_is_new():
317
461
            # XXX: this is utterly broken but there's no support for new files
318
462
            # either
319
 
            return False
 
463
            # well, there is now
 
464
            return True
320
465
        
321
466
        parent = self.get_window()
322
467
        files, response = hig.save_changes(
323
 
            [buff.filename],
 
468
            [buff.get_filename()],
324
469
            parent=parent
325
470
        )
326
471
        
327
472
        if response == gtk.RESPONSE_OK:
328
 
            buff.save()
329
 
            
 
473
            if not self._file_ops(buff, "save", "save"):
 
474
                return False
 
475
 
330
476
        return response in (gtk.RESPONSE_OK, gtk.RESPONSE_CLOSE)
331
477
 
332
478
            
418
564
    )
419
565
    def act_replace_all(self, action):
420
566
        """Replaces all matching words"""
 
567
    
 
568
    def act_select_line(self, action):
 
569
        self.cmd_select_line()
 
570
    
 
571
    def act_cut_line(self, action):
 
572
        self.cmd_cut_line()
 
573
    
 
574
    def act_select_word(self, action):
 
575
        self.cmd_select_word()
421
576
        
 
577
    def act_cut_word(self, action):
 
578
        self.cmd_cut_word()
422
579
 
 
580
    def act_select_sentence(self, action):
 
581
        self.cmd_select_sentence()
 
582
        
 
583
    def act_cut_sentence(self, action):
 
584
        self.cmd_cut_sentence()
 
585
        
 
586
    def act_select_paragraph(self, action):
 
587
        self.cmd_select_paragraph()
 
588
        
423
589
 
424
590
Service = culebra_editor