Merge: doc: fixed some typos and other misc. corrections
[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 bar.add back_button.native
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 # Button on the header bar to go back
59 var back_button = new BackButton is lazy
60
61 # On GNU/Linux, we go through all the callbacks once,
62 # there is no complex life-cycle.
63 redef fun run
64 do
65 app.on_create
66 app.on_restore_state
67 app.on_resume
68
69 gtk_main
70
71 app.on_pause
72 app.on_stop
73 app.on_save_state
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 native_window.show_all
92
93 super
94
95 if window.enable_back_button then
96 back_button.native.show
97 else back_button.native.hide
98 end
99 end
100
101 redef class Control
102 super GtkCallable
103 super Finalizable
104
105 # The GTK element used to implement `self`
106 fun native: NATIVE is abstract
107
108 # Type of `native`
109 type NATIVE: GtkWidget
110
111 redef fun finalize
112 do
113 var native = native
114 if not native.address_is_null then native.destroy
115 end
116 end
117
118 redef class CompositeControl
119 end
120
121 # On GNU/Linux, a window is implemented by placing the `view` in a `GtkStack` in the single GTK window
122 redef class Window
123
124 # Root view of this window
125 var view: nullable View = null
126
127 redef fun add(view)
128 do
129 if view isa View then
130 self.view = view
131 view.native.valign = new GtkAlign.start
132 view.native.set_size_request(gtk_window_width_request, 0)
133 end
134
135 super
136 end
137 end
138
139 redef class View
140 init do native.show
141
142 redef fun enabled do return native.sensitive
143 redef fun enabled=(enabled) do native.sensitive = enabled or else true
144 end
145
146 redef class Layout
147 redef type NATIVE: GtkBox
148
149 redef fun add(item)
150 do
151 super
152 if item isa View then native.add item.native
153 end
154
155 redef fun remove(item)
156 do
157 super
158 if item isa View then native.remove item.native
159 end
160 end
161
162 redef class HorizontalLayout
163 redef var native = new GtkBox(new GtkOrientation.horizontal, app.control_spacing)
164
165 redef fun add(item)
166 do
167 super
168 # FIXME abstract the use either homogeneous or weight to balance views size in a layout
169 native.homogeneous = true
170 native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
171 end
172 end
173
174 redef class VerticalLayout
175 redef var native = new GtkBox(new GtkOrientation.vertical, app.control_spacing)
176
177 redef fun add(item)
178 do
179 super
180
181 native.set_child_packing(item.native, true, true, 0, new GtkPackType.start)
182 end
183 end
184
185 # On GNU/Linux, this is implemented by a `GtkListBox` inside a `GtkScrolledWindow`
186 redef class ListLayout
187
188 redef type NATIVE: GtkScrolledWindow
189
190 redef var native = new GtkScrolledWindow
191
192 # Container inside `native`
193 var native_list_box = new GtkListBox
194
195 # `GtkListBoxRow` used to contains children `View`s
196 var native_rows = new Map[View, GtkListBoxRow]
197
198 init do
199 native_list_box.selection_mode = new GtkSelectionMode.none
200 native.add native_list_box
201
202 # Set the size of the GtkScrolledWindow:
203 # use content width and set static height
204 native.set_policy(new GtkPolicyType.never, new GtkPolicyType.automatic)
205 native.set_size_request(gtk_window_width_request, 640)
206 end
207
208 redef fun add(item)
209 do
210 super
211 if item isa View then
212 var native_row = new GtkListBoxRow
213 #native_row.activable = false # TODO with GTK 3.14
214 #native_row.selectable = false
215 native_row.add item.native
216
217 native_rows[item] = native_row
218 native_list_box.add native_row
219 native_row.show
220 end
221 end
222
223 redef fun remove(item)
224 do
225 super
226 if item isa View then
227 var native_row = native_rows.get_or_null(item)
228 if native_row == null then
229 print_error "Error: {self} does not contains {item}"
230 return
231 end
232
233 native_list_box.remove native_row
234 native_rows.keys.remove item
235 native_row.destroy
236 end
237 end
238 end
239
240 redef class Button
241 redef type NATIVE: GtkButton
242 redef var native = new GtkButton
243
244 redef fun text do return native.text
245 redef fun text=(value) do native.text = (value or else "").to_s
246
247 redef fun signal(sender, data) do notify_observers new ButtonPressEvent(self)
248
249 init do native.signal_connect("clicked", self, null)
250 end
251
252 # Button to go back between windows
253 class BackButton
254 super Button
255
256 # TODO i18n
257 redef fun text=(value) do super(value or else "Back")
258
259 redef fun signal(sender, data)
260 do
261 super
262
263 app.window.on_back_button
264 end
265 end
266
267 redef class Label
268 redef type NATIVE: GtkLabel
269 redef var native = new GtkLabel("")
270
271 redef fun text do return native.text
272
273 redef fun text=(value)
274 do
275 var cfmt = pango_markup_format.to_cstring
276 var cvalue = (value or else "").to_cstring
277 native.set_markup(cfmt, cvalue)
278 end
279
280 # Pango format string applied to the `text` attribute
281 var pango_markup_format = "\%s" is lazy
282
283 redef fun size=(size)
284 do
285 if size == null or size == 1.0 then
286 pango_markup_format = "\%s"
287 else if size < 1.0 then
288 pango_markup_format = "<span size=\"small\">\%s</span>"
289 else#if size > 1.0 then
290 pango_markup_format = "<span size=\"large\">\%s</span>"
291 end
292
293 # Force reloading `text`
294 text = text
295 end
296
297 redef fun align=(align)
298 do
299 align = align or else 0.0
300
301 # Set whole label alignement
302 native.set_alignment(align, 0.5)
303
304 # Set multiline justification
305 native.justify = if align == 0.5 then
306 new GtkJustification.center
307 else if align < 0.5 then
308 new GtkJustification.left
309 else#if align > 0.5 then
310 new GtkJustification.right
311 end
312 end
313
314 redef class CheckBox
315 redef type NATIVE: GtkCheckButton
316 redef var native = new GtkCheckButton
317
318 redef fun signal(sender, data) do notify_observers new ToggleEvent(self)
319 init do native.signal_connect("toggled", self, null)
320
321 redef fun text do return native.text
322 redef fun text=(value) do native.text = (value or else "").to_s
323
324 redef fun is_checked do return native.active
325 redef fun is_checked=(value) do native.active = value
326 end
327
328 redef class TextInput
329 redef type NATIVE: GtkEntry
330 redef var native = new GtkEntry
331
332 redef fun text do return native.text
333 redef fun text=(value) do
334 if value == null then value = ""
335 native.text = value.to_s
336 end
337
338 redef fun is_password=(value)
339 do
340 native.visibility = value != true
341 super
342 end
343 end
344
345 redef class Text
346 redef fun open_in_browser do system("xdg-open '{self.escape_to_sh}' &")
347 end