|
1 import gtk, pango |
|
2 import gtksourceview2 as gtksourceview |
|
3 from lxml import etree |
|
4 |
|
5 from panedext import HPanedExt |
|
6 from config import cfg |
|
7 |
|
8 |
|
9 class Editor(HPanedExt): |
|
10 def __init__(self): |
|
11 super(Editor, self).__init__() |
|
12 |
|
13 self.view = gtksourceview.View() |
|
14 self.view.connect('toggle-overwrite', self.on_toggle_overwrite) |
|
15 |
|
16 vbox = gtk.VBox() |
|
17 self.vbox1 = vbox |
|
18 self.add1(vbox) |
|
19 |
|
20 sw = gtk.ScrolledWindow() |
|
21 sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) |
|
22 sw.set_shadow_type(gtk.SHADOW_IN) |
|
23 vbox.pack_start(sw) |
|
24 tree = gtk.TreeView() |
|
25 tree.set_headers_visible(False) |
|
26 tree.get_selection().set_mode(gtk.SELECTION_BROWSE) |
|
27 sw.add(tree) |
|
28 |
|
29 model = gtk.ListStore(str, str, object, bool) # title, filename, buffer, modified |
|
30 tree.set_model(model) |
|
31 cell = gtk.CellRendererPixbuf() |
|
32 cell.set_property('stock-id', gtk.STOCK_SAVE) |
|
33 column = gtk.TreeViewColumn("File", cell, visible=3) |
|
34 tree.append_column(column) |
|
35 column = gtk.TreeViewColumn("File", gtk.CellRendererText(), text=0) |
|
36 tree.append_column(column) |
|
37 tree.get_selection().connect('changed', self.item_change) |
|
38 tree.set_property('can-focus', False) |
|
39 self.filelist = tree |
|
40 |
|
41 |
|
42 hbox = gtk.HBox() |
|
43 |
|
44 img = gtk.Image() |
|
45 img.set_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_SMALL_TOOLBAR) |
|
46 btn = gtk.Button() |
|
47 btn.set_relief(gtk.RELIEF_NONE) |
|
48 btn.set_focus_on_click(False) |
|
49 btn.set_image(img) |
|
50 btn.connect('clicked', self.item_new) |
|
51 hbox.pack_start(btn, expand=False) |
|
52 |
|
53 img = gtk.Image() |
|
54 img.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_SMALL_TOOLBAR) |
|
55 btn = gtk.Button() |
|
56 btn.set_relief(gtk.RELIEF_NONE) |
|
57 btn.set_focus_on_click(False) |
|
58 btn.set_image(img) |
|
59 btn.connect('clicked', self.item_open) |
|
60 hbox.pack_start(btn, expand=False) |
|
61 |
|
62 img = gtk.Image() |
|
63 img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_SMALL_TOOLBAR) |
|
64 btn = gtk.Button() |
|
65 btn.set_relief(gtk.RELIEF_NONE) |
|
66 btn.set_focus_on_click(False) |
|
67 btn.set_image(img) |
|
68 btn.connect('clicked', self.item_close) |
|
69 hbox.pack_start(btn, expand=False) |
|
70 hbox.connect('size-request', self.leftbuttons_size_request) |
|
71 |
|
72 vbox.pack_start(hbox, expand=False) |
|
73 |
|
74 vbox = gtk.VBox() |
|
75 vbox.set_property("width-request", 200) |
|
76 self.add2(vbox) |
|
77 self.child_set_property(vbox, 'shrink', False) |
|
78 |
|
79 |
|
80 # scroll |
|
81 sw = gtk.ScrolledWindow() |
|
82 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) |
|
83 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) |
|
84 sw.add(self.view) |
|
85 vbox.pack_start(sw) |
|
86 |
|
87 |
|
88 self.view.set_show_line_numbers(True) |
|
89 self.view.set_smart_home_end(True) |
|
90 #self.view.set_insert_spaces_instead_of_tabs(True) |
|
91 self.view.set_property("tab-width", 4) |
|
92 self.view.set_auto_indent(True) |
|
93 |
|
94 # font |
|
95 font_desc = pango.FontDescription('monospace') |
|
96 if font_desc: |
|
97 self.view.modify_font(font_desc) |
|
98 |
|
99 # status bar |
|
100 hbox = gtk.HBox() |
|
101 |
|
102 self.st_file, fr1 = self.construct_status('SQL snippet') |
|
103 self.st_file.set_ellipsize(pango.ELLIPSIZE_START) |
|
104 self.st_insovr, fr2 = self.construct_status('INS') |
|
105 self.st_linecol, fr3 = self.construct_status('Line: 0 Col: 0') |
|
106 |
|
107 |
|
108 img = gtk.Image() |
|
109 img.set_from_stock(gtk.STOCK_SAVE, gtk.ICON_SIZE_SMALL_TOOLBAR) |
|
110 #save = img |
|
111 save = gtk.Button() |
|
112 save.set_relief(gtk.RELIEF_NONE) |
|
113 save.set_focus_on_click(False) |
|
114 save.set_image(img) |
|
115 save.connect('clicked', self.item_save) |
|
116 hbox.pack_start(save, expand=False) |
|
117 |
|
118 hbox.pack_start(fr1, expand=True) |
|
119 hbox.pack_start(fr2, expand=False) |
|
120 hbox.pack_start(fr3, expand=False) |
|
121 |
|
122 #sep = gtk.HSeparator() |
|
123 #vbox.pack_start(sep, expand=False, fill=False, padding=0) |
|
124 |
|
125 #align = gtk.Alignment() |
|
126 #align.set_property("bottom-padding", 3) |
|
127 #align.set_property("xscale", 1.0) |
|
128 #align.add(hbox) |
|
129 |
|
130 #frame = gtk.Frame() |
|
131 #frame.add(hbox) |
|
132 #frame.set_shadow_type(gtk.SHADOW_ETCHED_IN) |
|
133 vbox.pack_start(hbox, expand=False, padding=0) |
|
134 |
|
135 self.load_nodes() |
|
136 self.build_context_menu() |
|
137 |
|
138 |
|
139 def build_context_menu(self): |
|
140 menu = gtk.Menu() |
|
141 item = gtk.ImageMenuItem(gtk.STOCK_SAVE, "Save") |
|
142 item.connect("activate", self.item_save) |
|
143 item.show() |
|
144 menu.append(item) |
|
145 |
|
146 item = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS, "Save as") |
|
147 item.connect("activate", self.item_save_as) |
|
148 item.show() |
|
149 menu.append(item) |
|
150 |
|
151 item = gtk.ImageMenuItem(gtk.STOCK_CLOSE, "Close") |
|
152 item.connect("activate", self.item_close) |
|
153 item.show() |
|
154 menu.append(item) |
|
155 |
|
156 item = gtk.SeparatorMenuItem() |
|
157 item.show() |
|
158 menu.append(item) |
|
159 |
|
160 item = gtk.ImageMenuItem(gtk.STOCK_GO_UP, "Move up") |
|
161 item.show() |
|
162 menu.append(item) |
|
163 |
|
164 item = gtk.ImageMenuItem(gtk.STOCK_GO_DOWN, "Move down") |
|
165 item.show() |
|
166 menu.append(item) |
|
167 |
|
168 self.filelist_menu = menu |
|
169 |
|
170 self.filelist.connect_object("button-press-event", self.on_filelist_button_press_event, menu) |
|
171 |
|
172 |
|
173 def on_filelist_button_press_event(self, w, event): |
|
174 if event.button == 3: |
|
175 x = int(event.x) |
|
176 y = int(event.y) |
|
177 pathinfo = self.filelist.get_path_at_pos(x, y) |
|
178 if pathinfo is not None: |
|
179 path, col, cellx, celly = pathinfo |
|
180 self.filelist.grab_focus() |
|
181 self.filelist.set_cursor(path, col, 0) |
|
182 self.filelist_menu.popup(None, None, None, event.button, event.time) |
|
183 return True |
|
184 |
|
185 |
|
186 |
|
187 def make_buffer(self): |
|
188 buffer = gtksourceview.Buffer() |
|
189 buffer.connect('mark-set', self.buffer_mark_set) |
|
190 buffer.connect('changed', self.buffer_changed) |
|
191 |
|
192 # style |
|
193 mgr = gtksourceview.style_scheme_manager_get_default() |
|
194 style_scheme = mgr.get_scheme('kate') |
|
195 if style_scheme: |
|
196 buffer.set_style_scheme(style_scheme) |
|
197 |
|
198 # syntax |
|
199 lngman = gtksourceview.language_manager_get_default() |
|
200 langsql = lngman.get_language('sql') |
|
201 buffer.set_language(langsql) |
|
202 buffer.set_highlight_syntax(True) |
|
203 |
|
204 return buffer |
|
205 |
|
206 |
|
207 def load_nodes(self): |
|
208 model = self.filelist.get_model() |
|
209 sel = cfg.root.editor.nodes.get('selected') |
|
210 for node in cfg.root.editor.nodes.node: |
|
211 buffer = self.make_buffer() |
|
212 name = node.get('name') |
|
213 type = node.get('type') |
|
214 filename = None |
|
215 if type == 'text' and node.text: |
|
216 buffer.set_text(node.text) |
|
217 if type == 'file': |
|
218 filename = node.text |
|
219 content = open(filename).read() |
|
220 buffer.set_text(content) |
|
221 iter = model.append([name, filename, buffer, False]) |
|
222 if sel == name: |
|
223 self.filelist.get_selection().select_iter(iter) |
|
224 |
|
225 |
|
226 def set_text(self, text): |
|
227 self.buffer.set_text(text) |
|
228 |
|
229 |
|
230 def get_text(self): |
|
231 start, end = self.buffer.get_bounds() |
|
232 return self.buffer.get_text(start, end) |
|
233 |
|
234 |
|
235 def get_selection(self): |
|
236 bounds = self.buffer.get_selection_bounds() |
|
237 if not bounds: |
|
238 return None |
|
239 return self.buffer.get_text(*bounds) |
|
240 |
|
241 |
|
242 def construct_status(self, text): |
|
243 st = gtk.Label(text) |
|
244 st.set_property("single-line-mode", True) |
|
245 st.set_property("xpad", 3) |
|
246 st.set_alignment(0.0, 0.5) |
|
247 fr = gtk.Frame() |
|
248 fr.set_shadow_type(gtk.SHADOW_ETCHED_OUT) |
|
249 fr.add(st) |
|
250 return st, fr |
|
251 |
|
252 |
|
253 def on_toggle_overwrite(self, w): |
|
254 if not w.get_overwrite(): |
|
255 self.st_insovr.set_label('OVR') |
|
256 else: |
|
257 self.st_insovr.set_label('INS') |
|
258 |
|
259 |
|
260 def buffer_mark_set(self, w, iter, textmark): |
|
261 if textmark == w.get_insert(): |
|
262 line = iter.get_line() |
|
263 col = iter.get_visible_line_offset() |
|
264 self.st_linecol.set_label('Line: %d Col: %d' % (line + 1, col + 1)) |
|
265 |
|
266 |
|
267 def buffer_changed(self, w): |
|
268 iter = w.get_iter_at_mark(w.get_insert()) |
|
269 line = iter.get_line() |
|
270 col = iter.get_visible_line_offset() |
|
271 self.st_linecol.set_label('Line: %d Col: %d' % (line + 1, col + 1)) |
|
272 |
|
273 |
|
274 def item_change(self, w): |
|
275 model, sel = self.filelist.get_selection().get_selected_rows() |
|
276 if not sel: |
|
277 return |
|
278 iter = model.get_iter(sel[0]) |
|
279 title, filename, self.buffer = model.get(iter, 0, 1, 2) |
|
280 self.view.set_buffer(self.buffer) |
|
281 |
|
282 if filename: |
|
283 self.st_file.set_text(filename) |
|
284 else: |
|
285 self.st_file.set_text('SQL snippet') |
|
286 |
|
287 # update config |
|
288 cfg.root.editor.nodes.set('selected', title) |
|
289 |
|
290 |
|
291 def probe_title(self, model, title): |
|
292 iter = model.get_iter_first() |
|
293 i = 1 |
|
294 while iter: |
|
295 if model.get_value(iter, 0).split(' /')[0] == title: |
|
296 new = title |
|
297 if i > 1: |
|
298 new += ' /%d' % i |
|
299 model.set_value(iter, 0, new) |
|
300 i += 1 |
|
301 iter = model.iter_next(iter) |
|
302 if i > 1: |
|
303 title = '%s /%d' % (title, i) |
|
304 return title |
|
305 |
|
306 |
|
307 def item_new(self, w): |
|
308 model = self.filelist.get_model() |
|
309 title = 'Untitled' |
|
310 title = self.probe_title(model, title) |
|
311 buffer = self.make_buffer() |
|
312 iter = model.append([title, None, buffer, False]) |
|
313 self.filelist.get_selection().select_iter(iter) |
|
314 |
|
315 # update config |
|
316 etree.SubElement(cfg.root.editor.nodes, 'node', type='text', name=title) |
|
317 cfg.root.editor.nodes.set('selected', title) |
|
318 cfg.save() |
|
319 |
|
320 |
|
321 def item_open(self, w): |
|
322 dialog = gtk.FileChooserDialog(title='Open file', action=gtk.FILE_CHOOSER_ACTION_OPEN, |
|
323 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) |
|
324 dialog.set_default_response(gtk.RESPONSE_OK) |
|
325 dialog.set_select_multiple(True) |
|
326 |
|
327 filter = gtk.FileFilter() |
|
328 filter.set_name("SQL files") |
|
329 filter.add_pattern("*.sql") |
|
330 dialog.add_filter(filter) |
|
331 |
|
332 filter = gtk.FileFilter() |
|
333 filter.set_name("All files") |
|
334 filter.add_pattern("*") |
|
335 dialog.add_filter(filter) |
|
336 |
|
337 response = dialog.run() |
|
338 if response == gtk.RESPONSE_OK: |
|
339 filenames = dialog.get_filenames() |
|
340 for fname in filenames: |
|
341 self.open_file(fname) |
|
342 |
|
343 dialog.destroy() |
|
344 |
|
345 |
|
346 def open_file(self, filename): |
|
347 title = filename.rsplit('/', 1)[1] |
|
348 |
|
349 model = self.filelist.get_model() |
|
350 iter = model.get_iter_first() |
|
351 i = 1 |
|
352 while iter: |
|
353 if model.get_value(iter, 1) == filename: |
|
354 # file already opened, select it |
|
355 self.filelist.get_selection().select_iter(iter) |
|
356 return |
|
357 iter = model.iter_next(iter) |
|
358 |
|
359 title = self.probe_title(model, title) |
|
360 |
|
361 # add item |
|
362 buffer = self.make_buffer() |
|
363 buffer.set_text(open(filename).read()) |
|
364 iter = model.append([title, filename, buffer, False]) |
|
365 self.filelist.get_selection().select_iter(iter) |
|
366 |
|
367 # update config |
|
368 el = etree.SubElement(cfg.root.editor.nodes, 'node', type='file', name=title) |
|
369 el._setText(filename) |
|
370 cfg.root.editor.nodes.set('selected', title) |
|
371 cfg.save() |
|
372 |
|
373 |
|
374 def item_save(self, w): |
|
375 model, sel = self.filelist.get_selection().get_selected_rows() |
|
376 if not sel: |
|
377 return |
|
378 iter = model.get_iter(sel[0]) |
|
379 |
|
380 filename, buffer = model.get(iter, 1, 2) |
|
381 |
|
382 data = buffer.get_text(*buffer.get_bounds()) |
|
383 open(filename, 'w').write(data) |
|
384 |
|
385 |
|
386 def item_save_as(self, w): |
|
387 model, sel = self.filelist.get_selection().get_selected_rows() |
|
388 if not sel: |
|
389 return |
|
390 iter = model.get_iter(sel[0]) |
|
391 |
|
392 filename, buffer = model.get(iter, 1, 2) |
|
393 |
|
394 path, file = filename.rsplit('/',1) |
|
395 |
|
396 dialog = gtk.FileChooserDialog(title='Save as', action=gtk.FILE_CHOOSER_ACTION_SAVE, |
|
397 buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK)) |
|
398 dialog.set_default_response(gtk.RESPONSE_OK) |
|
399 dialog.set_current_folder(path) |
|
400 dialog.set_current_name(file) |
|
401 |
|
402 filter = gtk.FileFilter() |
|
403 filter.set_name("SQL files") |
|
404 filter.add_pattern("*.sql") |
|
405 dialog.add_filter(filter) |
|
406 |
|
407 filter = gtk.FileFilter() |
|
408 filter.set_name("All files") |
|
409 filter.add_pattern("*") |
|
410 dialog.add_filter(filter) |
|
411 |
|
412 response = dialog.run() |
|
413 if response == gtk.RESPONSE_OK: |
|
414 filename = dialog.get_filename() |
|
415 data = buffer.get_text(*buffer.get_bounds()) |
|
416 open(filename, 'w').write(data) |
|
417 |
|
418 title = filename.rsplit('/',1)[1] |
|
419 title = self.probe_title(model, title) |
|
420 model.set(iter, 0, title) |
|
421 model.set(iter, 1, filename) |
|
422 model.set(iter, 3, False) # modified |
|
423 |
|
424 dialog.destroy() |
|
425 |
|
426 |
|
427 def item_close(self, w): |
|
428 model, sel = self.filelist.get_selection().get_selected_rows() |
|
429 if not sel: |
|
430 return |
|
431 iter = model.get_iter(sel[0]) |
|
432 newiter = model.iter_next(iter) |
|
433 if newiter is None and sel[0][0] > 0: |
|
434 newiter = model.get_iter((sel[0][0]-1,)) |
|
435 |
|
436 title, buffer = model.get(iter, 0, 2) |
|
437 #buffer.destroy() |
|
438 |
|
439 model.remove(iter) |
|
440 |
|
441 if newiter is None: |
|
442 self.item_new(None) |
|
443 else: |
|
444 self.filelist.get_selection().select_iter(newiter) |
|
445 |
|
446 # update config |
|
447 el = cfg.root.editor.nodes.find('node[@name="%s"]' % title) |
|
448 el.getparent().remove(el) |
|
449 cfg.save() |
|
450 |
|
451 |
|
452 def leftbuttons_size_request(self, w, request): |
|
453 self.set_snap1(request[0]) |
|
454 return True |
|
455 |