#!/usr/bin/env ruby # # Copyright (C) 2023 Ruby-GNOME Project Team # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # Example from: # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/examples/application9/exampleapp.c # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/examples/application9/exampleappwin.c # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/examples/application9/exampleappprefs.c # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/examples/application9/window.ui # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/examples/application9/gears-menu.ui # * https://gitlab.gnome.org/GNOME/gtk/-/blob/main/examples/application9/prefs.ui # License: LGPL2.1-or-later # GSETTINGS_SCHEMA_DIR must be set before requiring "gtk4" gem because it is used in the GIO initialization. if File.exist?(File.join(__dir__, "gschemas.compiled")) ENV["GSETTINGS_SCHEMA_DIR"] = __dir__ else raise %{gschemas.compiled doesn't exist. Run "rake" to generate it.} end require "gtk4" class ExampleAppPrefs < Gtk::Dialog type_register class << self def init template = <<~TEMPLATE TEMPLATE set_template(data: template) bind_template_child("font") bind_template_child("transition") end end def initialize(win) # The original C program sets use-header-bar property to TRUE instead of 1. # But the property type is int, not gboolean. # Therefore, the property value must be 1, not `true` here. super('transient-for': win, 'use-header-bar': 1) settings = Gio::Settings.new("org.gtk.exampleapp") settings.bind("font", font, "font", :default) settings.bind("transition", transition, "active-id", :default) end end class ExampleAppWindow < Gtk::ApplicationWindow type_register class << self def init template = <<~TEMPLATE TEMPLATE set_template(data: template) bind_template_child("stack") bind_template_child("gears") bind_template_child("search") bind_template_child("searchbar") bind_template_child("searchentry") bind_template_child("words") bind_template_child("sidebar") bind_template_child("lines") bind_template_child("lines_label") bind_template_callback_full("search_text_changed") bind_template_callback_full("visible_child_changed") end end def initialize(application) menu_ui = <<~MENU
_Words win.show-words _Lines win.show-lines _Preferences app.preferences
_Quit app.quit
MENU super(application: application) builder = Gtk::Builder.new(string: menu_ui) gears.menu_model = builder["menu"] @settings = Gio::Settings.new("org.gtk.exampleapp") @settings.bind("transition", stack, "transition-type", :default) @settings.bind("show-words", sidebar, "reveal-child", :default) search.bind_property("active", searchbar, "search-mode-enabled", :bidirectional) sidebar.signal_connect "notify::reveal-child" do |_sidebar, _pspec| update_words end action = @settings.create_action("show-words") add_action(action) action = Gio::PropertyAction.new("show-lines", lines, "visible") add_action(action) lines.bind_property("visible", lines_label, "visible", :default) end def open(file) basename = file.basename scrolled = Gtk::ScrolledWindow.new scrolled.hexpand = true scrolled.vexpand = true view = Gtk::TextView.new view.editable = false view.cursor_visible = false scrolled.child = view stack.add_titled(scrolled, basename, basename) buffer = view.buffer file.read do |stream| buffer.text = stream.read.force_encoding(Encoding::UTF_8) end tag = buffer.create_tag @settings.bind("font", tag, "font", :default) buffer.apply_tag(tag, buffer.start_iter, buffer.end_iter) search.sensitive = true update_words update_lines end private def search_text_changed(search_entry) text = search_entry.text return if text.empty? tab = stack.visible_child view = tab.child buffer = view.buffer range = buffer.start_iter.forward_search(text, :case_insensitive) return unless range buffer.select_range(range[0], range[1]) view.scroll_to_iter(range[0], 0.0, false, 0.0, 0.0) end def update_words tab = stack.visible_child return unless tab view = tab.child buffer = view.buffer strings = buffer.text.downcase.scan(/[[:word:]]+/).uniq while (child = words.first_child) words.remove(child) end strings.each do |s| row = Gtk::Button.new(label: s) row.signal_connect("clicked") {searchentry.text = s} words.insert(row, -1) end end def update_lines tab = stack.visible_child return unless tab view = tab.child buffer = view.buffer lines.text = buffer.line_count.to_s end def visible_child_changed(stack, params) return if stack.in_destruction? searchbar.search_mode = false update_words update_lines end end class ExampleApp < Gtk::Application def initialize super("org.gtk.exampleapp", :handles_open) signal_connect "startup" do |application| [ "preferences", "quit" ].each do |action_name| action = Gio::SimpleAction.new(action_name) action.signal_connect("activate") do |_action, _parameter| __send__("#{action_name}_activated") end application.add_action(action) end application.set_accels_for_action("app.quit", ["Q"]) end signal_connect "activate" do |application| window = ExampleAppWindow.new(application) window.present end signal_connect "open" do |application, files, hint| window = application.windows[0] || ExampleAppWindow.new(application) files.each do |file| window.open(file) end window.present end end private def preferences_activated prefs = ExampleAppPrefs.new(active_window) prefs.present end def quit_activated quit end end app = ExampleApp.new # Gtk::Application#run needs C style argv ([prog, arg1, arg2, ...,argn]). # The ARGV ruby variable only contains the arguments ([arg1, arg2, ...,argb]) # and not the program name. We have to add it explicitly. app.run([$PROGRAM_NAME] + ARGV)