I recently revisited this topic and was able to cobble together a simple python example, which demonstrates one way to accomplish code folding in gtk:
#!/usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '3.0')
import signal
import sys,os
import cairo
from gi.repository import Gtk as gtk
from gi.repository import Gdk as gdk
from gi.repository import GObject as gobject
from gi.repository import GtkSource as gtksource
from gi.repository.GdkPixbuf import Pixbuf
theme = 'light' # [light/dark] type of gtk theme in use
class Editor:
def check_sigint_timer(self,timeout):
gobject.timeout_add_seconds(timeout, self.check_sigint)
def check_sigint(self):
return True
def quit_activated(self):
dialog = gtk.MessageDialog(parent=self.window, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.YES_NO)
dialog.set_title("Question")
dialog.set_position(gtk.WindowPosition.CENTER_ALWAYS)
dialog.set_markup("Are you sure you want to quit?")
response = dialog.run()
if response == gtk.ResponseType.YES:
dialog.destroy()
gtk.main_quit()
elif response == gtk.ResponseType.NO:
dialog.destroy()
def delete_event_cb(self, widget, data=None):
print "delete_event signal occurred"
self.quit_activated()
return True
def destroy_cb(self, widget, data=None):
print "destroy signal occurred"
self.quit_activated()
def signal_handler(self, signal, frame):
print '\nYou pressed Ctrl+C!, exiting'
gtk.main_quit()
def get_fold_minus_pixbuf(self):
w = 24
h = 24
surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, w, h)
cr = cairo.Context (surface)
if theme == 'dark':
cr.set_source_rgb(0.94, 0.94, 0.94)
else:
cr.set_source_rgb(0., 0., 0.)
cr.rectangle(0,0,w,h)
cr.fill()
if theme == 'dark':
cr.set_source_rgb(0., 0., 0.)
else:
cr.set_source_rgb(0.94, 0.94, 0.94)
cr.move_to (w/5.,h/2.)
cr.line_to (w*0.8,h/2.)
cr.stroke ()
pixbuf = gdk.pixbuf_get_from_surface(surface,0,0,w,h)
return pixbuf
def get_fold_plus_pixbuf(self):
w = 24
h = 24
surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, w, h)
cr = cairo.Context (surface)
if theme == 'dark':
cr.set_source_rgb(0.94, 0.94, 0.94)
else:
cr.set_source_rgb(0., 0., 0.)
cr.rectangle(0,0,w,h)
cr.fill()
if theme == 'dark':
cr.set_source_rgb(0., 0., 0.)
else:
cr.set_source_rgb(0.94, 0.94, 0.94)
cr.move_to (w/5.,h/2.)
cr.line_to (w*0.8,h/2.)
cr.stroke ()
cr.move_to (w/2.,h/5.)
cr.line_to (w/2.,h*0.8)
cr.stroke ()
pixbuf = gdk.pixbuf_get_from_surface(surface,0,0,w,h)
return pixbuf
def get_fold_vert_pixbuf(self):
w = 24
h = 24
surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, w, h)
cr = cairo.Context (surface)
if theme == 'dark':
cr.set_source_rgb(1., 1., 1.)
else:
cr.set_source_rgb(0., 0., 0.)
cr.move_to (w/2,0)
cr.line_to (w/2,h)
cr.stroke ()
pixbuf = gdk.pixbuf_get_from_surface(surface,0,0,w,h)
return pixbuf
def get_fold_end_pixbuf(self):
w = 24
h = 24
surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, w, h)
cr = cairo.Context (surface)
if theme == 'dark':
cr.set_source_rgb(1., 1., 1.)
else:
cr.set_source_rgb(0., 0., 0.)
cr.move_to (w/2,0)
cr.line_to (w/2,h/2)
cr.line_to (w,h/2)
cr.stroke ()
pixbuf = gdk.pixbuf_get_from_surface(surface,0,0,w,h)
return pixbuf
def place_mark(self,mtype,line):
iter = self.buffer.get_iter_at_line (line)
if mtype == '+':
self.buffer.create_source_mark(None, "fold_plus", iter)
elif mtype == '-':
self.buffer.create_source_mark(None, "fold_minus", iter)
elif mtype == '|':
self.buffer.create_source_mark(None, "fold_vert", iter)
elif mtype == 'end':
self.buffer.create_source_mark(None, "fold_end", iter)
def create_marks(self):
pixbuf = self.get_fold_plus_pixbuf()
mark_attr = gtksource.MarkAttributes.new()
mark_attr.set_pixbuf(pixbuf)
self.view.set_mark_attributes("fold_plus",mark_attr,0)
pixbuf = self.get_fold_minus_pixbuf()
mark_attr = gtksource.MarkAttributes.new()
mark_attr.set_pixbuf(pixbuf)
self.view.set_mark_attributes("fold_minus",mark_attr,0)
pixbuf = self.get_fold_vert_pixbuf()
mark_attr = gtksource.MarkAttributes.new()
mark_attr.set_pixbuf(pixbuf)
self.view.set_mark_attributes("fold_vert",mark_attr,0)
pixbuf = self.get_fold_end_pixbuf()
mark_attr = gtksource.MarkAttributes.new()
mark_attr.set_pixbuf(pixbuf)
self.view.set_mark_attributes("fold_end",mark_attr,0)
def mark_activated_cb(self,view,iter,event):
line = iter.get_line()
marks = self.buffer.get_source_marks_at_iter(iter)
for mark in marks:
cat = mark.get_category()
if cat == 'fold_minus':
start = self.buffer.get_iter_at_line (self.block[0]+1)
end = self.buffer.get_iter_at_line (self.block[1]+1)
self.buffer.apply_tag_by_name("invisible",start,end)
# remove the existing mark, then create the opposite mark
start = self.buffer.get_iter_at_line (self.block[0])
self.buffer.remove_source_marks(start,start)
self.place_mark('+',self.block[0])
elif cat == 'fold_plus':
start = self.buffer.get_iter_at_line (self.block[0]+1)
end = self.buffer.get_iter_at_line (self.block[1]+1)
self.buffer.remove_all_tags(start,end)
# remove the existing mark, then create the opposite mark
start = self.buffer.get_iter_at_line (self.block[0])
self.buffer.remove_source_marks(start,start)
self.place_mark('-',self.block[0])
def __init__(self):
signal.signal(signal.SIGINT, self.signal_handler)
self.window = gtk.Window(gtk.WindowType.TOPLEVEL)
self.window.connect("delete_event", self.delete_event_cb)
self.window.connect("destroy", self.destroy_cb)
self.window.set_border_width(10)
self.window.maximize()
self.view = gtksource.View()
self.view.connect("line-mark-activated",self.mark_activated_cb)
self.buffer = self.view.get_buffer()
self.view.set_show_line_numbers(True)
self.view.set_show_line_marks(True)
sw = gtk.ScrolledWindow()
sw.add(self.view)
self.window.add(sw)
path = sys.argv[0]
txt = open(path).read()
self.buffer.set_text(txt)
lm = gtksource.LanguageManager.new()
lang = lm.guess_language(path)
self.buffer.set_highlight_syntax(True)
self.buffer.set_language(lang)
self.buffer.create_tag("invisible",invisible=True)
self.block = [25,35]
self.create_marks()
self.place_mark('-',self.block[0])
for i in xrange(self.block[0],self.block[1]):
self.place_mark('|',i)
self.place_mark('end',self.block[1])
self.window.show_all()
self.check_sigint_timer(1)
def main(self):
gtk.main()
if __name__ == "__main__":
ed = Editor()
ed.main()
Be sure and change the theme variable to reflect the type of gtk3 theme you happen to be using.
It would be greatly appreciated if anyone can show how to eliminate the line gaps in the vertical line drawn in the gutter.