pgconsole/editor.py
changeset 10 f3a1b9792cc9
child 76 3a41b351b122
equal deleted inserted replaced
9:2fcc8ef0b97d 10:f3a1b9792cc9
       
     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