# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Implementation of the app.nit UI module for GNU/Linux module ui import app::ui import gtk::v3_10 import data_store # Request width of the GTK window for an app.nit application # # This is the minimum width of the window, it may grow bigger to fit content. fun gtk_window_width_request: Int do return 480 redef class App redef fun setup do gtk_init # Single GTK window of this application var native_window: GtkWindow is lazy do var win = new GtkWindow(new GtkWindowType.toplevel) win.connect_destroy_signal_to_quit win.titlebar = native_header_bar win.add native_stack return win end # GTK 3 header bar var native_header_bar: GtkHeaderBar is lazy do var bar = new GtkHeaderBar bar.title = "app.nit" # TODO offer a portable API to name windows bar.show_close_button = true bar.add back_button.native return bar end # Root `GtkStack` used to simulate the many app.nit windows var native_stack: GtkStack is lazy do var stack = new GtkStack stack.homogeneous = false return stack end # Button on the header bar to go back var back_button = new BackButton is lazy # On GNU/Linux, we go through all the callbacks once, # there is no complex life-cycle. redef fun run do app.on_create app.on_restore_state app.on_resume gtk_main app.on_pause app.on_stop app.on_save_state end # Spacing between GTK controls, default at 2 var control_spacing = 2 is writable redef fun window=(window) do var root_view = window.view assert root_view != null native_stack.add root_view.native native_stack.visible_child = root_view.native # FIXME These settings forces the GTK window to resize to its minimum # size when changing app.nit windows. It is not pretty, but it could be # improved with GTK 3.18 and interpolate_size. native_window.resizable = false native_window.show_all super if window.enable_back_button then back_button.native.show else back_button.native.hide end end redef class Control super GtkCallable super Finalizable # The GTK element used to implement `self` fun native: NATIVE is abstract # Type of `native` type NATIVE: GtkWidget redef fun finalize do var native = native if not native.address_is_null then native.destroy end end redef class CompositeControl end # On GNU/Linux, a window is implemented by placing the `view` in a `GtkStack` in the single GTK window redef class Window # Root view of this window var view: nullable View = null redef fun add(view) do if view isa View then self.view = view view.native.valign = new GtkAlign.start view.native.set_size_request(gtk_window_width_request, 0) end super end end redef class View init do native.show redef fun enabled do return native.sensitive redef fun enabled=(enabled) do native.sensitive = enabled or else true end redef class Layout redef type NATIVE: GtkBox redef fun add(item) do super if item isa View then native.add item.native end redef fun remove(item) do super if item isa View then native.remove item.native end end redef class HorizontalLayout redef var native = new GtkBox(new GtkOrientation.horizontal, app.control_spacing) redef fun add(item) do super # FIXME abstract the use either homogeneous or weight to balance views size in a layout native.homogeneous = true native.set_child_packing(item.native, true, true, 0, new GtkPackType.start) end end redef class VerticalLayout redef var native = new GtkBox(new GtkOrientation.vertical, app.control_spacing) redef fun add(item) do super native.set_child_packing(item.native, true, true, 0, new GtkPackType.start) end end # On GNU/Linux, this is implemented by a `GtkListBox` inside a `GtkScrolledWindow` redef class ListLayout redef type NATIVE: GtkScrolledWindow redef var native = new GtkScrolledWindow # Container inside `native` var native_list_box = new GtkListBox # `GtkListBoxRow` used to contains children `View`s var native_rows = new Map[View, GtkListBoxRow] init do native_list_box.selection_mode = new GtkSelectionMode.none native.add native_list_box # Set the size of the GtkScrolledWindow: # use content width and set static height native.set_policy(new GtkPolicyType.never, new GtkPolicyType.automatic) native.set_size_request(gtk_window_width_request, 640) end redef fun add(item) do super if item isa View then var native_row = new GtkListBoxRow #native_row.activable = false # TODO with GTK 3.14 #native_row.selectable = false native_row.add item.native native_rows[item] = native_row native_list_box.add native_row native_row.show end end redef fun remove(item) do super if item isa View then var native_row = native_rows.get_or_null(item) if native_row == null then print_error "Error: {self} does not contains {item}" return end native_list_box.remove native_row native_rows.keys.remove item native_row.destroy end end end redef class Button redef type NATIVE: GtkButton redef var native = new GtkButton redef fun text do return native.text redef fun text=(value) do native.text = (value or else "").to_s redef fun signal(sender, data) do notify_observers new ButtonPressEvent(self) init do native.signal_connect("clicked", self, null) end # Button to go back between windows class BackButton super Button # TODO i18n redef fun text=(value) do super(value or else "Back") redef fun signal(sender, data) do super app.window.on_back_button end end redef class Label redef type NATIVE: GtkLabel redef var native = new GtkLabel("") redef fun text do return native.text redef fun text=(value) do var cfmt = pango_markup_format.to_cstring var cvalue = (value or else "").to_cstring native.set_markup(cfmt, cvalue) end # Pango format string applied to the `text` attribute var pango_markup_format = "\%s" is lazy redef fun size=(size) do if size == null or size == 1.0 then pango_markup_format = "\%s" else if size < 1.0 then pango_markup_format = "\%s" else#if size > 1.0 then pango_markup_format = "\%s" end # Force reloading `text` text = text end redef fun align=(align) do align = align or else 0.0 # Set whole label alignement native.set_alignment(align, 0.5) # Set multiline justification native.justify = if align == 0.5 then new GtkJustification.center else if align < 0.5 then new GtkJustification.left else#if align > 0.5 then new GtkJustification.right end end redef class CheckBox redef type NATIVE: GtkCheckButton redef var native = new GtkCheckButton redef fun signal(sender, data) do notify_observers new ToggleEvent(self) init do native.signal_connect("toggled", self, null) redef fun text do return native.text redef fun text=(value) do native.text = (value or else "").to_s redef fun is_checked do return native.active redef fun is_checked=(value) do native.active = value end redef class TextInput redef type NATIVE: GtkEntry redef var native = new GtkEntry redef fun text do return native.text redef fun text=(value) do if value == null then value = "" native.text = value.to_s end redef fun is_password=(value) do native.visibility = value != true super end end redef class Text redef fun open_in_browser do system("xdg-open '{self.escape_to_sh}' &") end