Refactor ToolBase to allow tool composition. Add TableSync tool (composited). Move more tools under pgtool.
from gi.repository import Gtk
from gi.repository import Pango
from gi.repository import GtkSource
from lxml import etree
from pgconsole.panedext import HPanedExt
from pgconsole.config import cfg
class Editor(HPanedExt):
def __init__(self):
super(Editor, self).__init__()
self.view = GtkSource.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.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
sw.set_shadow_type(Gtk.ShadowType.IN)
vbox.pack_start(sw, expand=True, fill=True, padding=0)
tree = Gtk.TreeView()
tree.set_headers_visible(False)
tree.get_selection().set_mode(Gtk.SelectionMode.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.IconSize.SMALL_TOOLBAR)
btn = Gtk.Button()
btn.set_relief(Gtk.ReliefStyle.NONE)
btn.set_focus_on_click(False)
btn.set_image(img)
btn.connect('clicked', self.item_new)
hbox.pack_start(btn, expand=False, fill=True, padding=0)
img = Gtk.Image()
img.set_from_stock(Gtk.STOCK_OPEN, Gtk.IconSize.SMALL_TOOLBAR)
btn = Gtk.Button()
btn.set_relief(Gtk.ReliefStyle.NONE)
btn.set_focus_on_click(False)
btn.set_image(img)
btn.connect('clicked', self.item_open)
hbox.pack_start(btn, expand=False, fill=True, padding=0)
img = Gtk.Image()
img.set_from_stock(Gtk.STOCK_CLOSE, Gtk.IconSize.SMALL_TOOLBAR)
btn = Gtk.Button()
btn.set_relief(Gtk.ReliefStyle.NONE)
btn.set_focus_on_click(False)
btn.set_image(img)
btn.connect('clicked', self.item_close)
hbox.pack_start(btn, expand=False, fill=True, padding=0)
hbox.connect('size-allocate', self.leftbuttons_size_request)
vbox.pack_start(hbox, expand=False, fill=True, padding=0)
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.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
sw.add(self.view)
vbox.pack_start(sw, expand=True, fill=True, padding=0)
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.EllipsizeMode.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.IconSize.SMALL_TOOLBAR)
#save = img
save = Gtk.Button()
save.set_relief(Gtk.ReliefStyle.NONE)
save.set_focus_on_click(False)
save.set_image(img)
save.connect('clicked', self.item_save)
hbox.pack_start(save, expand=False, fill=True, padding=0)
hbox.pack_start(fr1, expand=True, fill=True, padding=0)
hbox.pack_start(fr2, expand=False, fill=True, padding=0)
hbox.pack_start(fr3, expand=False, fill=True, padding=0)
#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, fill=True, padding=0)
self.load_nodes()
self.build_context_menu()
def build_context_menu(self):
menu = Gtk.Menu()
item = Gtk.ImageMenuItem(use_stock=True, label=Gtk.STOCK_SAVE)
item.connect("activate", self.item_save)
item.show()
menu.append(item)
item = Gtk.ImageMenuItem(use_stock=True, label=Gtk.STOCK_SAVE_AS)
item.connect("activate", self.item_save_as)
item.show()
menu.append(item)
item = Gtk.ImageMenuItem(use_stock=True, label=Gtk.STOCK_CLOSE)
item.connect("activate", self.item_close)
item.show()
menu.append(item)
item = Gtk.SeparatorMenuItem()
item.show()
menu.append(item)
item = Gtk.ImageMenuItem(use_stock=True, label=Gtk.STOCK_GO_UP)
item.show()
menu.append(item)
item = Gtk.ImageMenuItem(use_stock=True, label=Gtk.STOCK_GO_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, None, event.button, event.time)
return True
def make_buffer(self):
buffer = GtkSource.Buffer()
buffer.connect('mark-set', self.buffer_mark_set)
buffer.connect('changed', self.buffer_changed)
# style
mgr = GtkSource.StyleSchemeManager.get_default()
style_scheme = mgr.get_scheme('kate')
if style_scheme:
buffer.set_style_scheme(style_scheme)
# syntax
lngman = GtkSource.LanguageManager.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
try:
content = open(filename).read()
except IOError:
content = ''
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.ShadowType.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.FileChooserAction.OPEN,
buttons=(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_OPEN,Gtk.ResponseType.OK))
dialog.set_default_response(Gtk.ResponseType.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.ResponseType.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.FileChooserAction.SAVE,
buttons=(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,Gtk.STOCK_SAVE,Gtk.ResponseType.OK))
dialog.set_default_response(Gtk.ResponseType.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.ResponseType.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].get_indices()[0] > 0:
newiter = model.get_iter((sel[0].get_indices()[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.width)
return True