Merge: Serialization support SimpleCollection and Map, also bug fixes
authorJean Privat <jean@pryen.org>
Wed, 20 May 2015 00:30:22 +0000 (20:30 -0400)
committerJean Privat <jean@pryen.org>
Wed, 20 May 2015 00:30:22 +0000 (20:30 -0400)
This PR is the result of using Nit serialization to implement game saving/loading in WBTW.

Update the serialization to work with (or avoid) the new auto constructors.

Support serializing Sets and Maps.

The `is_same_serialized` fix a problem where two instances were wrongly serialized as being the same. Because it previously used a `HashMap` (and thus `==`), two different _empty_ arrays were serialized as one and resulted in a single object on deserialization.

Once this and #1348 are merge, I'll update the calculator example to use the serialization.

Pull-Request: #1352
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

20 files changed:
examples/calculator/.gitignore [new file with mode: 0644]
examples/calculator/Makefile
examples/calculator/src/calculator.nit [new file with mode: 0644]
examples/calculator/src/calculator_android.nit [deleted file]
examples/calculator/src/calculator_gtk.nit [deleted file]
examples/calculator/src/calculator_test.nit
lib/android/README.md
lib/android/examples/Makefile
lib/android/examples/src/ui_test.nit
lib/android/ui/ui.nit
lib/app/README.md [new file with mode: 0644]
lib/app/ui.nit [new file with mode: 0644]
lib/gtk/v3_4/gtk_core.nit
lib/gtk/v3_6.nit
lib/linux/ui.nit [new file with mode: 0644]
src/doc/doc_phases/doc_structure.nit
src/doc/html_templates/html_model.nit
src/doc/html_templates/html_templates.nit
src/location.nit
src/neo.nit

diff --git a/examples/calculator/.gitignore b/examples/calculator/.gitignore
new file mode 100644 (file)
index 0000000..700b511
--- /dev/null
@@ -0,0 +1 @@
+res/
index dd66960..071fa2b 100644 (file)
@@ -1,12 +1,26 @@
-all:
-       mkdir -p bin/
+NITC=../../bin/nitc
+NITLS=../../bin/nitls
 
-       ../../bin/nitc --dir bin/ src/calculator_gtk.nit src/calculator_test.nit
+all: bin/calculator bin/calculator.apk bin/test
 
-android:
-       mkdir -p bin/ res/
+bin/calculator: $(shell ${NITLS} -M src/calculator.nit ../../lib/linux/ui.nit) ${NITC}
+       mkdir -p bin
+       ${NITC} -o $@ src/calculator.nit -m ../../lib/linux/ui.nit
+
+bin/calculator.apk: $(shell ${NITLS} -M src/calculator.nit ../../lib/android/ui/) ${NITC} ../../contrib/inkscape_tools/bin/svg_to_icons
+       mkdir -p bin res
        ../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out res/
-       ../../bin/nitc -o bin/calculator.apk src/calculator_android.nit
+       ${NITC} -o $@ src/calculator.nit -m ../../lib/android/ui/
+
+../../contrib/inkscape_tools/bin/svg_to_icons:
+       make -C ../../contrib/inkscape_tools/
 
-android-install: android
+android-install: bin/calculator.apk
        adb install -r bin/calculator.apk
