1# Gtk support 2 3Lgi Gtk support is based on gobject-introspection support. Some 4extensions are provided to support non-introspectable features and to 5provide easier and more Lua-like access to some important Gtk 6features. 7 8## Basic Widget and Container support 9 10### Style properties access 11 12To read style property values of the widget, a `style` attribute is 13implemented. Following example reads `resize-grip-height` style 14property from Gtk.Window instance: 15 16 local window = Gtk.Window() 17 print(window.style.resize_grip_height) 18 19### Gtk.Widget width and height properties 20 21lgi adds new `width` and `height` properties to Gtk.Widget. Reading them 22yields allocated size (`Gtk.Widget.get_allocated_size()`), writing them sets 23new size request (`Gtk.Widget.set_size_request()`). These usages typically 24means what an application needs - actual allocated size to draw on when 25reading, and request for specific size when writing them. 26 27### Child properties 28 29Child properties are properties of the relation between a container 30and child. A Lua-friendly access to these properties is implemented 31by `property` attribute of `Gtk.Container`. Following example 32illustrates writing and reading of `width` property of `Gtk.Grid` 33and child `Gtk.Button`: 34 35 local grid, button = Gtk.Grid(), Gtk.Button() 36 grid:add(button) 37 grid.property[button].width = 2 38 print(grid.property[button].width) -- prints 2 39 40### Adding children to container 41 42Basic method for adding child widget into container is 43`Gtk.Container.add()` method. This method is overloaded by Lgi so 44that it accepts either widget, or table containing widget at index 1 45and the rest `name=value` pairs define child properties. Therefore 46this method is full replacement of unintrospectable 47`gtk_container_add_with_properties()` function. Example from previous 48chapter simplified using this technique follows: 49 50 local grid, button = Gtk.Grid(), Gtk.Button() 51 grid:add { button, width = 2 } 52 print(grid.property[button].width) -- prints 2 53 54Another important feature of containers is that they have extended 55constructor, and array part of constructor argument table can contain 56widgets to be added. Therefore, previous example can be written like 57this: 58 59 local button = Gtk.Button() 60 local grid = Gtk.Grid { 61 { button, width = 2 } 62 } 63 print(grid.property[button].width) -- prints 2 64 65### 'id' property of widgets 66 67Another important feature is that all widgets support `id` property, 68which can hold an arbitrary string which is used to identify the 69widget. `id` is assigned by caller, defaults to `nil`. To look up 70widget with specified id in the container's widget tree (i.e. not only 71in direct container children), query `child` property of the container 72with requested id. Previous example rewritten with this technique 73would look like this: 74 75 local grid = Gtk.Grid { 76 { Gtk.Button { id = 'button' }, width = 2 } 77 } 78 print(grid.property[grid.child.button].width) -- prints 2 79 80The advantage of these features is that they allow using Lua's 81data-description face for describing widget hierarchies in natural 82way, instead of human-unfriendly `Gtk.Builder`'s XML. A small example 83follows: 84 85 Gtk = lgi.Gtk 86 local window = Gtk.Window { 87 title = 'Application', 88 default_width = 640, default_height = 480, 89 Gtk.Grid { 90 orientation = Gtk.Orientation.VERTICAL, 91 Gtk.Toolbar { 92 Gtk.ToolButton { id = 'about', stock_id = Gtk.STOCK_ABOUT }, 93 Gtk.ToolButton { id = 'quit', stock_id = Gtk.STOCK_QUIT }, 94 }, 95 Gtk.ScrolledWindow { 96 Gtk.TextView { id = 'view', expand = true } 97 }, 98 Gtk.Statusbar { id = 'statusbar' } 99 } 100 } 101 102 local n = 0 103 function window.child.about:on_clicked() 104 n = n + 1 105 window.child.view.buffer.text = 'Clicked ' .. n .. ' times' 106 end 107 108 function window.child.quit:on_clicked() 109 window:destroy() 110 end 111 112 window:show_all() 113 114Run `samples/console.lua`, paste example into entry view and enjoy. 115The `samples/console.lua` example itself shows more complex usage of 116this pattern. 117 118## Gtk.Builder 119 120Although Lua's declarative style for creating widget hierarchies (as 121presented in chapter discussing `Gtk.Container` extensions) is generally 122preferred to builder's XML authoring by hand, `Gtk.Builder` can still be 123useful when widget hierarchies are designed in some external tool like 124`glade`. 125 126Original `gtk_builder_add_from_file` and `gtk_builder_add_from_string` 127return `guint` instead of `gboolean`, which would make direct usage 128from Lua awkward. Lgi overrides these methods to return `boolean` as 129the first return value, so that typical 130`assert(builder:add_from_file(filename))` can be used. 131 132A new `objects` attribute provides direct access to loaded objects by 133their identifier, so that instead of `builder:get_object('id')` it 134is possible to use `builder.objects.id` 135 136`Gtk.Builder.connect_signals(handlers)` tries to connect all signals 137to handlers which are defined in `handlers` table. Functions from 138`handlers` table are invoked with target object on which is signal 139defined as first argument, but it is possible to define `object` 140attribute, in this case the object instance specified in `object` 141attribute is used. `after` attribute is honored, but `swapped` is 142completely ignored, as its semantics for lgi is unclear and not very 143useful. 144 145## Gtk.Action and Gtk.ActionGroup 146 147Lgi provides new method `Gtk.ActionGroup:add()` which generally replaces 148unintrospectable `gtk_action_group_add_actions()` family of functions. 149`Gtk.ActionGroup:add()` accepts single argument, which may be one of: 150 151- an instance of `Gtk.Action` - this is identical with calling 152 `Gtk.Action.add_action()`. 153- a table containing instance of `Gtk.Action` at index 1, and 154 optionally having attribute `accelerator`; this is a shorthand for 155 `Gtk.ActionGroup.add_action_with_accel()` 156- a table with array of `Gtk.RadioAction` instances, and optionally 157 `on_change` attribute containing function to be called when the radio 158 group state is changed. 159 160All actions or groups can be added by an array part of `Gtk.ActionGroup` 161constructor, as demonstrated by following example: 162 163 local group = Gtk.ActionGroup { 164 Gtk.Action { name = 'new', label = "_New" }, 165 { Gtk.Action { name = 'open', label = "_Open" }, 166 accelerator = '<control>O' }, 167 { 168 Gtk.RadioAction { name = 'simple', label = "_Simple", value = 1 }, 169 { Gtk.RadioAction { name = 'complex', label = "_Complex", 170 value = 2 }, accelerator = '<control>C' }, 171 on_change = function(action) 172 print("Changed to: ", action.name) 173 end 174 }, 175 } 176 177To access specific action from the group, a read-only attribute `action` 178is added to the group, which allows to be indexed by action name to 179retrieve. So continuing the example above, we can implement 'new' 180action like this: 181 182 function group.action.new:on_activate() 183 print("Action 'New' invoked") 184 end 185 186## Gtk.TextTagTable 187 188It is possible to populate new instance of the tag table with tags 189during the construction, an array part of constructor argument table is 190expected to contain `Gtk.TextTag` instances which are then automatically 191added to the table. 192 193A new attribute `tag` is added, provides Lua table which can be indexed 194by string representing tag name and returns the appropriate tag (so it is 195essentially a wrapper around `Gtk.TextTagTable:lookup()` method). 196 197Following example demonstrates both capabilities: 198 199 local tag_table = Gtk.TextTagTable { 200 Gtk.TextTag { name = 'plain', color = 'blue' }, 201 Gtk.TextTag { name = 'error', color = 'red' }, 202 } 203 204 assert(tag_table.tag.plain == tag_table:lookup('plain')) 205 206## TreeView and related classes 207 208`Gtk.TreeView` and related classes like `Gtk.TreeModel` are one of the 209most complicated objects in the whole `Gtk`. Lgi adds some overrides 210to simplify the work with them. 211 212### Gtk.TreeModel 213 214Lgi supports direct indexing of treemodel instances by iterators 215(i.e. `Gtk.TreeIter` instances). To get value at specified column 216number, index the resulting value again with column number. Note that 217although `Gtk` uses 0-based column numbers, Lgi remaps them to 1-based 218numbers, because working with 1-based arrays is much more natural for 219Lua. 220 221Another extension provided by Lgi is 222`Gtk.TreeModel:pairs([parent_iter])` method for Lua-native iteration of 223the model. This method returns 3 values suitable to pass to generic 224`for`, so that standard Lua iteration protocol can be used. See the 225example in the next chapter which uses this technique. 226 227### Gtk.ListStore and Gtk.TreeStore 228 229Standard `Gtk.TreeModel` implementations, `Gtk.ListStore` and 230`Gtk.TreeStore` extend the concept of indexing model instance with 231iterators also to writing values. Indexing resulting value with 2321-based column number allows writing individual values, while 233assigning the table containing column-keyed values allows assigning 234multiple values at once. Following example illustrates all these 235techniques: 236 237 local PersonColumn = { NAME = 1, AGE = 2, EMPLOYEE = 3 } 238 local store = Gtk.ListStore.new { 239 [PersonColumn.NAME] = GObject.Type.STRING, 240 [PersonColumn.AGE] = GObject.Type.INT, 241 [PersonColumn.EMPLOYEE] = GObject.Type.BOOLEAN, 242 } 243 local person = store:append() 244 store[person] = { 245 [PersonColumn.NAME] = "John Doe", 246 [PersonColumn.AGE] = 45, 247 [PersonColumn.EMPLOYEE] = true, 248 } 249 assert(store[person][PersonColumn.AGE] == 45) 250 store[person][PersonColumn.AGE] = 42 251 assert(store[person][PersonColumn.AGE] == 42) 252 253 -- Print all persons in the store 254 for i, p in store:pairs() do 255 print(p[PersonColumn.NAME], p[PersonColumn.AGE]) 256 end 257 258Note that `append` and `insert` methods are overridden and accept 259additional parameter containing table with column/value pairs, so 260creation section of previous example can be simplified to: 261 262 local person = store:append { 263 [PersonColumn.NAME] = "John Doe", 264 [PersonColumn.AGE] = 45, 265 [PersonColumn.EMPLOYEE] = true, 266 } 267 268Note that while the example uses `Gtk.ListStore`, similar overrides 269are provided also for `Gtk.TreeStore`. 270 271### Gtk.TreeView and Gtk.TreeViewColumn 272 273Lgi provides `Gtk.TreeViewColumn:set(cell, data)` method, which allows 274assigning either a set of `cell` renderer attribute->model column 275pairs (in case that `data` argument is a table), or assigns custom 276data function for specified cell renderer (when `data` is a function). 277Note that column must already have assigned cell renderer. See 278`gtk_tree_view_column_set_attributes()` and 279`gtk_tree_view_column_set_cell_data_func()` for precise documentation. 280 281The override `Gtk.TreeViewColumn:add(def)` composes both adding new 282cellrenderer and setting attributes or data function. `def` argument 283is a table, containing cell renderer instance at index 1 and `data` at 284index 2. Optionally, it can also contain `expand` attribute (set to 285`true` or `false`) and `align` (set either to `start` or `end`). This 286method is basically combination of `gtk_tree_view_column_pack_start()` 287or `gtk_tree_view_column_pack_end()` and `set()` override method. 288 289Array part of `Gtk.TreeViewColumn` constructor call is mapped to call 290`Gtk.TreeViewColumn:add()` method, and array part of `Gtk.TreeView` 291constructor call is mapped to call `Gtk.TreeView:append_column()`, and 292this allows composing the whole initialized treeview in a declarative 293style like in the example below: 294 295 -- This example reuses 'store' model created in examples in 296 -- Gtk.TreeModel chapter. 297 local view = Gtk.TreeView { 298 model = store, 299 Gtk.TreeViewColumn { 300 title = "Name and age", 301 expand = true, 302 { Gtk.CellRendererText {}, { text = PersonColumn.NAME } }, 303 { Gtk.CellRendererText {}, { text = PersonColumn.AGE } }, 304 }, 305 Gtk.TreeViewColumn { 306 title = "Employee", 307 { Gtk.CellRendererToggle {}, { active = PersonColumn.EMPLOYEE } } 308 }, 309 } 310