glava-ridged/glava-config/window.lua

985 lines
31 KiB
Lua
Raw Normal View History

2023-09-07 16:16:06 -06:00
--[[
MAINTAINER NOTICE:
This application aims to be both Gtk+ 3 and 4 compatible for future-proofing. This means
avoiding *every* deprecated widget in Gtk+ 3, and watching out for some old functionality:
* Gdk.Color usage, use Gdk.RGBA instead
* Pango styles and style overrides
* Check convenience wrappers for deprecation, ie. GtkColorButton
* Avoid seldom used containers, as they may have been removed in 4.x (ie. GtkButtonBox)
In some cases we use deprecated widgets or 3.x restricted functionality, but only when we
query that the types are available from LGI (and otherwise use 4.x compatible code).
]]
return function()
local lgi = require 'lgi'
local utils = require 'glava-config.utils'
local mappings = require 'glava-config.mappings'
local GObject = lgi.GObject
local Gtk = lgi.Gtk
local Pango = lgi.Pango
local Gdk = lgi.Gdk
local GdkPixbuf = lgi.GdkPixbuf
local cairo = lgi.cairo
-- Both `GtkColorChooserDialog` and `GtkColorSelectionDialog` are
-- supported by this tool, but the latter is deprecated and does
-- not exist in 4.x releases.
--
-- The old chooser, however, is objectively better so let's try
-- to use it if it exists.
local use_old_chooser = true
if Gtk.get_major_version() >= 4 then
use_old_chooser = false
end
local window
local repeat_pattern = cairo.SurfacePattern(
cairo.ImageSurface.create_from_png(glava.resource_path .. "transparent.png")
)
repeat_pattern:set_extend("REPEAT")
-- We need to define a CSS class to use an alternative font for
-- color and identity entries; used to indicate to the user that
-- the field has formatting requirements
local cssp = Gtk.CssProvider {}
cssp:load_from_data(".fixed-width-font-entry { font-family: \"Monospace\"; }")
local ItemColumn = {
PROFILE = 1,
ENABLED = 2,
ACTIVABLE = 3,
WEIGHT = 4,
VISIBLE = 5
}
-- Fill store with initial items.
local item_store = Gtk.ListStore.new {
[ItemColumn.PROFILE] = GObject.Type.STRING,
[ItemColumn.ENABLED] = GObject.Type.BOOLEAN,
[ItemColumn.ACTIVABLE] = GObject.Type.BOOLEAN,
[ItemColumn.VISIBLE] = GObject.Type.BOOLEAN,
[ItemColumn.WEIGHT] = GObject.Type.INT
}
local default_entry = {
[ItemColumn.PROFILE] = "Default",
[ItemColumn.ENABLED] = false,
[ItemColumn.VISIBLE] = false,
[ItemColumn.ACTIVABLE] = false,
[ItemColumn.WEIGHT] = 600
}
-- Apply `t[k] = v` to all table argument at array indexes,
-- and return the unpacked list of tables. Used for nesting
-- widget construction.
local function apply(tbl)
local ret = {}
for k, v in ipairs(tbl) do
ret[k] = v
tbl[k] = nil
end
for k, v in pairs(tbl) do
for _, r in ipairs(ret) do
r[k] = v
end
end
return unpack(ret)
end
-- Apply `binds[k] = v` while returning unpacked values
local binds = {}
local function bind(tbl)
local ret = {}
for k, v in pairs(tbl) do
binds[k] = v
ret[#ret + 1] = v
end
return unpack(ret)
end
local function link(tbl)
for _, v in ipairs(tbl) do
v:get_style_context():add_class("linked")
end
return unpack(tbl)
end
local function ComboBoxFixed(tbl)
local inst = Gtk.ComboBoxText { id = tbl.id }
for _, v in pairs(tbl) do
inst:append_text(v)
end
inst:set_active(tbl.default or 0)
return inst
end
local SpoilerView = function(tbl)
local stack = Gtk.Stack {
expand = true,
transition_type = Gtk.StackTransitionType.CROSSFADE
}
local btn = Gtk.CheckButton {
active = tbl.active or false
}
if tbl.active ~= true then
stack:add_named(Gtk.Box {}, "none")
end
stack:add_named(tbl[1], "view")
if tbl.active == true then
stack:add_named(Gtk.Box {}, "none")
end
function btn:on_toggled(path)
stack:set_visible_child_name(btn.active and "view" or "none")
end
return Gtk.Box {
expand = false,
orientation = "VERTICAL",
spacing = 4,
Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
btn,
Gtk.Label { label = tbl.label or "Spoiler" }
},
Gtk.Separator(),
stack
}
end
local ConfigView = function(tbl)
local grid = {
row_spacing = 2,
column_spacing = 12,
column_homogeneous = false,
row_homogeneous = false
}
local list = {}
local idx = 0
local function cbuild(list, entry)
list[#list + 1] = {
Gtk.Label { label = entry[1], halign = "START", valign = "START" },
left_attach = 0, top_attach = idx
}
list[#list + 1] = {
Gtk.Box { hexpand = true },
left_attach = 1, top_attach = idx
}
list[#list + 1] = {
apply { halign = "END", entry[3] or Gtk.Box {} },
left_attach = 2, top_attach = idx
}
list[#list + 1] = {
apply { halign = "FILL", hexpand = false, entry[2] },
left_attach = 3, top_attach = idx
}
list[#list + 1] = {
Gtk.Separator {
vexpand = false
}, left_attach = 0, top_attach = idx + 1, width = 3
}
idx = idx + 2
end
for _, entry in ipairs(tbl) do
cbuild(list, entry)
end
local adv = {}
if tbl.advanced then
idx = 0
for _, entry in ipairs(tbl.advanced) do
cbuild(adv, entry)
end
end
for k, v in pairs(grid) do
list[k] = v
adv[k] = v
end
return Gtk.ScrolledWindow {
expand = true,
Gtk.Box {
margin_top = 12,
margin_start = 16,
margin_end = 16,
hexpand = true,
vexpand = true,
halign = "FILL",
orientation = "VERTICAL",
spacing = 6,
Gtk.Grid(list),
#adv > 0 and SpoilerView
{ label = "Show Advanced",
Gtk.Grid(adv)
} or Gtk.Box {}
} }
end
local function wrap_label(widget, label)
if label then
widget = Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
Gtk.Label {
label = label
}, widget
}
end
return widget
end
-- Generators for producing widgets (and their layouts) that bind to configuration values
-- note: `get_data` returns stringified data
local widget_generators
widget_generators = {
-- A switch to represent a true/false value
["boolean"] = function(attrs)
local widget = Gtk.Switch { hexpand = false }
return {
widget = Gtk.Box { Gtk.Box { hexpand = true }, wrap_label(widget, attrs.label) },
set_data = function(x)
widget.active = x
return true
end,
get_data = function() return widget.active end,
connect = function(f) widget.on_state_set = f end
}
end,
-- Entry for a generic string, may have predefined selections
["string"] = function(attrs)
local widget = apply {
attrs.entries ~= nil
and apply { ComboBoxFixed(attrs.entries) }
or Gtk.Entry { width_chars = 12 },
hexpand = true
}
return {
widget = wrap_label(widget, attrs.label),
internal = widget,
set_data = function(x)
if not attrs.entries then
widget:set_text(x)
else
for k, v in ipairs(attrs.entries) do
if v == x then
widget:set_active(v - 1)
return true
end
end
return false
end
return true
end,
get_data = function()
local text = (not attrs.entries) and widget:get_text() or widget:get_active_text()
if attrs.translate then
text = attrs.translate[text]
end
return text
end,
connect = function(f)
-- Note: the underlying widget can be `GtkComboBoxText` or `GtkEntry`;
-- they simply just use the same signal for user input
widget.on_changed = f
end
}
end,
-- Entry for a valid C/GLSL identity, may have predefined selections
["ident"] = function(attrs)
local s = widget_generators.string(attrs)
-- Set fixed-width font if the users enter/select identifiers by their name,
-- rather than a description to indicate it's a GLSL identity
if not attrs.translate then
s.internal:get_style_context():add_provider(cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
s.internal:get_style_context():add_class("fixed-width-font-entry")
end
if not attrs.entries and not attrs._ignore_restrict then
-- Handle idenifier formatting for entries without a preset list
local handlers = {}
local function run_handlers()
for _, f in ipairs(handlers) do f() end
end
function s.internal:on_changed()
local i = s.internal.text
if i:match("[^%w]") ~= nil or i:sub(1, 1):match("[^%a]") ~= nil then
s.internal.text = i:gsub("[^%w]", ""):gsub("^[^%a]+", "")
else
run_handlers()
end
end
s.connect = function(f)
handlers[#handlers + 1] = f
end
end
return s
end,
-- A full GLSL expression
["expr"] = function(attrs)
-- Expressions can be implemented by using the identity field and disabling
-- input format restrictions.
attrs._ignore_restrict = true
return widget_generators.ident(attrs)
end,
-- Adjustable and bound floating-point value
["float"] = function(attrs)
local widget = Gtk.SpinButton {
hexpand = true,
adjustment = Gtk.Adjustment {
lower = attrs.lower or 0,
upper = attrs.upper or 100,
page_size = 1,
step_increment = attrs.increment or 1,
page_increment = attrs.increment or 1
},
width_chars = attrs.width or 6,
numeric = true,
digits = attrs.digits or 2,
climb_rate = attrs.increment or 1
}
return {
widget = wrap_label(widget, attrs.label),
set_data = function(x)
widget:set_text(x)
return true
end,
get_data = function() return widget:get_text() end,
connect = function(f) widget.on_value_changed = f end
}
end,
-- Adjustable and bound integral value
["int"] = function(attrs)
local widget = Gtk.SpinButton {
hexpand = true,
adjustment = Gtk.Adjustment {
lower = attrs.lower or 0,
upper = attrs.upper or 100,
page_size = 1,
step_increment = attrs.increment or 1,
page_increment = attrs.increment or 1
},
width_chars = attrs.width or 6,
numeric = true,
digits = 0,
climb_rate = attrs.increment or 1
}
return {
widget = wrap_label(apply { vexpand = false, widget }, attrs.label),
set_data = function(x)
widget:set_text(x)
return true
end,
get_data = function() return widget:get_text() end,
connect = function(f) widget.on_value_changed = f end
}
end,
-- The color type is the hardest to implement; as Gtk deprecated
-- the old color chooser button, so we have to implement our own.
-- The benefits of doing this mean we get to use the "nice" Gtk3
-- chooser, and the button rendering itself is much better.
["color"] = function(attrs)
local dialog_open = false
local handlers = {}
local function run_handlers()
for _, f in ipairs(handlers) do f() end
end
local c = Gdk.RGBA {
red = 1.0, green = 1.0, blue = 1.0, alpha = 1.0
}
local area = Gtk.DrawingArea()
area:set_size_request(16, 16)
local draw = function(widget, cr)
local context = widget:get_style_context()
local width = widget:get_allocated_width()
local height = widget:get_allocated_height()
local aargc = { width / 2, height / 2, math.min(width, height) / 2, 0, 2 * math.pi }
Gtk.render_background(context, cr, 0, 0, width, height)
cr:set_source(repeat_pattern)
cr:arc(unpack(aargc))
cr:fill()
cr:set_source_rgba(c.red, c.green, c.blue, c.alpha)
cr:arc(unpack(aargc))
cr:fill()
end
if Gtk.get_major_version() >= 4 then
area:set_draw_func(draw)
else
area.on_draw = draw
end
local btn = Gtk.Button {
apply {
margin_top = 1,
margin_bottom = 1,
area
} }
local entry = Gtk.Entry {
hexpand = true,
width_chars = 9,
max_length = 9,
text = attrs.alpha and "#FFFFFFFF" or "#FFFFFF"
}
entry:get_style_context():add_provider(cssp, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
entry:get_style_context():add_class("fixed-width-font-entry")
local widget = Gtk.Box {
orientation = "HORIZONTAL",
spacing = 0,
entry, btn
}
link { widget }
widget = wrap_label(widget, attrs.label)
function btn:on_clicked()
local c_change_staged = false
local dialog = (use_old_chooser and Gtk.ColorSelectionDialog or Gtk.ColorChooserDialog)
{ title = "Select Color",
transient_for = window,
modal = true,
destroy_with_parent = true
}
if use_old_chooser then
dialog.cancel_button:set_visible(false)
dialog.ok_button.label = "Close"
dialog.color_selection.current_rgba = c
if attrs.alpha then
dialog.color_selection.has_opacity_control = true
end
function dialog.color_selection:on_color_changed()
c_change_staged = true
c = dialog.color_selection.current_rgba
entry:set_text(attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c))
area:queue_draw()
end
else
dialog.rgba = c
if attrs.alpha then
dialog.use_alpha = true
end
end
dialog_open = true
local ret = dialog:run()
dialog_open = false
dialog:set_visible(false)
if not use_old_chooser and ret == Gtk.ResponseType.OK then
c = dialog.rgba
entry:set_text(attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c))
area:queue_draw()
run_handlers()
elseif use_old_chooser and c_change_staged then
run_handlers()
end
end
function entry:on_changed()
local s = utils.sanitize_color(entry.text)
c = utils.parse_color_rgba(s)
area:queue_draw()
if not dialog_open then run_handlers() end
end
return {
widget = widget,
set_data = function(x)
local s = utils.sanitize_color(x)
c = utils.parse_color_rgba(s)
area:queue_draw()
entry:set_text(s)
return true
end,
get_data = function(x)
return attrs.alpha and utils.format_color_rgba(c) or utils.format_color_rgb(c)
end,
connect = function(f)
handlers[#handlers + 1] = f
end
}
end,
-- A field capable of producing a GLSL color expression.
["color-expr"] = function(attrs, header)
-- Define color control variables for use in color expressions
local controls = {
{ "Baseline", "d" },
{ "X axis", "gl_FragCoord.x" },
{ "Y axis", "gl_FragCoord.y" }
}
local control_list = {}
for i, v in ipairs(controls) do
control_list[i] = v[1]
controls[v[1]] = v[2]
end
-- Define color expression types. Field data is assigned according
-- to the associated pattern, and entries are ordered in terms of
-- match priority
local cetypes = {
{ "Gradient",
fields = {
{ "color" },
{ "color" },
{ "ident",
entries = control_list,
translate = controls,
header = "Axis:"
},
{ "float",
upper = 1000,
lower = -1000,
header = "Scale:"
} },
-- match against GLSL mix expression, ie.
-- `mix(#3366b2, #a0a0b2, clamp(d / GRADIENT, 0, 1))`
match = "mix%s*%(" ..
"%s*(#[%dA-Fa-f]*)%s*," ..
"%s*(#[%dA-Fa-f]*)%s*," ..
"%s*clamp%s*%(%s*(%w+)%s*/%s*(%w+)%s*,%s*0%s*,%s*1%s*%)%s*%)",
output = "mix(%s, %s, clamp(%s / %s, 0, 1))"
},
{ "Solid",
fields = { { "color" } },
match = "#[%dA-Fa-f]*",
output = "%s",
default = true
} }
local stack = Gtk.Stack { vhomogeneous = false }
local hstack = Gtk.Stack { vhomogeneous = false }
local cekeys = {}
local default = nil
for i, v in ipairs(cetypes) do
if not v.default then
cekeys[#cekeys + 1] = v[1]
else
table.insert(cekeys, 1, v[1])
end
cetypes[v[1]] = v
local wfields = {}
local hfields = {
Gtk.Label {
halign = "END",
valign = "START",
label = header
} }
local gen = {}
for k, e in ipairs(v.fields) do
v.alpha = attrs.alpha
local g = widget_generators[e[1]](e)
gen[#gen + 1] = g
wfields[k] = g.widget
hfields[#hfields + 1] = Gtk.Label {
halign = "END",
label = e.header
}
end
v.gen = gen
v.widget = Gtk.Box(
apply {
homogeneous = true,
orientation = "VERTICAL",
spacing = 1,
wfields
} )
v.hwidget = Gtk.Box(
apply {
homogeneous = true,
orientation = "VERTICAL",
spacing = 1,
hfields
} )
hstack:add_named(v.hwidget, v[1])
stack:add_named(v.widget, v[1])
if v.default then
default = v[1]
end
v.set_data = function(x)
for i, m in ipairs { string.match(x, v.match) } do
gen[i].set_data(m)
end
end
v.get_data = function()
local fields = {}
for i = 1, #v.fields do
fields[i] = gen[i]:get_data()
end
return string.format(v.output, unpack(fields))
end
v.connect = function(f)
for _, g in ipairs(gen) do
g.connect(f)
end
end
end
local cbox = apply {
hexpand = true,
ComboBoxFixed(cekeys)
}
stack:set_visible_child(cetypes[default].widget)
hstack:set_visible_child(cetypes[default].hwidget)
cetypes[default].widget:show()
cetypes[default].hwidget:show()
function cbox:on_changed()
local t = cbox:get_active_text()
stack:set_visible_child_name(t)
hstack:set_visible_child_name(t)
end
local widget = Gtk.Box {
orientation = "VERTICAL",
spacing = 1,
wrap_label(cbox, attrs.label), stack
}
return {
widget = widget,
header_widget = hstack,
set_data = function(x)
for i, v in ipairs(cetypes) do
if string.match(x, v.match) ~= nil then
v.set_data(x)
return true
end
end
return false
end,
get_data = function()
return cetypes[cbox:get_active_text()].get_data()
end,
connect = function(f)
for i, v in ipairs(cetypes) do
v.connect(f)
end
end
}
end
}
-- Extra widget for special service/autostart functionality
local ServiceView = function(self)
local switch = Gtk.Switch {
sensitive = false,
hexpand = false
}
local method = ComboBoxFixed {
"None",
"SystemD User Service",
"InitD Entry",
"Desktop Entry"
}
method.on_changed = function(box)
local opt = box:get_active_text()
switch.sensitive = opt ~= "None"
if switch.active == true and opt == "None" then
switch:activate()
end
for _, entry in item_store:pairs() do
if entry[ItemColumn.PROFILE] == self.name then
entry[ItemColumn.ACTIVABLE] = opt ~= "None"
if opt == "None" then
entry[ItemColumn.ENABLED] = false
end
end
end
end
switch.on_notify["active"] = function(inst, pspec)
for _, entry in item_store:pairs() do
if entry[ItemColumn.PROFILE] == self.name then
entry[ItemColumn.ENABLED] = switch.active
end
end
-- TODO handle enable here
end
return ConfigView {
{ "Enabled", Gtk.Box { Gtk.Box { hexpand = true }, switch } },
{ "Autostart Method", method }
}, switch
end
-- Produce a widget containing a scroll area full of widgets bound to
-- requests/defines in the specified profile.
local function ProfileView(name)
local self = { name = name }
local args = {}
for k, v in pairs(mappings) do
local layout = {}
for _, e in ipairs(v) do
if type(e) == "table" then
local header = nil
local fields = {}
local ftypes = type(e.field_type) == "table" and e.field_type or { e.field_type }
local fattrs = type(e.field_type) == "table" and e.field_attrs or { e.field_attrs }
if not fattrs then fattrs = {} end
for i, f in ipairs(ftypes) do
local entry = widget_generators[f](fattrs[i] or {}, e.header)
if not header then
header = entry.header_widget
end
fields[#fields + 1] = entry.widget
-- todo: finish linking config
entry.connect(function()
print(string.format("assign %s->%s->%s[%d] = %s", k, e[1], f, i, tostring(entry.get_data())))
end)
end
-- disable header display widget if there are multiple fields
if #fields > 1 then header = nil end
fields.orientation = "VERTICAL"
fields.spacing = 2
local fwidget = {
e.description,
#fields > 1 and
Gtk.Frame {
label = fattrs.frame_label,
apply {
margin_start = 4,
margin_end = 4,
margin_top = 4,
margin_bottom = 4,
Gtk.Box(fields)
} } or fields[1],
header or (e.header and Gtk.Label { valign = "START", label = e.header } or Gtk.Box {})
}
if not e.advanced then
layout[#layout + 1] = fwidget
else
if not layout.advanced then layout.advanced = {} end
layout.advanced[#layout.advanced + 1] = fwidget
end
end
end
args[#args + 1] = { tab_label = v.name, ConfigView(layout) }
end
local service, chk = ServiceView(self)
args[#args + 1] = {
tab_label = "Autostart",
name ~= "Default" and service or
Gtk.Box {
valign = "CENTER",
orientation = "VERTICAL",
spacing = 8,
Gtk.Label {
label = "Autostart options are not available for the default user profile."
},
Gtk.Button {
hexpand = false,
halign = "CENTER",
label = "Show Profiles"
} } }
args.expand = true
notebook = Gtk.Notebook(args)
notebook:show_all()
self.widget = notebook
self.autostart_enabled = chk
function self:rename(new)
self.name = new
end
function self:delete()
end
return self;
end
local view_registry = {}
view_registry[default_entry[ItemColumn.PROFILE]] = ProfileView(default_entry[ItemColumn.PROFILE])
item_store:append(default_entry)
window = Gtk.Window {
title = "GLava Config",
default_width = 320,
default_height = 200,
border_width = 5,
Gtk.Box {
orientation = "HORIZONTAL",
spacing = 6,
homogeneous = false,
Gtk.Box {
hexpand = false,
orientation = "VERTICAL",
spacing = 5,
Gtk.ScrolledWindow {
shadow_type = "ETCHED_IN",
vexpand = true,
width_request = 200,
bind {
view = Gtk.TreeView {
model = item_store,
activate_on_single_click = true,
Gtk.TreeViewColumn {
title = "Profile",
expand = true,
{ bind { profile_renderer = Gtk.CellRendererText {} },
{ text = ItemColumn.PROFILE,
editable = ItemColumn.VISIBLE,
weight = ItemColumn.WEIGHT
} } },
Gtk.TreeViewColumn {
title = "Enabled",
alignment = 0.5,
-- Note `xalign` usage here comes from GtkCellRenderer, which unlike the
-- legacy alignment widget is not deprecated
{ bind { toggle_renderer = Gtk.CellRendererToggle { xalign = 0.5 } },
{ active = ItemColumn.ENABLED,
activatable = ItemColumn.ACTIVABLE,
visible = ItemColumn.VISIBLE
} } } } } },
link {
Gtk.Box {
hexpand = true,
bind {
reload = Gtk.Button {
Gtk.Image {
icon_name = "view-refresh-symbolic"
} },
},
bind {
add = Gtk.Button {
halign = "FILL",
hexpand = true,
label = "Create Profile",
} },
bind {
remove = Gtk.Button {
halign = "END",
sensitive = false,
Gtk.Image {
icon_name = "user-trash-symbolic"
} } } } } },
Gtk.Box {
orientation = "VERTICAL",
spacing = 6,
link {
Gtk.Box {
Gtk.ToggleButton {
Gtk.Image {
icon_name = "view-paged-symbolic"
},
on_clicked = function()
--
end
},
bind {
display_path = Gtk.Entry {
-- todo: bind to config
text = "~/.config/glava/rc.glsl",
editable = false,
hexpand = true
} } } },
bind {
stack_view = Gtk.Stack {
expand = true,
transition_type = Gtk.StackTransitionType.CROSSFADE
} } } } }
local selection = binds.view:get_selection()
selection.mode = 'SINGLE'
binds.stack_view:add_named(view_registry[default_entry[ItemColumn.PROFILE]].widget,
default_entry[ItemColumn.PROFILE])
function unique_profile(profile_name_proto)
local profile_idx = 0
local profile_name = profile_name_proto
while true do
local used = false
for i, entry in item_store:pairs() do
if entry[ItemColumn.PROFILE] == profile_name then
used = true
end
end
if not used then break else
profile_idx = profile_idx + 1
profile_name = profile_name_proto .. " (" .. tostring(profile_idx) .. ")"
end
end
return profile_name
end
function binds.view:on_row_activated(path, column)
local name = item_store[path][ItemColumn.PROFILE]
binds.stack_view:set_visible_child_name(name)
binds.remove.sensitive = (name ~= "Default")
end
function binds.profile_renderer:on_edited(path_string, new_profile)
local path = Gtk.TreePath.new_from_string(path_string)
local old = item_store[path][ItemColumn.PROFILE]
local store = binds.stack_view:get_child_by_name(old)
new_profile = string.match(new_profile, "^%s*(.-)%s*$")
if old == new_profile or new_profile == "Default" then return end
new_profile = unique_profile(new_profile)
print("Renamining profile \"" .. old .. "\" -> \"" .. new_profile .. "\"")
binds.stack_view:remove(store)
binds.stack_view:add_named(store, new_profile)
local vstore = view_registry[old]
view_registry[old] = nil
view_registry[new_profile] = vstore
vstore:rename(new_profile)
item_store[path][ItemColumn.PROFILE] = new_profile
end
function binds.toggle_renderer:on_toggled(path_string)
local path = Gtk.TreePath.new_from_string(path_string)
if view_registry[item_store[path][ItemColumn.PROFILE]].autostart_enabled.active
~= not item_store[path][ItemColumn.ENABLED] then
view_registry[item_store[path][ItemColumn.PROFILE]].autostart_enabled:activate()
end
item_store[path][ItemColumn.ENABLED] =
view_registry[item_store[path][ItemColumn.PROFILE]].autostart_enabled.active
end
function binds.add:on_clicked()
local profile_name = unique_profile("New Profile")
local entry = {
[ItemColumn.PROFILE] = profile_name,
[ItemColumn.ENABLED] = false,
[ItemColumn.ACTIVABLE] = false,
[ItemColumn.VISIBLE] = true,
[ItemColumn.WEIGHT] = 400
}
local view = ProfileView(profile_name)
item_store:append(entry)
view_registry[profile_name] = view
binds.stack_view:add_named(view.widget, profile_name);
end
function binds.remove:on_clicked()
local dialog = Gtk.Dialog {
title = "Confirmation",
transient_for = window,
modal = true,
destroy_with_parent = true
}
local byes = dialog:add_button("Yes", Gtk.ResponseType.YES)
local bcancel = dialog:add_button("Cancel", Gtk.ResponseType.CANCEL)
dialog:get_action_area().halign = Gtk.Align.CENTER
local box = Gtk.Box {
orientation = 'HORIZONTAL',
spacing = 8,
border_width = 8,
Gtk.Image {
icon_name = "dialog-warning-symbolic",
icon_size = Gtk.IconSize.DIALOG,
},
Gtk.Label {
label = "Are you sure you want to delete the selected profile?"
} }
dialog:get_content_area():add(box)
box:show_all()
local ret = dialog:run()
dialog:set_visible(false)
if ret ~= Gtk.ResponseType.YES then return end
local model, iter = selection:get_selected()
if model and iter then
for iter, entry in item_store:pairs() do
if selection:iter_is_selected(iter) then
binds.stack_view:remove(
binds.stack_view:get_child_by_name(
entry[ItemColumn.PROFILE]))
view_registry[entry[ItemColumn.PROFILE]]:delete()
view_registry[entry[ItemColumn.PROFILE]] = nil
end
end
model:remove(iter)
end
end
function window:on_destroy() os.exit(0) end
window:show_all()
window:set_icon_from_file(glava.resource_path .. "glava.bmp")
Gtk.main()
end