+
+bin/test: $(shell ${NITLS} -M src/calculator_test.nit) ${NITC}
+       mkdir -p bin
+       ${NITC} -o $@ src/calculator_test.nit
+
+check: bin/test
+       bin/test
diff --git a/examples/calculator/src/calculator.nit b/examples/calculator/src/calculator.nit
new file mode 100644 (file)
index 0000000..2e2f437
--- /dev/null
@@ -0,0 +1,114 @@
+# 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.
+
+# Portable calculator UI
+module calculator is
+app_name "app.nit Calc."
+       app_version(0, 1, git_revision)
+       app_namespace "org.nitlanguage.calculator"
+
+       # Lock in portrait mode
+       android_manifest_activity """android:screenOrientation="portrait""""
+       android_api_target 15
+end
+
+import app::ui
+import app::data_store
+import android::aware
+
+import calculator_logic
+
+redef class App
+       redef fun on_create
+       do
+               # Create the main window
+               window = new CalculatorWindow
+               super
+       end
+end
+
+# The main (and only) window of this calculator
+class CalculatorWindow
+       super Window
+
+       # Calculator context, our business logic
+       private var context = new CalculatorContext
+
+       # Main window layout
+       private var layout = new VerticalLayout(parent=self)
+
+       # Main display, at the top of the screen
+       private var display = new TextInput(parent=layout)
+
+       # Maps operators as `String` to their `Button`
+       private var buttons = new HashMap[String, Button]
+
+       init
+       do
+               # All the button labels, row by row
+               var rows = [["7", "8", "9", "+"],
+                           ["4", "5", "6", "-"],
+                           ["1", "2", "3", "*"],
+                           ["0", ".", "C", "/"],
+                           ["="]]
+
+               for row in rows do
+                       var row_layout = new HorizontalLayout(parent=layout)
+
+                       for op in row do
+                               var but = new Button(parent=row_layout, text=op)
+                               but.observers.add self
+                               buttons[op] = but
+                       end
+               end
+       end
+
+       redef fun on_event(event)
+       do
+               if event isa ButtonPressEvent then
+
+                       var op = event.sender.text
+                       if op == "." then
+                               event.sender.enabled = false
+                               context.switch_to_decimals
+                       else if op.is_numeric then
+                               var n = op.to_i
+                               context.push_digit n
+                       else
+                               buttons["."].enabled = true
+                               context.push_op op.chars.first
+                       end
+
+                       display.text = context.display_text
+               end
+       end
+
+       redef fun on_save_state
+       do
+               app.data_store["context"] = context.to_json
+               super
+       end
+
+       redef fun on_restore_state
+       do
+               super
+
+               var save = app.data_store["context"]
+               if save == null then return
+               assert save isa String
+
+               self.context = new CalculatorContext.from_json(save)
+               display.text = context.display_text
+       end
+end
diff --git a/examples/calculator/src/calculator_android.nit b/examples/calculator/src/calculator_android.nit
deleted file mode 100644 (file)
index 1b1e686..0000000
+++ /dev/null
@@ -1,135 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
-# 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.
-
-# Android calculator application
-module calculator_android is
-       app_name "app.nit Calc."
-       app_version(0, 1, git_revision)
-       app_namespace "org.nitlanguage.calculator"
-
-       # Lock in portrait mode
-       android_manifest_activity """android:screenOrientation="portrait""""
-end
-
-# FIXME the next line should import `android` only when it uses nit_activity
-import android::log
-import android::ui
-
-import calculator_logic
-
-redef class Activity
-       super EventCatcher
-
-       private var context = new CalculatorContext
-
-       # The main display, at the top of the screen
-       private var display: EditText
-
-       # Maps operators as `String` to their `Button`
-       private var op2but = new HashMap[String, Button]
-
-       # Has this window been initialized?
-       private var inited = false
-
-       redef fun on_start
-       do
-               super
-
-               if inited then return
-               inited = true
-
-               # Setup UI
-               var layout = new NativeLinearLayout(native)
-               layout.set_vertical
-
-               # Display screen
-               var display = new EditText
-               layout.add_view_with_weight(display.native, 1.0)
-               display.text_size = 36.0
-               self.display = display
-
-               # Buttons; numbers and operators
-               var ops = [["7", "8", "9", "+"],
-                          ["4", "5", "6", "-"],
-                          ["1", "2", "3", "*"],
-                          ["0", ".", "C", "/"],
-                          ["="]]
-
-               for line in ops do
-                       var buts_layout = new NativeLinearLayout(native)
-                       buts_layout.set_horizontal
-                       layout.add_view_with_weight(buts_layout, 1.0)
-
-                       for op in line do
-                               var but = new Button
-                               but.event_catcher = self
-                               but.text = op
-                               but.text_size = 40
-                               buts_layout.add_view_with_weight(but.native, 1.0)
-                               op2but[op] = but
-                       end
-               end
-
-               native.content_view = layout
-       end
-
-       redef fun on_save_instance_state(state)
-       do
-               super
-
-               var nity = new Bundle.from(state)
-               nity["context"] = context.to_json
-       end
-
-       redef fun on_restore_instance_state(state)
-       do
-               super
-
-               var nity = new Bundle.from(state)
-               if not nity.has("context") then return
-
-               var json = nity.string("context")
-               if json == null then return
-
-               context = new CalculatorContext.from_json(json)
-       end
-
-       redef fun on_resume
-       do
-               display.text = context.display_text
-       end
-
-       redef fun catch_event(event)
-       do
-               if event isa ClickEvent then
-                       var sender = event.sender
-                       var op = sender.text
-
-                       if op == "." then
-                               sender.enabled = false
-                               context.switch_to_decimals
-                       else if op.is_numeric then
-                               var n = op.to_i
-                               context.push_digit n
-                       else
-                               op2but["."].enabled = true
-                               context.push_op op.chars.first
-                       end
-
-                       display.text = context.display_text
-               end
-       end
-end
diff --git a/examples/calculator/src/calculator_gtk.nit b/examples/calculator/src/calculator_gtk.nit
deleted file mode 100644 (file)
index 7586230..0000000
+++ /dev/null
@@ -1,113 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2013-2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
-# 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.
-
-# GTK calculator
-module calculator_gtk
-
-import calculator_logic
-
-import gtk
-
-# GTK calculator UI
-class CalculatorGui
-       super GtkCallable
-
-       private var win: GtkWindow is noinit
-       private var container: GtkGrid is noinit
-
-       private var lbl_disp: GtkLabel is noinit
-       private var but_eq: GtkButton is noinit
-       private var but_dot: GtkButton is noinit
-
-       private var context = new CalculatorContext
-
-       redef fun signal(sender, op)
-       do
-               if op isa Char then # is an operation
-                       if op == '.' then
-                               but_dot.sensitive = false
-                               context.switch_to_decimals
-                       else
-                               but_dot.sensitive = true
-                               context.push_op op
-                       end
-               else if op isa Int then # is a number
-                       context.push_digit op
-               end
-
-               lbl_disp.text = context.display_text
-       end
-
-       init
-       do
-               init_gtk
-
-               win = new GtkWindow(new GtkWindowType.toplevel)
-               win.connect_destroy_signal_to_quit
-
-               container = new GtkGrid
-               win.add container
-
-               lbl_disp = new GtkLabel("_")
-               container.attach(lbl_disp, 0, 0, 5, 1)
-
-               # Digits
-               for n in [0..9] do
-                       var but = new GtkButton.with_label(n.to_s)
-                       but.request_size(64, 64)
-                       but.signal_connect("clicked", self, n)
-                       if n == 0 then
-                               container.attach(but, 0, 4, 1, 1)
-                       else container.attach(but, (n-1)%3, 3-(n-1)/3, 1, 1)
-               end
-
-               # Operators
-               var r = 1
-               for op in ['+', '-', '*', '/'] do
-                       var but = new GtkButton.with_label(op.to_s)
-                       but.request_size(64, 64)
-                       but.signal_connect("clicked", self, op)
-                       container.attach(but, 3, r, 1, 1)
-                       r+=1
-               end
-
-               # =
-               but_eq = new GtkButton.with_label("=")
-               but_eq.request_size(64, 64)
-               but_eq.signal_connect("clicked", self, '=')
-               container.attach(but_eq, 4, 3, 1, 2)
-
-               # .
-               but_dot = new GtkButton.with_label(".")
-               but_dot.request_size(64, 64)
-               but_dot.signal_connect("clicked", self, '.')
-               container.attach(but_dot, 1, 4, 1, 1)
-
-               # C
-               var but_c =  new GtkButton.with_label("C")
-               but_c.request_size(64, 64)
-               but_c.signal_connect("clicked", self, 'C')
-               container.attach(but_c, 2, 4, 1, 1)
-
-               win.show_all
-       end
-end
-
-# Do not show GUI in when testing
-if "NIT_TESTING".environ == "true" then exit 0
-
-var app = new CalculatorGui
-run_gtk
index 6937751..270854c 100644 (file)
@@ -29,7 +29,7 @@ context.push_op( '*' )
 context.push_digit( 2 )
 context.push_op( '=' )
 var r = context.result
