#!/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
Preferences01
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
Example Application6004000Lines:0stack0edit-find-symbolicnoneverticalslide-rightnevernone
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
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)