pgconsole/editor.py
author Radek Brich <radek.brich@devl.cz>
Thu, 31 Jan 2013 11:02:04 +0100
changeset 62 af637235ca81
parent 10 f3a1b9792cc9
child 76 3a41b351b122
permissions -rw-r--r--
Update loopquery: allow any number of queries, support reading parameters from config file.

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