-assert r == "30.00" else print r or else "-"
+assert r == 30 else print r or else "-"
 
 context = new CalculatorContext
 context.push_digit( 1 )
@@ -40,14 +40,14 @@ context.push_op( '*' )
 context.push_digit( 3 )
 context.push_op( '=' )
 r = context.result
-assert r == "42.30" else print r or else "-"
+assert r == 42.3 else print r or else "-"
 
 context.push_op( '+' )
 context.push_digit( 1 )
 context.push_digit( 1 )
 context.push_op( '=' )
 r = context.result
-assert r == "53.30" else print r or else "-"
+assert r == 53.3 else print r or else "-"
 
 context = new CalculatorContext
 context.push_digit( 4 )
@@ -58,7 +58,7 @@ context.push_op( '/' )
 context.push_digit( 3 )
 context.push_op( '=' )
 r = context.result
-assert r == "14.10" else print r or else "-"
+assert r == 14.1 else print r or else "-"
 
 #test multiple decimals
 context = new CalculatorContext
@@ -72,7 +72,7 @@ context.push_op( '+' )
 context.push_digit( 1 )
 context.push_op( '=' )
 r = context.result
-assert r == "51.123" else print r or else "-"
+assert r == 51.123 else print r or else "-"
 
 #test 'C' button
 context = new CalculatorContext
@@ -84,4 +84,4 @@ context.push_digit( 0 )
 context.push_op( '=' )
 context.push_op( 'C' )
 r = context.result
-assert r == "0.0" else print r or else "-"
+assert r == null else print r
index fb11c65..47c0e4b 100644 (file)
@@ -13,29 +13,15 @@ The tools `android`, `ndk-build` and `ant` must be in your PATH.
 
 # Configure your Android application
 
-The `app.nit` framework and this project offers some services to
-customized the generated Android application.
+The _app.nit_ framework and this project offers some services to
+customize the generated Android application.
 
-## Module annotations
+## Annotations
 
-* `app_version` specifies the version of the generated APK file.
-It takes 3 arguments: the major, minor and revision version numbers.
-The special function `git_revision` will use the prefix of the hash of the
-latest git commit. The default version is 1.0.
+* All _app.nit_ annotations are applied to Android projects:
+  `app_name`, `app_namespace` and `app_version`.
 
-    Example: `app_version(1, 0, git_revision)`
-
-* `app_name` takes a single argument, the visible name of the Android
-application. By default, the compiler would use the name of the target
-module. This name will be used as the name of the main activity and
-as the launcher name.
-
-    Example: `app_name "My App"`
-
-* `app_namespace` specifies the package used by the generated Java
-classes and the APK file. Once the application is published, this
-value should not be changed. By default, the compiler will use
-the package `org.nitlanguage.{module_name}`.
+    See: `../app/README.md`
 
 * Custom information can be added to the Android manifest file
 using the annotations `android_manifest`, `android_manifest_application`
@@ -47,8 +33,8 @@ and `android_manifest_activity`.
     android_manifest """<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>"""
     ~~~
 
-* The API version target can be specified with `min_api_version`,
-`max_api_version` and `target_api_version`. These take a single
+* The API version target can be specified with `android_api_min`,
+`android_api_max` and `android_api_target`. These take a single
 integer as argument. They are applied in the Android manifest as
 `minSdkVesion`, `targetSdkVersion` and `maxSdkVersion`.
 
index f0fd982..17fb8bf 100644 (file)
@@ -2,10 +2,9 @@ android:
        mkdir -p bin/ res/
        ../../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out res/
        ../../../bin/nitc --dir bin/ src/ui_test.nit
-       adb install -r bin/ui_test.apk
 
 install: android
-       adb install -r bin/ui.apk
+       adb install -r bin/ui_test.apk
 
 clean:
        rm -rf bin
index bdb06c5..4f63a92 100644 (file)
@@ -20,44 +20,37 @@ module ui_test is
        app_version(0, 1, git_revision)
        app_namespace "org.nitlanguage.ui_test"
        android_manifest_activity """android:theme="@android:style/Theme.Light""""
+       android_api_target 15
 end
 
-import android
 import android::ui
 import android::toast
 import android::notification
 
 redef class App
-
-       var but_notif: Button
-       var but_toast: Button
-
-       var notif: nullable Notification = null
-
-       var inited = false
-       redef fun init_window
+       redef fun on_create
        do
+               self.window = new Window
                super
+       end
+end
 
-               if inited then return
-               inited = true
+redef class Window
 
-               # Setup UI
-               var context = native_activity
-               var layout = new NativeLinearLayout(context)
-               layout.set_vertical
+       private var layout = new VerticalLayout(parent=self)
 
-               but_notif = new Button
-               but_notif.text = "Show Notification"
-               layout.add_view but_notif.native
+       private var but_notif = new Button(parent=layout, text="Show Notification")
+       private var but_toast = new Button(parent=layout, text="Show Toast")
 
