gtksourceview3/tests/test-widget.py

590 lines
23 KiB
Python
Raw Permalink Normal View History

2022-05-14 03:31:02 +08:00
#!/usr/bin/env python
# -*- Mode: Python; py-indent-offset: 4 -*-
# vim: tabstop=4 shiftwidth=4 expandtab
# WARNING: test-widget.c is more complete and is better maintained.
import os, os.path
import sys
import gi
from gi.repository import Gio, GObject, Pango, Gtk, GdkPixbuf, Gdk, GtkSource
ui_description = """
<ui>
<menubar name=\"MainMenu\">
<menu action=\"FileMenu\">
<menuitem action=\"Open\"/>
<menuitem action=\"Print\"/>
<menuitem action=\"Find\"/>
<menuitem action=\"Replace\"/>
<separator/>
<menuitem action=\"Quit\"/>
</menu>
<menu action=\"ViewMenu\">
<menuitem action=\"NewView\"/>
<separator/>
<menuitem action=\"HlSyntax\"/>
<menuitem action=\"HlBracket\"/>
<menuitem action=\"ShowNumbers\"/>
<menuitem action=\"ShowMarks\"/>
<menuitem action=\"ShowMargin\"/>
<menuitem action=\"HlLine\"/>
<menuitem action=\"DrawSpaces\"/>
<menuitem action=\"WrapLines\"/>
<separator/>
<menuitem action=\"AutoIndent\"/>
<menuitem action=\"InsertSpaces\"/>
<separator/>
<menu action=\"TabWidth\">
<menuitem action=\"TabWidth4\"/>
<menuitem action=\"TabWidth6\"/>
<menuitem action=\"TabWidth8\"/>
<menuitem action=\"TabWidth10\"/>
<menuitem action=\"TabWidth12\"/>
</menu>
<menu action=\"IndentWidth\">
<menuitem action=\"IndentWidthUnset\"/>
<menuitem action=\"IndentWidth4\"/>
<menuitem action=\"IndentWidth6\"/>
<menuitem action=\"IndentWidth8\"/>
<menuitem action=\"IndentWidth10\"/>
<menuitem action=\"IndentWidth12\"/>
</menu>
<separator/>
<menu action=\"SmartHomeEnd\">
<menuitem action=\"SmartHomeEndDisabled\"/>
<menuitem action=\"SmartHomeEndBefore\"/>
<menuitem action=\"SmartHomeEndAfter\"/>
<menuitem action=\"SmartHomeEndAlways\"/>
</menu>
<separator/>
<menuitem action=\"ForwardString\"/>
<menuitem action=\"BackwardString\"/>
</menu>
<menu action=\"HelpMenu\">
<menuitem action=\"About\"/>
</menu>
</menubar>
</ui>
"""
class AboutDialog(Gtk.AboutDialog):
def __init__(self, parent):
Gtk.AboutDialog.__init__(self)
self.set_name('GtkSourceView Test')
self.set_copyright('Copyright (c) 2010 Ignacio Casal Quinteiro')
self.set_website_label('https://wiki.gnome.org/Projects/GtkSourceView')
self.set_authors(['Ignacio Casal Quinteiro', 'Paolo Borelli'])
self.set_transient_for(parent)
self.connect("response", lambda d, r: d.destroy())
class SearchDialog(Gtk.Dialog):
def __init__(self, parent, replace, what, replacement):
if replace:
title = "Replace"
else:
title = "Find"
Gtk.Dialog.__init__(self, title, parent, Gtk.DialogFlags.MODAL,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self._search_widget = Gtk.Entry()
if what:
self._search_widget.set_text(what)
self._search_widget.set_activates_default(True)
self._search_widget.show()
print(self.get_content_area())
self.get_content_area().pack_start(self._search_widget, True, True, 0)
if replace:
self._replace_widget = Gtk.Entry()
if replacement:
self._replace_widget.set_text(replacement)
self._replace_widget.set_activates_default(True)
self._replace_widget.show()
self.get_content_area().pack_start(self._replace_widget, True, True, 0)
self._case_sensitive = Gtk.CheckButton.new_with_label("Case sensitive")
self._case_sensitive.show()
self.get_content_area().pack_start(self._case_sensitive, False, False, 0)
def run_search(self):
while True:
if self.run() != Gtk.ResponseType.OK:
self.hide()
return False
if self._search_widget.get_text() != "":
break
self.hide()
return True
def is_case_sensitive(self):
return self._case_sensitive.get_active()
def get_search_text(self):
return self._search_widget.get_text()
def get_replace_text(self):
return self._replace_widget.get_text()
class Window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.MARK_TYPE_1 = "one"
self.MARK_TYPE_2 = "two"
self.set_title('GtkSourceView Demo')
self.set_icon_name('text-editor')
self.set_default_size(500, 500)
self.connect_after('destroy', _quit)
self._vbox = Gtk.VBox()
self.add(self._vbox)
sw = Gtk.ScrolledWindow(hadjustment=None,
vadjustment=None)
sw.set_shadow_type(Gtk.ShadowType.IN)
self._buf = GtkSource.Buffer()
self._view = GtkSource.View.new_with_buffer(self._buf)
self.insert_menu()
mgr = GtkSource.StyleSchemeManager.get_default()
style_scheme = mgr.get_scheme('classic')
if style_scheme:
self._buf.set_style_scheme(style_scheme)
self._vbox.pack_start(sw, True, True, 0)
sw.add(self._view)
self._pos_label = Gtk.Label()
self._vbox.pack_end(self._pos_label, False, False, 0)
self.add_source_mark_pixbufs()
self._buf.connect("mark-set", self.move_cursor_cb, None)
self._view.connect("line-mark-activated", self.line_mark_activated, None)
self._buf.connect("bracket-matched", self.bracket_matched, None);
def insert_menu(self):
action_group = Gtk.ActionGroup("GtkSourceViewActions")
action_group.add_actions([("FileMenu", None, "_File", None, None, None),
("Open", Gtk.STOCK_OPEN, "_Open", "<control>O",
"Open a file", self.open_file_cb),
("Print", Gtk.STOCK_PRINT, "_Print", "<control>P",
"Print the current file", self.print_file_cb),
("Find", Gtk.STOCK_FIND, "_Find", "<control>F",
"Find", self.find_cb),
("Replace", Gtk.STOCK_FIND_AND_REPLACE, "Search and _Replace", "<control>R",
"Search and replace", self.replace_cb),
("Quit", Gtk.STOCK_QUIT, "_Quit", "<control>Q",
"Exit the application", self.quit_cb),
("ViewMenu", None, "_View", None, None, None),
("NewView", Gtk.STOCK_NEW, "_New View", None,
"Create a new view of the file", self.new_view_cb),
("TabWidth", None, "_Tab Width", None, None, None),
("IndentWidth", None, "I_ndent Width", None, None, None),
("SmartHomeEnd", None, "_Smart Home/End", None, None, None),
("ForwardString", None, "_Forward to string toggle", "<control>S",
"Forward to the start or end of the next string", self.forward_string_cb),
("BackwardString", None, "_Backward to string toggle", "<control><shift>S",
"Backward to the start or end of the next string", self.backward_string_cb),
("HelpMenu", None, "_Help", None, None, None),
("About", Gtk.STOCK_ABOUT, "_About...", None,
"About GtkSourceView Test Widget", self.about_cb)])
action_group.add_toggle_actions([("HlSyntax", None, "Highlight _Syntax", None,
"Toggle syntax highlighting", self.hl_syntax_toggled_cb),
("HlBracket", None, "Highlight Matching _Bracket", None,
"Toggle highlighting of matching bracket", self.hl_bracket_toggled_cb),
("ShowNumbers", None, "Show _Line Numbers", None,
"Toggle visibility of line numbers in the left margin", self.numbers_toggled_cb),
("ShowMarks", None, "Show Line _Marks", None,
"Toggle visibility of marks in the left margin", self.marks_toggled_cb),
("ShowMargin", None, "Show Right M_argin", None,
"Toggle visibility of right margin indicator", self.margin_toggled_cb),
("HlLine", None, "_Highlight Current Line", None,
"Toggle highlighting of current line", self.hl_line_toggled_cb),
("DrawSpaces", None, "_Draw Spaces", None,
"Draw Spaces", self.draw_spaces_toggled_cb),
("WrapLines", None, "_Wrap Lines", None,
"Toggle line wrapping", self.wrap_lines_toggled_cb),
("AutoIndent", None, "Enable _Auto Indent", None,
"Toggle automatic auto indentation of text", self.auto_indent_toggled_cb),
("InsertSpaces", None, "Insert _Spaces Instead of Tabs", None,
"Whether to insert space characters when inserting tabulations",
self.insert_spaces_toggled_cb)])
action_group.add_radio_actions([("TabWidth4", None, "4", None,
"Set tabulation width to 4 spaces", 4),
("TabWidth6", None, "6", None,
"Set tabulation width to 6 spaces", 6),
("TabWidth8", None, "8", None,
"Set tabulation width to 8 spaces", 8),
("TabWidth10", None, "10", None,
"Set tabulation width to 10 spaces", 10),
("TabWidth12", None, "12", None,
"Set tabulation width to 12 spaces", 12)],
-1, self.tabs_toggled_cb)
action_group.add_radio_actions([("IndentWidthUnset", None, "Use Tab Width", None,
"Set indent width same as tab width", -1),
("IndentWidth4", None, "4", None,
"Set indent width to 4 spaces", 4),
("IndentWidth6", None, "6", None,
"Set indent width to 6 spaces", 6),
("IndentWidth8", None, "8", None,
"Set indent width to 8 spaces", 8),
("IndentWidth10", None, "10", None,
"Set indent width to 10 spaces", 10),
("IndentWidth12", None, "12", None,
"Set indent width to 12 spaces", 12)],
-1, self.indent_toggled_cb)
action_group.add_radio_actions([("SmartHomeEndDisabled", None, "Disabled", None,
"Smart Home/End disabled", GtkSource.SmartHomeEndType.DISABLED),
("SmartHomeEndBefore", None, "Before", None,
"Smart Home/End before", GtkSource.SmartHomeEndType.BEFORE),
("SmartHomeEndAfter", None, "After", None,
"Smart Home/End after", GtkSource.SmartHomeEndType.AFTER),
("SmartHomeEndAlways", None, "Always", None,
"Smart Home/End always", GtkSource.SmartHomeEndType.ALWAYS)],
-1, self.smart_home_end_toggled_cb)
self._ui_manager = Gtk.UIManager()
self._ui_manager.insert_action_group(action_group, 0)
try:
self._ui_manager.add_ui_from_string(ui_description)
except:
return
menu = self._ui_manager.get_widget("/MainMenu")
self._vbox.pack_start(menu, False, False, 0)
accel_group = self._ui_manager.get_accel_group()
self.add_accel_group (accel_group)
# Add default values
action_group.get_action("HlSyntax").set_active(True)
action_group.get_action("HlBracket").set_active(True)
action_group.get_action("ShowNumbers").set_active(True)
action_group.get_action("ShowMarks").set_active(True)
action_group.get_action("ShowMargin").set_active(True)
action_group.get_action("AutoIndent").set_active(True)
action_group.get_action("TabWidth8").set_active(True)
action_group.get_action("IndentWidthUnset").set_active(True)
def add_source_mark_pixbufs(self):
attrs = GtkSource.MarkAttributes ();
color = Gdk.RGBA();
parsed = color.parse("lightgreen")
if parsed:
attrs.set_background(color)
attrs.set_stock_id(Gtk.STOCK_YES)
attrs.connect("query-tooltip-markup", self.mark_tooltip_func)
self._view.set_mark_attributes (self.MARK_TYPE_1, attrs, 1)
attrs = GtkSource.MarkAttributes ();
color = Gdk.RGBA();
parsed = color.parse("pink")
if parsed:
attrs.set_background(color)
attrs.set_stock_id(Gtk.STOCK_NO)
attrs.connect("query-tooltip-markup", self.mark_tooltip_func)
self._view.set_mark_attributes (self.MARK_TYPE_2, attrs, 2)
def remove_all_marks(self):
start, end = self._buf.get_bounds()
self._buf.remove_source_marks(start, end, None)
def mark_tooltip_func(self, attrs, mark):
i = self._buf.get_iter_at_mark(mark)
line = i.get_line() + 1
column = i.get_line_offset()
if mark.get_category() == self.MARK_TYPE_1:
return "Line: %d, Column: %d" % (line, column)
else:
return "<b>Line</b>: %d\n<i>Column</i>: %d" % (line, column)
def update_cursor_position(self):
i = self._buf.get_iter_at_mark(self._buf.get_insert())
chars = i.get_offset()
row = i.get_line() + 1
col = self._view.get_visual_column(i) + 1
classes = self._buf.get_context_classes_at_iter(i)
classes_str = ""
i = 0
for c in classes:
if len(classes) != i + 1:
classes_str += c + ", "
else:
classes_str += c
msg = "char: %d, line: %d, column: %d, classes: %s" % (chars, row, col, classes_str)
self._pos_label.set_text(msg)
# Callbacks
def open_file_cb(self, action, user_data=None):
chooser = Gtk.FileChooserDialog("Open File...", None,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = chooser.run()
if response == Gtk.ResponseType.OK:
filename = chooser.get_filename()
if filename:
self.open_file(filename)
chooser.destroy()
def print_file_cb(self, action, user_data=None):
compositor = GtkSource.PrintCompositor.new_from_view (self._view)
operation = Gtk.PrintOperation ()
operation.set_job_name (os.path.basename(self._filepath))
operation.set_show_progress (True)
# we do blocking pagination
operation.connect('paginate', self.paginate_cb, compositor)
operation.connect('draw-page', self.draw_page_cb, compositor)
operation.run (Gtk.PrintOperationAction.PRINT_DIALOG, None)
def find_cb(self, action, user_data=None):
dialog = SearchDialog(self, False, None, None)
if dialog.is_case_sensitive:
search_flags = GtkSource.SearchFlags.CASE_INSENSITIVE
else:
search_flags = 0
if dialog.run_search():
i = self._buf.get_iter_at_mark(self._buf.get_insert())
searched, start, end = i.forward_search(dialog.get_search_text(),
search_flags, None)
if searched:
self._buf.select_range(start, end)
else:
end = i
i = self._buf.get_start_iter()
searched, start, end = i.forward_search(dialog.get_search_text(),
search_flags, end)
if searched:
self._buf.select_range(start, end)
def replace_cb(self, action, user_data=None):
dialog = SearchDialog(self, False, None, None)
if dialog.is_case_sensitive:
search_flags = GtkSource.SearchFlags.CASE_INSENSITIVE
else:
search_flags = 0
i = self._buf.get_start_iter()
while True:
searched, start, end = i.forward_search(dialog.get_search_text(),
search_flags, None)
if not searched:
break
self._buf.delete(start, end)
self._buf.insert(start, dialog.get_replace_text())
i = start
def quit_cb(self, action, user_data=None):
_quit()
def new_view_cb(self, action, user_data=None):
window = Window()
window.show_all()
def hl_syntax_toggled_cb(self, action, user_data=None):
self._buf.set_highlight_syntax(action.get_active())
def hl_bracket_toggled_cb(self, action, user_data=None):
self._buf.set_highlight_matching_brackets(action.get_active())
def numbers_toggled_cb(self, action, user_data=None):
self._view.set_show_line_numbers(action.get_active())
def marks_toggled_cb(self, action, user_data=None):
self._view.set_show_line_marks(action.get_active())
def margin_toggled_cb(self, action, user_data=None):
self._view.set_show_right_margin(action.get_active())
def hl_line_toggled_cb(self, action, user_data=None):
self._view.set_highlight_current_line(action.get_active())
def draw_spaces_toggled_cb(self, action, user_data=None):
if (action.get_active()):
draw_spaces = GtkSource.DrawSpacesFlags.ALL
else:
draw_spaces = 0
self._view.set_draw_spaces(draw_spaces)
def wrap_lines_toggled_cb(self, action, user_data=None):
if (action.get_active()):
wrap_mode = Gtk.WrapMode.WORD
else:
wrap_mode = Gtk.WrapMode.NONE
self._view.set_wrap_mode(wrap_mode)
def auto_indent_toggled_cb(self, action, user_data=None):
self._view.set_auto_indent(action.get_active())
def insert_spaces_toggled_cb(self, action, user_data=None):
self._view.set_insert_spaces_instead_of_tabs(action.get_active())
def forward_string_cb(self, action, user_data=None):
insert = self._buf.get_insert()
it = self._buf.get_iter_at_mark(insert)
if (self._buf.iter_forward_to_context_class_toggle(it, "string")):
self._buf.place_cursor(it)
self._view.scroll_mark_onscreen(insert)
def backward_string_cb(self, action, user_data=None):
insert = self._buf.get_insert()
it = self._buf.get_iter_at_mark(insert)
if (self._buf.iter_backward_to_context_class_toggle(it, "string")):
self._buf.place_cursor(it)
self._view.scroll_mark_onscreen(insert)
def tabs_toggled_cb(self, action, current, user_data=None):
self._view.set_tab_width(current.get_current_value())
def indent_toggled_cb(self, action, current):
self._view.set_indent_width(current.get_current_value())
def smart_home_end_toggled_cb(self, action, current, user_data=None):
self._view.set_smart_home_end(current.get_current_value())
def about_cb(self, action, user_data=None):
about = AboutDialog(self)
about.show()
def move_cursor_cb(self, buf, cursor_iter, mark, user_data):
if mark != buf.get_insert():
return
self.update_cursor_position()
def line_mark_activated(self, gutter, place, ev, user_data):
if ev.button.button == 1:
mark_type = self.MARK_TYPE_1
else:
mark_type = self.MARK_TYPE_2
mark_list = self._buf.get_source_marks_at_line(place.get_line(), mark_type)
if mark_list:
self._buf.delete_mark(mark_list[0])
else:
self._buf.create_source_mark(None, mark_type, place)
def bracket_matched(self, buf, place, state, user_data):
# FIXME: figure out how to obtain the nick from the enum value
# print "Bracket match state: '%s'\n" % nick
if state == GtkSource.BracketMatchType.FOUND:
bracket = place.get_char()
row = place.get_line() + 1
col = place.get_line_offset() + 1
print("Matched bracket: '%c' at row: %i, col: %i" % (bracket, row, col))
def open_file(self, filename):
if os.path.isabs(filename):
path = filename
else:
path = os.path.abspath(filename)
self._filepath = path
f = Gio.file_new_for_path(path)
info = f.query_info("*", 0, None)
content_type = info.get_content_type()
mgr = GtkSource.LanguageManager.get_default()
language = mgr.guess_language(filename, content_type)
self.remove_all_marks()
self._buf.set_language(language)
self._buf.set_highlight_syntax(True)
self._buf.begin_not_undoable_action()
# stream = f.read(None)
# chunk, r = stream.read(4096, None)
#FIXME: Use Gio
try:
txt = open(path, 'r').read()
except:
return False
self._buf.set_text(txt, -1)
self._buf.end_not_undoable_action()
self._buf.set_modified(False)
i = self._buf.get_start_iter()
self._buf.place_cursor(i)
return True
def paginate_cb(self, operation, context, compositor):
print("Pagination progress: %.2f %%\n" % (compositor.get_pagination_progress () * 100.0))
if compositor.paginate (context):
print("Pagination progress: %.2f %%\n" % (compositor.get_pagination_progress () * 100.0))
operation.set_n_pages (compositor.get_n_pages ())
return True
return False
def draw_page_cb(self, operation, context, page_nr, compositor):
compositor.draw_page (context, page_nr)
def _quit(*args):
Gtk.main_quit()
def main(args = []):
global window
window = Window()
if len(args) > 2:
window.open_file(args[1])
else:
window.open_file(args[0])
window.show_all()
Gtk.main()
if __name__ == '__main__':
main(sys.argv)