pgconsole/editor.py
changeset 10 f3a1b9792cc9
child 76 3a41b351b122
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pgconsole/editor.py	Tue Aug 16 23:53:54 2011 +0200
@@ -0,0 +1,455 @@
+import gtk, pango
+import gtksourceview2 as gtksourceview
+from lxml import etree
+
+from panedext import HPanedExt
+from config import cfg
+
+
+class Editor(HPanedExt):
+    def __init__(self):
+        super(Editor, self).__init__()
+
+        self.view = gtksourceview.View()
+        self.view.connect('toggle-overwrite', self.on_toggle_overwrite)
+
+        vbox = gtk.VBox()
+        self.vbox1 = vbox
+        self.add1(vbox)
+
+        sw = gtk.ScrolledWindow()
+        sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+        sw.set_shadow_type(gtk.SHADOW_IN)
+        vbox.pack_start(sw)
+        tree = gtk.TreeView()
+        tree.set_headers_visible(False)
+        tree.get_selection().set_mode(gtk.SELECTION_BROWSE)
+        sw.add(tree)
+
+        model = gtk.ListStore(str, str, object, bool)  # title, filename, buffer, modified
+        tree.set_model(model)
+        cell = gtk.CellRendererPixbuf()
+        cell.set_property('stock-id', gtk.STOCK_SAVE)
+        column = gtk.TreeViewColumn("File", cell, visible=3)
+        tree.append_column(column)
+        column = gtk.TreeViewColumn("File", gtk.CellRendererText(), text=0)
+        tree.append_column(column)
+        tree.get_selection().connect('changed', self.item_change)
+        tree.set_property('can-focus', False)
+        self.filelist = tree
+
+
+        hbox = gtk.HBox()
+
+        img = gtk.Image()
+        img.set_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_SMALL_TOOLBAR)
+        btn = gtk.Button()
+        btn.set_relief(gtk.RELIEF_NONE)
+        btn.set_focus_on_click(False)
+        btn.set_image(img)
+        btn.connect('clicked', self.item_new)
+        hbox.pack_start(btn, expand=False)
+
+        img = gtk.Image()
+        img.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_SMALL_TOOLBAR)
+        btn = gtk.Button()
+        btn.set_relief(gtk.RELIEF_NONE)
+        btn.set_focus_on_click(False)
+        btn.set_image(img)
+        btn.connect('clicked', self.item_open)
+        hbox.pack_start(btn, expand=False)
+
+        img = gtk.Image()
+        img.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_SMALL_TOOLBAR)
+        btn = gtk.Button()
+        btn.set_relief(gtk.RELIEF_NONE)
+        btn.set_focus_on_click(False)
+        btn.set_image(img)
+        btn.connect('clicked', self.item_close)
+        hbox.pack_start(btn, expand=False)
+        hbox.connect('size-request', self.leftbuttons_size_request)
+
+        vbox.pack_start(hbox, expand=False)
+
+        vbox = gtk.VBox()
+        vbox.set_property("width-request", 200)
+        self.add2(vbox)
+        self.child_set_property(vbox, 'shrink', False)
+
+
+        # scroll
+        sw = gtk.ScrolledWindow()
+        sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+        sw.add(self.view)
+        vbox.pack_start(sw)
+
+
+        self.view.set_show_line_numbers(True)
+        self.view.set_smart_home_end(True)
+        #self.view.set_insert_spaces_instead_of_tabs(True)
+        self.view.set_property("tab-width", 4)
+        self.view.set_auto_indent(True)
+
+        # font
+        font_desc = pango.FontDescription('monospace')
+        if font_desc:
+            self.view.modify_font(font_desc)
+
+        # status bar
+        hbox = gtk.HBox()
+
+        self.st_file, fr1 = self.construct_status('SQL snippet')
+        self.st_file.set_ellipsize(pango.ELLIPSIZE_START)
+        self.st_insovr, fr2 = self.construct_status('INS')
+        self.st_linecol, fr3 = self.construct_status('Line: 0 Col: 0')
+
+
+        img = gtk.Image()
+        img.set_from_stock(gtk.STOCK_SAVE, gtk.ICON_SIZE_SMALL_TOOLBAR)
+        #save = img
+        save = gtk.Button()
+        save.set_relief(gtk.RELIEF_NONE)
+        save.set_focus_on_click(False)
+        save.set_image(img)
+        save.connect('clicked', self.item_save)
+        hbox.pack_start(save, expand=False)
+
+        hbox.pack_start(fr1, expand=True)
+        hbox.pack_start(fr2, expand=False)
+        hbox.pack_start(fr3, expand=False)
+
+        #sep = gtk.HSeparator()
+        #vbox.pack_start(sep, expand=False, fill=False, padding=0)
+
+        #align = gtk.Alignment()
+        #align.set_property("bottom-padding", 3)
+        #align.set_property("xscale", 1.0)
+        #align.add(hbox)
+
+        #frame = gtk.Frame()
+        #frame.add(hbox)
+        #frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+        vbox.pack_start(hbox, expand=False, padding=0)
+
+        self.load_nodes()
+        self.build_context_menu()
+
+
+    def build_context_menu(self):
+        menu = gtk.Menu()
+        item = gtk.ImageMenuItem(gtk.STOCK_SAVE, "Save")
+        item.connect("activate", self.item_save)
+        item.show()
+        menu.append(item)
+
+        item = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS, "Save as")
+        item.connect("activate", self.item_save_as)
+        item.show()
+        menu.append(item)
+
+        item = gtk.ImageMenuItem(gtk.STOCK_CLOSE, "Close")
+        item.connect("activate", self.item_close)
+        item.show()
+        menu.append(item)
+
+        item = gtk.SeparatorMenuItem()
+        item.show()
+        menu.append(item)
+
+        item = gtk.ImageMenuItem(gtk.STOCK_GO_UP, "Move up")
+        item.show()
+        menu.append(item)
+
+        item = gtk.ImageMenuItem(gtk.STOCK_GO_DOWN, "Move down")
+        item.show()
+        menu.append(item)
+
+        self.filelist_menu = menu
+
+        self.filelist.connect_object("button-press-event", self.on_filelist_button_press_event, menu)
+
+
+    def on_filelist_button_press_event(self, w, event):
+        if event.button == 3:
+            x = int(event.x)
+            y = int(event.y)
+            pathinfo = self.filelist.get_path_at_pos(x, y)
+            if pathinfo is not None:
+                path, col, cellx, celly = pathinfo
+                self.filelist.grab_focus()
+                self.filelist.set_cursor(path, col, 0)
+                self.filelist_menu.popup(None, None, None, event.button, event.time)
+            return True
+
+
+
+    def make_buffer(self):
+        buffer = gtksourceview.Buffer()
+        buffer.connect('mark-set', self.buffer_mark_set)
+        buffer.connect('changed', self.buffer_changed)
+
+        # style
+        mgr = gtksourceview.style_scheme_manager_get_default()
+        style_scheme = mgr.get_scheme('kate')
+        if style_scheme:
+            buffer.set_style_scheme(style_scheme)
+
+        # syntax
+        lngman = gtksourceview.language_manager_get_default()
+        langsql = lngman.get_language('sql')
+        buffer.set_language(langsql)
+        buffer.set_highlight_syntax(True)
+
+        return buffer
+
+
+    def load_nodes(self):
+        model = self.filelist.get_model()
+        sel = cfg.root.editor.nodes.get('selected')
+        for node in cfg.root.editor.nodes.node:
+            buffer = self.make_buffer()
+            name = node.get('name')
+            type = node.get('type')
+            filename = None
+            if type == 'text' and node.text:
+                buffer.set_text(node.text)
+            if type == 'file':
+                filename = node.text
+                content = open(filename).read()
+                buffer.set_text(content)
+            iter = model.append([name, filename, buffer, False])
+            if sel == name:
+                self.filelist.get_selection().select_iter(iter)
+
+
+    def set_text(self, text):
+        self.buffer.set_text(text)
+
+
+    def get_text(self):
+        start, end = self.buffer.get_bounds()
+        return self.buffer.get_text(start, end)
+
+
+    def get_selection(self):
+        bounds = self.buffer.get_selection_bounds()
+        if not bounds:
+            return None
+        return self.buffer.get_text(*bounds)
+
+
+    def construct_status(self, text):
+        st = gtk.Label(text)
+        st.set_property("single-line-mode", True)
+        st.set_property("xpad", 3)
+        st.set_alignment(0.0, 0.5)
+        fr = gtk.Frame()
+        fr.set_shadow_type(gtk.SHADOW_ETCHED_OUT)
+        fr.add(st)
+        return st, fr
+
+
+    def on_toggle_overwrite(self, w):
+        if not w.get_overwrite():
+            self.st_insovr.set_label('OVR')
+        else:
+            self.st_insovr.set_label('INS')
+
+
+    def buffer_mark_set(self, w, iter, textmark):
+        if textmark == w.get_insert():
+            line = iter.get_line()
+            col = iter.get_visible_line_offset()
+            self.st_linecol.set_label('Line: %d Col: %d' % (line + 1, col + 1))
+
+
+    def buffer_changed(self, w):
+        iter = w.get_iter_at_mark(w.get_insert())
+        line = iter.get_line()
+        col = iter.get_visible_line_offset()
+        self.st_linecol.set_label('Line: %d Col: %d' % (line + 1, col + 1))
+
+
+    def item_change(self, w):
+        model, sel = self.filelist.get_selection().get_selected_rows()
+        if not sel:
+            return
+        iter = model.get_iter(sel[0])
+        title, filename, self.buffer = model.get(iter, 0, 1, 2)
+        self.view.set_buffer(self.buffer)
+
+        if filename:
+            self.st_file.set_text(filename)
+        else:
+            self.st_file.set_text('SQL snippet')
+
+        # update config
+        cfg.root.editor.nodes.set('selected', title)
+
+
+    def probe_title(self, model, title):
+        iter = model.get_iter_first()
+        i = 1
+        while iter:
+            if model.get_value(iter, 0).split(' /')[0] == title:
+                new = title
+                if i > 1:
+                    new += ' /%d' % i
+                model.set_value(iter, 0, new)
+                i += 1
+            iter = model.iter_next(iter)
+        if i > 1:
+            title = '%s /%d' % (title, i)
+        return title
+
+
+    def item_new(self, w):
+        model = self.filelist.get_model()
+        title = 'Untitled'
+        title = self.probe_title(model, title)
+        buffer = self.make_buffer()
+        iter = model.append([title, None, buffer, False])
+        self.filelist.get_selection().select_iter(iter)
+
+        # update config
+        etree.SubElement(cfg.root.editor.nodes, 'node', type='text', name=title)
+        cfg.root.editor.nodes.set('selected', title)
+        cfg.save()
+
+
+    def item_open(self, w):
+        dialog = gtk.FileChooserDialog(title='Open file', action=gtk.FILE_CHOOSER_ACTION_OPEN,
+            buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
+        dialog.set_default_response(gtk.RESPONSE_OK)
+        dialog.set_select_multiple(True)
+
+        filter = gtk.FileFilter()
+        filter.set_name("SQL files")
+        filter.add_pattern("*.sql")
+        dialog.add_filter(filter)
+
+        filter = gtk.FileFilter()
+        filter.set_name("All files")
+        filter.add_pattern("*")
+        dialog.add_filter(filter)
+
+        response = dialog.run()
+        if response == gtk.RESPONSE_OK:
+            filenames = dialog.get_filenames()
+            for fname in filenames:
+                self.open_file(fname)
+
+        dialog.destroy()
+
+
+    def open_file(self, filename):
+        title = filename.rsplit('/', 1)[1]
+
+        model = self.filelist.get_model()
+        iter = model.get_iter_first()
+        i = 1
+        while iter:
+            if model.get_value(iter, 1) == filename:
+                # file already opened, select it
+                self.filelist.get_selection().select_iter(iter)
+                return
+            iter = model.iter_next(iter)
+
+        title = self.probe_title(model, title)
+
+        # add item
+        buffer = self.make_buffer()
+        buffer.set_text(open(filename).read())
+        iter = model.append([title, filename, buffer, False])
+        self.filelist.get_selection().select_iter(iter)
+
+        # update config
+        el = etree.SubElement(cfg.root.editor.nodes, 'node', type='file', name=title)
+        el._setText(filename)
+        cfg.root.editor.nodes.set('selected', title)
+        cfg.save()
+
+
+    def item_save(self, w):
+        model, sel = self.filelist.get_selection().get_selected_rows()
+        if not sel:
+            return
+        iter = model.get_iter(sel[0])
+
+        filename, buffer = model.get(iter, 1, 2)
+
+        data = buffer.get_text(*buffer.get_bounds())
+        open(filename, 'w').write(data)
+
+
+    def item_save_as(self, w):
+        model, sel = self.filelist.get_selection().get_selected_rows()
+        if not sel:
+            return
+        iter = model.get_iter(sel[0])
+
+        filename, buffer = model.get(iter, 1, 2)
+
+        path, file = filename.rsplit('/',1)
+
+        dialog = gtk.FileChooserDialog(title='Save as', action=gtk.FILE_CHOOSER_ACTION_SAVE,
+            buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_SAVE,gtk.RESPONSE_OK))
+        dialog.set_default_response(gtk.RESPONSE_OK)
+        dialog.set_current_folder(path)
+        dialog.set_current_name(file)
+
+        filter = gtk.FileFilter()
+        filter.set_name("SQL files")
+        filter.add_pattern("*.sql")
+        dialog.add_filter(filter)
+
+        filter = gtk.FileFilter()
+        filter.set_name("All files")
+        filter.add_pattern("*")
+        dialog.add_filter(filter)
+
+        response = dialog.run()
+        if response == gtk.RESPONSE_OK:
+            filename = dialog.get_filename()
+            data = buffer.get_text(*buffer.get_bounds())
+            open(filename, 'w').write(data)
+
+            title = filename.rsplit('/',1)[1]
+            title = self.probe_title(model, title)
+            model.set(iter, 0, title)
+            model.set(iter, 1, filename)
+            model.set(iter, 3, False) # modified
+
+        dialog.destroy()
+
+
+    def item_close(self, w):
+        model, sel = self.filelist.get_selection().get_selected_rows()
+        if not sel:
+            return
+        iter = model.get_iter(sel[0])
+        newiter = model.iter_next(iter)
+        if newiter is None and sel[0][0] > 0:
+            newiter = model.get_iter((sel[0][0]-1,))
+
+        title, buffer = model.get(iter, 0, 2)
+        #buffer.destroy()
+
+        model.remove(iter)
+
+        if newiter is None:
+            self.item_new(None)
+        else:
+            self.filelist.get_selection().select_iter(newiter)
+
+        # update config
+        el = cfg.root.editor.nodes.find('node[@name="%s"]' % title)
+        el.getparent().remove(el)
+        cfg.save()
+
+
+    def leftbuttons_size_request(self, w, request):
+        self.set_snap1(request[0])
+        return True
+