-               but_toast = new Button
-               but_toast.text = "Show Toast"
-               layout.add_view but_toast.native
+       private var notif: nullable Notification = null
 
-               context.content_view = layout
+       init
+       do
+               but_notif.observers.add self
+               but_toast.observers.add self
        end
 
+       # Action when pressing `but_notif`
        fun act_notif
        do
                var notif = self.notif
@@ -72,14 +65,16 @@ redef class App
                end
        end
 
+       # Action when pressing `but_toast`
        fun act_toast
        do
-               toast("Sample toast from app.nit at {get_time}", false)
+               app.toast("Sample toast from app.nit at {get_time}", false)
        end
 
-       redef fun catch_event(event)
+       redef fun on_event(event)
        do
-               if event isa ClickEvent then
+               print "on_event {event}"
+               if event isa ButtonPressEvent then
                        var sender = event.sender
                        if sender == but_notif then
                                act_notif
index a37e572..f534e77 100644 (file)
@@ -1,7 +1,5 @@
 # This file is part of NIT (http://www.nitlanguage.org).
 #
-# Copyright 2014 Alexis Laferrière <alexis.laf@xymus.net>
-#
 # 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
 # Views and services to use the Android native user interface
 module ui
 
+# Implementation note:
+#
+# We cannot rely on `Activity::on_restore_instance_state` to implement
+# `on_restore_state` is it only invoked if there is a bundled state,
+# and we don't use the Android bundled state.
+
 import native_ui
+import log
+import nit_activity
+
+import app::ui
+private import data_store
+
+redef class Control
+       # The Android element used to implement `self`
+       fun native: NATIVE is abstract
 
-# An event from the `app.nit` framework
-interface AppEvent
-       # Reaction to this event
-       fun react do end
+       # Type of `native`
+       type NATIVE: JavaObject
 end
 
-# A control click event
-class ClickEvent
-       super AppEvent
+redef class Window
+       redef var native = app.native_activity
 
-       # Sender of this event
-       var sender: Button
+       redef type NATIVE: NativeActivity
 
-       redef fun react do sender.click self
-end
+       redef fun add(item)
+       do
+               super
 
-# Receiver of events not handled directly by the sender
-interface EventCatcher
-       fun catch_event(event: AppEvent) do end
+               # FIXME abstract the Android restriction where `content_view` must be a layout
+               assert item isa Layout
+               native.content_view = item.native
+       end
 end
 
-redef class App
-       super EventCatcher
-end
+redef class View
+       redef type NATIVE: NativeView
 
-# An `Object` that raises events
-abstract class Eventful
-       var event_catcher: EventCatcher = app is lazy, writable
+       redef fun enabled=(enabled) do native.enabled = enabled or else true
+       redef fun enabled do return native.enabled
 end
 
-#
-## Nity classes and services
-#
+redef class Layout
+       redef type NATIVE: NativeViewGroup
 
-# An Android control with text
-abstract class TextView
-       super Finalizable
-       super Eventful
-
-       # Native Java variant to this Nity class
-       type NATIVE: NativeTextView
+       redef fun add(item)
+       do
+               super
 
-       # The native Java object encapsulated by `self`
-       var native: NATIVE is noinit
+               assert item isa View
 
-       # Get the text of this view
-       fun text: String
-       do
-               var jstr = native.text
-               var str = jstr.to_s
-               jstr.delete_local_ref
-               return str
+               # FIXME abstract the use either homogeneous or weight to balance views size in a layout
+               native.add_view_with_weight(item.native, 1.0)
        end
+end
 
-       # Set the text of this view
-       fun text=(value: Text)
-       do
-               var jstr = value.to_s.to_java_string
-               native.text = jstr
-               jstr.delete_local_ref
+redef class HorizontalLayout
+       redef var native do
+               var layout = new NativeLinearLayout(app.native_activity)
+               layout.set_horizontal
+               return layout
        end
+end
 
-       # Get whether this view is enabled or not
-       fun enabled: Bool do return native.enabled
-
-       # Set if this view is enabled
-       fun enabled=(val: Bool) do native.enabled = val
-
-       # Set the size of the text in this view at `dpi`
-       fun text_size=(dpi: Numeric) do native.text_size = dpi.to_f
-
-       private var finalized = false
-       redef fun finalize
-       do
-               if not finalized then
-                       native.delete_global_ref
-                       finalized = true
-               end
+redef class VerticalLayout
+       redef var native do
+               var layout = new NativeLinearLayout(app.native_activity)
+               layout.set_vertical
+               return layout
        end
 end
 
-# An Android button
-class Button
-       super TextView
+redef class TextView
+       redef type NATIVE: NativeTextView
 
-       redef type NATIVE: NativeButton
-
-       init
-       do
-               var native = new NativeButton(app.native_activity, self)
-               self.native = native.new_global_ref
+       redef fun text do return native.text.to_s
+       redef fun text=(value) do
+               if value == null then value = ""
+               native.text = value.to_java_string
        end
 
-       # Click event
-       #
-       # By default, this method calls `app.catch_event`. It can be specialized
-       # with custom behavior or the receiver of `catch_event` can be changed
-       # with `event_catcher=`.
-       fun click(event: AppEvent) do event_catcher.catch_event(event)
+       # Size of the text
+       fun text_size: Float do return native.text_size
 
-       private fun click_from_native do click(new ClickEvent(self))
+       # Size of the text
+       fun text_size=(text_size: nullable Float) do
+               if text_size != null then native.text_size = text_size
+       end
 end
 
-# An Android editable text field
-class EditText
-       super TextView
-
+redef class TextInput
        redef type NATIVE: NativeEditText
+       redef var native = (new NativeEditText(app.native_activity)).new_global_ref
+end
 
