lib/linux: implement ToggleEvent
[nit.git] / lib / linux / ui.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Implementation of the app.nit UI module for GNU/Linux
16 module ui
17
18 import app::ui
19 import gtk::v3_10
20
21 import data_store
22
23 # Request width of the GTK window for an app.nit application
24 #
25 # This is the minimum width of the window, it may grow bigger to fit content.
26 fun gtk_window_width_request: Int do return 480
27
28 redef class App
29 redef fun setup do gtk_init
30
31 # Single GTK window of this application
32 var native_window: GtkWindow is lazy do
33 var win = new GtkWindow(new GtkWindowType.toplevel)
34 win.connect_destroy_signal_to_quit
35 win.titlebar = native_header_bar
36 win.add native_stack
37 return win
38 end
39
40 # GTK 3 header bar
41 var native_header_bar: GtkHeaderBar is lazy do
42 var bar = new GtkHeaderBar
43 bar.title = "app.nit" # TODO offer a portable API to name windows
44 bar.show_close_button = true
45
46 # TODO add back button
47
48 return bar
49 end
50
51 # Root `GtkStack` used to simulate the many app.nit windows
52 var native_stack: GtkStack is lazy do
53 var stack = new GtkStack
54 stack.homogeneous = false
55 return stack
56 end
57
58 # On GNU/Linux, we go through all the callbacks once,
59 # there is no complex life-cycle.
60 redef fun run
61 do
62 app.on_create
63 app.on_restore_state
64 app.on_start
65 app.on_resume
66
67 native_window.show_all
68 gtk_main
69
70 app.on_pause
71 app.on_stop
72 app.on_save_state
73 app.on_destroy
74 end
75
76 # Spacing between GTK controls, default at 2
77 var control_spacing = 2 is writable
78
79 redef fun window=(window)
80 do
81 var root_view = window.view
82 assert root_view != null
83 native_stack.add root_view.native
84 native_stack.visible_child = root_view.native
85
86 # FIXME These settings forces the GTK window to resize to its minimum
87 # size when changing app.nit windows. It is not pretty, but it could be
88 # improved with GTK 3.18 and interpolate_size.
89 native_window.resizable = false
90
91 super
92 end
93 end
94
95 redef class Control
96 super GtkCallable
97 super Finalizable
98
99 # The GTK element used to implement `self`
100 fun native: NATIVE is abstract
101
102 # Type of `native`
103 type NATIVE: GtkWidget
104
105 redef fun finalize
106 do
107 var native = native
108 if not native.address_is_null then native.destroy
109 end
110 end
111
112 redef class CompositeControl
113 end
114
115 # On GNU/Linux, a window is implemented by placing the `view` in a `GtkStack` in the single GTK window
116 redef class Window
117
118 # Root view of this window
119 var view: nullable View = null
120
121 redef fun add(view)
122 do
123 if view isa View then
124 self.view = view
125 view.native.valign = new GtkAlign.start
126 view.native.set_size_request(gtk_window_width_request, 0)
127 end
128
129 super
130 end
131 end
132
133 redef class View
134 init do native.show
135
136 redef fun enabled do return native.sensitive
137 redef fun enabled=(enabled) do native.sensitive = enabled or else true
138 end
139
140 redef class Layout
141 redef type NATIVE: GtkBox
142
143 redef fun add(item)
144 do
145 super
146 if item isa View then native.add item.native
147 end
148
149 redef fun remove(item)
150 do
151 super
152 if item isa View then native.remove item.native
153 end
154 end
155
156 redef class HorizontalLayout
157 redef var native = new GtkBox(new GtkOrientation.horizontal, app.control_spacing)
158
159 redef fun add(item)
160 do
161 super
162 # FIXME abstract the use either homogeneous or weight to balance views size in a layout
163 native.homogeneous = true
164 native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
165 end
166 end
167
168 redef class VerticalLayout
169 redef var native = new GtkBox(new GtkOrientation.vertical, app.control_spacing)
170
171 redef fun add(item)
172 do
173 super
174
175 native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
176 end
177 end
178
179 # On GNU/Linux, this is implemented by a `GtkListBox` inside a `GtkScrolledWindow`
180 redef class ListLayout
181
182 redef type NATIVE: GtkScrolledWindow
183
184 redef var native = new GtkScrolledWindow
185
186 # Container inside `native`
187 var native_list_box = new GtkListBox
188
189 # `GtkListBoxRow` used to contains children `View`s
190 var native_rows = new Map[View, GtkListBoxRow]
191
192 init do
193 native_list_box.selection_mode = new GtkSelectionMode.none
194 native.add native_list_box
195
196 # Set the size of the GtkScrolledWindow:
197 # use content width and set static height
198 native.set_policy(new GtkPolicyType.never, new GtkPolicyType.automatic)
199 native.set_size_request(gtk_window_width_request, 640)
200 end
201
202 redef fun add(item)
203 do
204 super
205 if item isa View then
206 var native_row = new GtkListBoxRow
207 #native_row.activable = false # TODO with GTK 3.14
208 #native_row.selectable = false
209 native_row.add item.native
210
211 native_rows[item] = native_row
212 native_list_box.add native_row
213 native_row.show
214 end
215 end
216
217 redef fun remove(item)
218 do
219 super
220 if item isa View then
221 var native_row = native_rows.get_or_null(item)
222 if native_row == null then
223 print_error "Error: {self} does not contains {item}"
224 return
225 end
226
227 native_list_box.remove native_row
228 native_rows.keys.remove item
229 native_row.destroy
230 end
231 end
232 end
233
234 redef class Button
235 redef type NATIVE: GtkButton
236 redef var native = new GtkButton
237
238 redef fun text do return native.text
239 redef fun text=(value) do native.text = (value or else "").to_s
240
241 redef fun signal(sender, data) do notify_observers new ButtonPressEvent(self)
242
243 init do native.signal_connect("clicked", self, null)
244 end
245
246 redef class Label
247 redef type NATIVE: GtkLabel
248 redef var native = new GtkLabel("")
249
250 redef fun text do return native.text
251
252 redef fun text=(value)
253 do
254 var cfmt = pango_markup_format.to_cstring
255 var cvalue = (value or else "").to_cstring
256 native.set_markup(cfmt, cvalue)
257 end
258
259 # Pango format string applied to the `text` attribute
260 var pango_markup_format = "\%s" is lazy
261
262 redef fun size=(size)
263 do
264 if size == null or size == 1.0 then
265 pango_markup_format = "\%s"
266 else if size < 1.0 then
267 pango_markup_format = "<span size=\"small\">\%s</span>"
268 else#if size > 1.0 then
269 pango_markup_format = "<span size=\"large\">\%s</span>"
270 end
271
272 # Force reloading `text`
273 text = text
274 end
275
276 redef fun align=(align)
277 do
278 align = align or else 0.0
279
280 # Set whole label alignement
281 native.set_alignment(align, 0.5)
282
283 # Set multiline justification
284 native.justify = if align == 0.5 then
285 new GtkJustification.center
286 else if align < 0.5 then
287 new GtkJustification.left
288 else#if align > 0.5 then
289 new GtkJustification.right
290 end
291 end
292
293 redef class CheckBox
294 redef type NATIVE: GtkCheckButton
295 redef var native = new GtkCheckButton
296
297 redef fun signal(sender, data) do notify_observers new ToggleEvent(self)
298 init do native.signal_connect("toggled", self, null)
299
300 redef fun text do return native.text
301 redef fun text=(value) do native.text = (value or else "").to_s
302
303 redef fun is_checked do return native.active
304 redef fun is_checked=(value) do native.active = value
305 end
306
307 redef class TextInput
308 redef type NATIVE: GtkEntry
309 redef var native = new GtkEntry
310
311 redef fun text do return native.text
312 redef fun text=(value) do
313 if value == null then value = ""
314 native.text = value.to_s
315 end
316
317 redef fun is_password=(value)
318 do
319 native.visibility = value != true
320 super
321 end
322 end