-       init
-       do
-               var native = new NativeEditText(app.activities.first.native)
-               self.native = native.new_global_ref
-       end
+redef class Button
+       super Finalizable
+
+       redef type NATIVE: NativeButton
+       redef var native = (new NativeButton(app.native_activity, self)).new_global_ref
+
+       private fun on_click do notify_observers new ButtonPressEvent(self)
+
+       redef fun finalize do native.delete_global_ref
 end
 
 redef class NativeButton
-       new (context: NativeActivity, sender_object: Object)
-       import Button.click_from_native in "Java" `{
+       private new (context: NativeActivity, sender_object: Button)
+       import Button.on_click in "Java" `{
                final int final_sender_object = sender_object;
 
                return new android.widget.Button(context){
                        @Override
                        public boolean onTouchEvent(android.view.MotionEvent event) {
                                if(event.getAction() == android.view.MotionEvent.ACTION_DOWN) {
-                                       Button_click_from_native(final_sender_object);
+                                       Button_on_click(final_sender_object);
                                        return true;
                                }
                                return false;
diff --git a/lib/app/README.md b/lib/app/README.md
new file mode 100644 (file)
index 0000000..22f4f77
--- /dev/null
@@ -0,0 +1,184 @@
+_app.nit_ is a framework to create cross-platform applications
+
+The framework provides services to manage common needs of modern mobile applications:
+
+* Life-cycle
+* User interface
+* Persistence
+* Package metadata
+* Compilation and packaging
+
+The features offered by _app.nit_ are common to all platforms, but
+may not be available on all devices.
+
+## Application Life-Cycle
+
+The _app.nit_ application life-cycle is compatible with all target platforms.
+It relies on the following sequence of events, represented here by their callback method name:
+
+1. `on_create`: The application is being created.
+   You should build the UI at this time.
+
+2. `on_start`: The app is starting or restarting, background activities may
+
+3. `on_resume`: The app enters the active state, it is in the foreground.
+
+4. `on_pause`: The app leaves the active state and the foreground.
+   It may still be visible in the background.
+   It may then go back to `on_resume` or `on_stop`.
+
+5. `on_stop`: The app is completely hidden.
+   It may then be destroyed (`on_destroy`) or go back to `on_start`.
+
+6. `on_destroy`: The app is being destroyed.
+
+Life-cycle events related to saving and restoring the application state are provided by two special callback methods:
+
+* `on_save_state`: The app may be destroyed soon, save its state for a future `on_restore_state`.
+  More on how it can be done in the `app::data_store` section.
+
+* `on_restore_state`: The app is launching, restore its state from a previous `on_save_state`.
+
+These events are synchronized to the native platforms applications
+The `App` instance is the first to be notified of these events.
+Other UI elements, from the `ui` submodule, are notified of the same events using a simple depth first visit.
+So all UI elements can react separately to live-cycle events.
+
+## User Interface
+
+The `app::ui` module defines an abstract API to build a portable graphical application.
+The API is composed of interactive `Control`s, visible `View`s and an active `Window`.
+
+Here is a subset of the most useful controls and views:
+
+* The classic pushable `Button` with text (usually rectangular).
+
+* `TextInput` is a field for the user to enter text.
+
+* `HorizontalLayout` and `VerticalLayout` organize other controls in order.
+
+Each control is notified of input events by callbacks to `on_event`.
+All controls have observers that are also notified of the events.
+So there is two ways  to customize the behavior on a given event:
+
+* Create a subclass of the wanted `Control`, let's say `Button`, and specialize `on_event`.
+
+* Add an observer to a `Button` instance, and implement `on_event` in the observer.
+
+### Usage Example
+
+The calculator example (at `../../examples/calculator/src/calculator.nit`) is a concrete,
+simple and complete use of the _app.nit_ portable UI.
+
+### Platform-specific UI
+
+You can go beyond the portable UI API of _app.nit_ by using the natives services of a platform.
+
+The suggested approach is to use platform specific modules to customize the application on a precise platform.
+This module redefine `Window::on_start` to call the native language of the platform and setup a native UI.
+
+_TODO complete description and add concrete examples_
+
+## Persistent State with data\_store
+
+_app.nit_ offers the submodule `app::data_store` to easily save the application state and user preferences.
+The service is accessible by the method `App::data_store`. The `DataStore` itself defines 2 methods:
+
+* `DataStore::[]=` saves and associates any serializable instances to a `String` key.
+Pass `null` to clear the value associated to a key.
+
+* `DataStore::[]` returns the object associated to a `String` key.
+It returns `null` if nothing is associated to the key.
+
+### Usage Example
+
+~~~
+import app::data_store
+
+redef class App
+       var user_name: String
+
+       redef fun on_save_state
+       do
+               app.data_store["first run"] = false
+               app.data_store["user name"] = user_name
+
+               super # call `on_save_state` on all attached instances of `AppComponent`
+       end
+
+       redef fun on_restore_state
+       do
+               var first_run = app.data_store["first run"]
+               if first_run != true then
+                       print "It's the first run of this application"
+               end
+
+               var user_name = app.data_store["user name"]
+               if user_name isa String then
+                       self.user_name = user_name
+               else self.user_name = "Undefined"
+
+               super
+       end
+end
+~~~
+
+## Metadata annotations
+
+The _app.nit_ framework defines three annotations to customize the application package.
+
+* `app_name` takes a single argument, the visible name of the application.
+  This name is used for launchers and window title.
+  By default, the name of the target module.
+
+* `app_namespace` specifies the full namespace (or package name) of the application package.
+  This value usually identify the application uniquely on application stores.
+  It should not change once the application has benn published.
+  By default, the namespace is `org.nitlanguage.{module_name}`.
+
+* `app_version` specifies the version of the application package.
+  This annotation expects at least one argument, usually we use three version numbers:
+  the major, minor and revision.
+  The special function `git_revision` will use the prefix of the hash of the latest git commit.
+  By default, the version is 0.1.
+
+### Usage Example
+
+~~~
+module my_module is
+    app_name "My App"
+    app_namespace "org.example.my_app"
+    app_version(1, 0, git_revision)
+end
+~~~
+
+## Compiling and Packaging an Application
+
+The Nit compiler detects the target platform from the importations and generates the appropriate application format and package.
+
+Applications using only the portable services of _app.nit_ require some special care at compilation.
+Such an application, let's say `calculator.nit`, does not depend on a specific platform and use the portable UI.
+The target platform must be specifed to the compiler for it to produce the correct application package.
+There is two main ways to achieve this goal:
+
+* The the mixin option (`-m path`) loads an additionnal module before compiling.
+  It can be used to load platform specific implementations of the _app.nit_ portable UI.
+
+  ~~~
+  # GNU/Linux version, using GTK
+  nitc calculator.nit -m NIT_DIR/lib/linux/ui.nit
+
+  # Android version
+  nitc calculator.nit -m NIT_DIR/lib/android/ui/
+  ~~~
+
+* A common alternative for larger projects is to use platform specific modules.
+  Continuing with the `calculator.nit` example, it can be accompagnied by the module `calculator_linux.nit`.
+  This module imports both `calculator` and `linux::ui`, and can also use other GNU/Linux specific code.
+
+  ~~~
+  module calculator_linux
+
+  import calculator
+  import linux::ui
+  ~~~
diff --git a/lib/app/ui.nit b/lib/app/ui.nit
new file mode 100644 (file)
index 0000000..2e30c76
--- /dev/null
@@ -0,0 +1,189 @@
+# 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.
+
+# Portable UI API for the _app.nit_ framework
+module ui
+
+import app_base
+
+redef class App
+       super AppComponent
+
+       # The current `Window` of this activity
+       #
+       # This attribute must be set by refinements of `App`.
+       var window: Window is writable
+
+       redef fun on_create do window.on_create
+
+       redef fun on_start do window.on_start
+
+       redef fun on_resume do window.on_resume
+
+       redef fun on_pause do window.on_pause
+
+       redef fun on_stop do window.on_stop
+
+       redef fun on_destroy do window.on_destroy
+
+       redef fun on_restore_state do window.on_restore_state
+
+       redef fun on_save_state do window.on_save_state
+end
+
+# An event created by an `AppComponent` and sent to `AppObserver`s
+interface AppEvent
+end
+
+# Observer of `AppEvent`s raised by `AppComponent`s
+interface AppObserver
+       # Notification of `event` raised by `sender`
+       #
+       # To be implemented in subclasses as needed.
+       fun on_event(event: AppEvent) do end
+end
+
+redef class AppComponent
+       super AppObserver
+
+       # All `AppObserver` notified of events raised by `self`
+       #
+       # By default, only `self` is an observer.
+       # Any other `AppObserver` can be added to this collection.
+       var observers = new HashSet[AppObserver].from([self: AppObserver])
+
+       # Propagate `event` to all `observers` by calling `AppObserver::on_event`
+       fun notify_observers(event: AppEvent)
+       do
+               for observer in observers do
+                       observer.on_event(event)
+               end
+       end
+end
+
+# A control implementing the UI
+class Control
+       super AppComponent
+
+       # Direct parent `Control` in the control tree
+       #
+       # If `null` then `self` is at the root of the tree, or not yet attached.
+       var parent: nullable CompositeControl = null is private writable(set_parent)
+
+       # Direct parent `Control` in the control tree
+       #
+       # Setting `parent` calls `remove` on the old parent and `add` on the new one.
+       fun parent=(parent: nullable CompositeControl)
+       is autoinit     do
+               var old_parent = self.parent
+               if old_parent != null and old_parent != parent then
+                       old_parent.remove self
+               end
+
+               if parent != null then parent.add self
+
+               set_parent parent
+       end
+end
+
+# A `Control` grouping other controls
+class CompositeControl
+       super Control
+
+       private var items = new HashSet[Control]
+
+       # Add `item` as a child of `self`
+       protected fun add(item: Control) do items.add item
+
+       # Remove `item` from `self`
+       protected fun remove(item: Control) do if has(item) then items.remove item
+
+       # Is `item` in `self`?
+       protected fun has(item: Control): Bool do return items.has(item)
+
+       redef fun on_create do for i in items do i.on_create
+
+       redef fun on_start do for i in items do i.on_start
+
+       redef fun on_resume do for i in items do i.on_resume
+
+       redef fun on_pause do for i in items do i.on_pause
+
+       redef fun on_stop do for i in items do i.on_stop
+
+       redef fun on_destroy do for i in items do i.on_destroy
+
+       redef fun on_restore_state do for i in items do i.on_restore_state
+
+       redef fun on_save_state do for i in items do i.on_save_state
+end
+
+# A window, root of the `Control` tree
+class Window
+       super CompositeControl
+end
+
+# A viewable `Control`
+abstract class View
+       super Control
+
+       # Is this control enabled so the user can interact with it?
+       #
+       # By default, or if set to `null`, the control is enabled.
+       var enabled: nullable Bool is writable #, abstract FIXME with #1311
+end
+
+# A control with some `text`
+abstract class TextView
+       super View
+
+       # Main `Text` of this control
+       #
+       # By default, or if set to `null`, no text is shown.
+       var text: nullable Text is writable #, abstract FIXME with #1311
+end
+
+# A control for the user to enter custom `text`
+class TextInput
+       super TextView
+end
+
+# A pushable button, raises `ButtonPressEvent`
+class Button
+       super TextView
+end
+
+# A `Button` press event
+class ButtonPressEvent
+       super AppEvent
+
+       # The `Button` that raised this event
+       var sender: Button
+end
+
+# A layout to visually organize `Control`s
+abstract class Layout
+       super View
+       super CompositeControl
+end
+
+# An horizontal linear organization
+class HorizontalLayout
+       super Layout
+end
+
+# A vertical linear organization
+class VerticalLayout
+       super Layout
+end
index 88842a4..88e373a 100644 (file)
@@ -389,11 +389,11 @@ extern class GtkBox `{ GtkBox * `}
        `}
 
        # Give the children of `self` equal space in the box?
-       fun omogeneous: Bool `{ return gtk_box_get_homogeneous(recv); `}
+       fun homogeneous: Bool `{ return gtk_box_get_homogeneous(recv); `}
 
        # Give the children of `self` equal space in the box?
-       fun omogeneous=(omogeneous: Bool) `{
-               gtk_box_set_homogeneous(recv, omogeneous);
+       fun homogeneous=(homogeneous: Bool) `{
+               gtk_box_set_homogeneous(recv, homogeneous);
        `}
 
        # Add `child` and pack it at the start of the box
index 036c2bb..ec2de93 100644 (file)
@@ -29,3 +29,52 @@ extern class GtkSearchEntry `{GtkSearchEntry *`}
                return (GtkSearchEntry *)gtk_search_entry_new();
        `}
 end
+
+redef extern class GtkEntry
+       # Purpose of this text field
+       #
+       # Can be used by on-screen keyboards and other input methods to adjust their behaviour.
+       fun input_purpose: GtkInputPurpose `{
+               return gtk_entry_get_input_purpose(recv);
+       `}
+
+       # Input purpose, tweaks the behavior of this widget
+       #
+       # Can be used by on-screen keyboards and other input methods to adjust their behaviour.
+       fun input_purpose=(purpose: GtkInputPurpose) `{
+               gtk_entry_set_input_purpose(recv, purpose);
+       `}
+end
+
+# Describe the purpose of an input widget
+extern class GtkInputPurpose `{ GtkInputPurpose `}
+       # Allow any character
+       new free_form `{ return GTK_INPUT_PURPOSE_FREE_FORM; `}
+
+       # Allow only alphabetic characters
+       new alpha `{ return GTK_INPUT_PURPOSE_ALPHA; `}
+
+       # Allow only digits
+       new digits `{ return GTK_INPUT_PURPOSE_DIGITS; `}
+
+       # Edited field expects numbers
+       new number `{ return GTK_INPUT_PURPOSE_NUMBER; `}
+
+       # Edited field expects phone number
+       new phone `{ return GTK_INPUT_PURPOSE_PHONE; `}
+
+       # Edited field expects URL
+       new url `{ return GTK_INPUT_PURPOSE_URL; `}
+
+       # Edited field expects email address
+       new email `{ return GTK_INPUT_PURPOSE_EMAIL; `}
+
+       # Edited field expects the name of a person
+       new name `{ return GTK_INPUT_PURPOSE_NAME; `}
+
+       # Like `free_form`, but characters are hidden
+       new password `{ return GTK_INPUT_PURPOSE_PASSWORD; `}
+
+       # Like `digits`, but characters are hidden
+       new pin `{ return GTK_INPUT_PURPOSE_PIN; `}
+end
diff --git a/lib/linux/ui.nit b/lib/linux/ui.nit
new file mode 100644 (file)
index 0000000..4f67eed
--- /dev/null
@@ -0,0 +1,139 @@
+# 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
+
+import data_store
+
+redef class App
+       redef fun setup do init_gtk
+
+       # 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_start
+               app.on_resume
+
+               var window = window
+               window.native.show_all
+               run_gtk
+
+               app.on_pause
+               app.on_stop
+               app.on_save_state
+               app.on_destroy
+       end
+
+       # Spacing between GTK controls, default at 2
+       var control_spacing = 2 is writable
+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
+       redef type NATIVE: GtkContainer
+
+       redef fun add(item)
+       do
+               super
+               native.add item.native
+       end
+end
+
+redef class Window
+       redef type NATIVE: GtkWindow
+       redef var native do
+               var win = new GtkWindow(new GtkWindowType.toplevel)
+               win.connect_destroy_signal_to_quit
+               return win
+       end
+end
+
+redef class View
+       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
+end
+
+redef class HorizontalLayout
+       redef var native = new GtkBox(new GtkOrientation.horizontal, app.control_spacing)
+
+       redef fun add(item)
+       do
+               super
+               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
+
+               # 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 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
+
+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
+end
index d392961..507a0a4 100644 (file)
@@ -16,6 +16,7 @@
 module doc_structure
 
 import doc_concerns
+import modelize
 
 # StructurePhase populates the DocPage content with section and article.
 #
@@ -177,6 +178,12 @@ redef class MClassPage
                mentity.intro_mmodule.mgroup.mproject.booster_rank = 0
                mentity.intro_mmodule.mgroup.booster_rank = 0
                mentity.intro_mmodule.booster_rank = 0
+               var constructors = new ConstructorsSection(mentity)
+               var minit = mentity.root_init
+               if minit != null then
+                       constructors.add_child new DefinitionArticle(minit)
+               end
+               section.add_child constructors
                section.add_child new ConcernsArticle(mentity, concerns)
                for mentity in concerns do
                        var ssection = new ConcernSection(mentity)
@@ -187,7 +194,12 @@ redef class MClassPage
                                        v.name_sorter.sort(group)
                                        for mprop in group do
                                                for mpropdef in mpropdefs_for(mprop, mentity) do
-                                                       ssection.add_child new DefinitionArticle(mpropdef)
+                                                       if mpropdef isa MMethodDef and mpropdef.mproperty.is_init then
+                                                               if mpropdef == minit then continue
+                                                               constructors.add_child new DefinitionArticle(mpropdef)
+                                                       else
+                                                               ssection.add_child new DefinitionArticle(mpropdef)
+                                                       end
                                                end
                                        end
                                end
@@ -289,6 +301,11 @@ class MEntityComposite
        var mentity: MEntity
 end
 
+# A list of constructors.
+class ConstructorsSection
+       super MEntitySection
+end
+
 # A Section about a Concern.
 #
 # Those sections are used to build the page summary.
index c112e25..cd6974c 100644 (file)
@@ -462,22 +462,50 @@ redef class MMethodDef
 
        # FIXME annotation should be handled in their own way
        redef fun html_modifiers do
+               if mproperty.is_init then
+                       var res = new Array[String]
+                       if mproperty.visibility != public_visibility then
+                               res.add mproperty.visibility.to_s
+                       end
+                       return res
+               end
                var res = super
                if is_abstract then
                        res.add "abstract"
                else if is_intern then
                        res.add "intern"
                end
+               res.add "fun"
+               return res
+       end
+
+       redef fun html_declaration do
                if mproperty.is_init then
-                       res.add "init"
-               else
-                       res.add "fun"
+                       var tpl = new Template
+                       tpl.add "<span>"
+                       tpl.add html_modifiers.join(" ")
+                       tpl.add " "
+                       tpl.add html_link
+                       tpl.add html_signature
+                       tpl.add "</span>"
+                       return tpl
                end
-               return res
+               return super
+       end
+
+       redef fun html_short_signature do
+               if mproperty.is_root_init and new_msignature != null then
+                       return new_msignature.html_short_signature
+               end
+               return msignature.html_short_signature
        end
 
-       redef fun html_short_signature do return msignature.html_short_signature
-       redef fun html_signature do return msignature.html_signature
+       redef fun html_signature do
+               if mproperty.is_root_init and new_msignature != null then
+                       return new_msignature.html_signature
+               end
+               return msignature.html_signature
+       end
 end
 
 redef class MVirtualTypeProp
index 4841f02..fb04bfe 100644 (file)
@@ -447,6 +447,13 @@ redef class MEntitySection
        redef var html_subtitle is lazy do return mentity.html_declaration
 end
 
+redef class ConstructorsSection
+       redef var html_id is lazy do return "article:{mentity.nitdoc_id}.constructors"
+       redef var html_title = "Constructors"
+       redef var html_subtitle = null
+       redef fun is_toc_hidden do return is_empty
+end
+
 redef class ConcernSection
        redef var html_id is lazy do return "concern:{mentity.nitdoc_id}"
        redef var html_title is lazy do return "in {mentity.nitdoc_name}"
index b65511d..1a27697 100644 (file)
@@ -74,6 +74,67 @@ class Location
        # End of this location on `line_end`
        var column_end: Int
 
+       # Builds a location instance from its string representation.
+       #
+       # Examples:
+       #
+       # ~~~
+       # var loc = new Location.from_string("location.nit:82,2--105,8")
+       # assert loc.to_s == "location.nit:82,2--105,8"
+       #
+       # loc = new Location.from_string("location.nit")
+       # assert loc.to_s == "location.nit"
+       #
+       # loc = new Location.from_string("location.nit:82,2")
+       # assert loc.to_s == "location.nit:82,2--0,0"
+       #
+       # loc = new Location.from_string("location.nit:82--105")
+       # assert loc.to_s == "location.nit:82,0--105,0"
+       #
+       # loc = new Location.from_string("location.nit:82,2--105")
+       # assert loc.to_s == "location.nit:82,2--105,0"
+       #
+       # loc = new Location.from_string("location.nit:82--105,8")
+       # assert loc.to_s == "location.nit:82,0--105,8"
+       # ~~~
+       init from_string(string: String) do
+               self.line_start = 0
+               self.line_end = 0
+               self.column_start = 0
+               self.column_end = 0
+               # parses the location string and init position vars
+               var parts = string.split_with(":")
+               var filename = parts.shift
+               self.file = new SourceFile(filename, new FileReader.open(filename))
+               # split position
+               if parts.is_empty then return
+               var pos = parts.first.split_with("--")
+               # split start position
+               if pos.first.has(",") then
+                       var pos1 = pos.first.split_with(",")
+                       self.line_start = pos1[0].to_i
+                       if pos1.length > 1 then
+                               self.column_start = pos1[1].to_i
+                       end
+               else
+                       self.line_start = pos.first.to_i
+               end
+               # split end position
+               if pos.length <= 1 then return
+               if pos[1].has(",") then
+                       var pos2 = pos[1].split_with(",")
+                       if pos2.length > 1 then
+                               self.line_end = pos2[0].to_i
+                               self.column_end = pos2[1].to_i
+                       else
+                               self.line_end = self.line_start
+                               self.column_end = pos2[0].to_i
+                       end
+               else
+                       self.line_end = pos[1].to_i
+               end
+       end
+
        # The index in the start character in the source
        fun pstart: Int do return file.line_starts[line_start-1] + column_start-1
 
index f0e2911..5e28695 100644 (file)
@@ -869,21 +869,7 @@ class NeoModel
 
        # Get a `Location` from its string representation.
        private fun to_location(loc: String): Location do
-               #TODO filepath
-               var parts = loc.split_with(":")
-               var file = new SourceFile.from_string(parts[0], "")
-               if parts.length == 1 then
-                       return new Location(file, 0, 0, 0, 0)
-               end
-               var pos = parts[1].split_with("--")
-               var pos1 = pos[0].split_with(",")
-               var pos2 = pos[1].split_with(",")
-               var line_s = pos1[0].to_i
-               var line_e = pos2[0].to_i
-               var column_s = pos1[1].to_i
-               var column_e = 0
-               if pos2.length == 2 then pos2[1].to_i
-               return new Location(file, line_s, line_e, column_s, column_e)
+               return new Location.from_string(loc)
        end
 
        # Get a `MVisibility` from its string representation.