From 5517ed7296dad8ab177f7e6569ad77ebe609e20d Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Thu, 20 Apr 2017 23:09:36 +0300 Subject: [PATCH] GTK sample (#515) * GTK sample * Add full UTF8 recoding ability to interop --- .../kotlin/kotlinx/cinterop/NativeUtils.kt | 13 +--- runtime/src/main/cpp/KString.cpp | 16 ++++- .../main/kotlin/kotlin/text/StringBuilder.kt | 5 +- samples/gtk/README.md | 12 ++++ samples/gtk/build.sh | 34 +++++++++ samples/gtk/gtk3.def | 1 + samples/gtk/src/Main.kt | 69 +++++++++++++++++++ 7 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 samples/gtk/README.md create mode 100755 samples/gtk/build.sh create mode 100644 samples/gtk/gtk3.def create mode 100644 samples/gtk/src/Main.kt diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt index 570f95b9b13..5ae4904cae7 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt @@ -4,18 +4,7 @@ import konan.internal.Intrinsic internal fun decodeFromUtf8(bytes: ByteArray): String = kotlin.text.fromUtf8Array(bytes, 0, bytes.size) -fun encodeToUtf8(str: String): ByteArray { - val result = ByteArray(str.length) - - for (index in 0 .. str.length - 1) { - val char = str[index] - if (char.toInt() >= 128) { - TODO("non-ASCII char") - } - result[index] = char.toByte() - } - return result -} +fun encodeToUtf8(str: String): ByteArray = kotlin.text.toUtf8Array(str, 0, str.length) @Intrinsic external fun bitsToFloat(bits: Int): Float diff --git a/runtime/src/main/cpp/KString.cpp b/runtime/src/main/cpp/KString.cpp index c1411e1b93e..e117938a8ce 100644 --- a/runtime/src/main/cpp/KString.cpp +++ b/runtime/src/main/cpp/KString.cpp @@ -784,6 +784,20 @@ OBJ_GETTER(Kotlin_String_fromUtf8Array, KConstRef thiz, KInt start, KInt size) { RETURN_RESULT_OF(utf8ToUtf16, rawString, array->count_); } +OBJ_GETTER(Kotlin_String_toUtf8Array, KString thiz, KInt start, KInt size) { + RuntimeAssert(thiz->type_info() == theStringTypeInfo, "Must use String"); + if (start < 0 || size < 0 || size > thiz->count_ - start) { + ThrowArrayIndexOutOfBoundsException(); + } + const KChar* utf16 = CharArrayAddressOfElementAt(thiz, start); + std::string utf8; + utf8::utf16to8(utf16, utf16 + size, back_inserter(utf8)); + ArrayHeader* result = AllocArrayInstance( + theByteArrayTypeInfo, utf8.size() + 1, OBJ_RESULT)->array(); + ::memcpy(ByteArrayAddressOfElementAt(result, 0), utf8.c_str(), utf8.size()); + RETURN_OBJ(result->obj()); +} + OBJ_GETTER(Kotlin_String_fromCharArray, KConstRef thiz, KInt start, KInt size) { const ArrayHeader* array = thiz->array(); RuntimeAssert(array->type_info() == theCharArrayTypeInfo, "Must use a char array"); @@ -796,7 +810,7 @@ OBJ_GETTER(Kotlin_String_fromCharArray, KConstRef thiz, KInt start, KInt size) { } ArrayHeader* result = AllocArrayInstance( - theStringTypeInfo, size, OBJ_RESULT)->array(); + theStringTypeInfo, size, OBJ_RESULT)->array(); memcpy(CharArrayAddressOfElementAt(result, 0), CharArrayAddressOfElementAt(array, start), size * sizeof(KChar)); diff --git a/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt index f4d70993cac..820982bf8f5 100644 --- a/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt +++ b/runtime/src/main/kotlin/kotlin/text/StringBuilder.kt @@ -15,11 +15,13 @@ */ package kotlin.text -import kotlin.collections.* @SymbolName("Kotlin_String_fromUtf8Array") external fun fromUtf8Array(array: ByteArray, start: Int, size: Int) : String +@SymbolName("Kotlin_String_toUtf8Array") +external fun toUtf8Array(string: String, start: Int, size: Int) : ByteArray + // TODO: make it somewhat private? @SymbolName("Kotlin_String_fromCharArray") external fun fromCharArray(array: CharArray, start: Int, size: Int) : String @@ -27,7 +29,6 @@ external fun fromCharArray(array: CharArray, start: Int, size: Int) : String @SymbolName("Kotlin_String_toCharArray") external fun toCharArray(string: String) : CharArray - class StringBuilder private constructor ( private var array: CharArray ) : CharSequence, Appendable { diff --git a/samples/gtk/README.md b/samples/gtk/README.md new file mode 100644 index 00000000000..0a52429fe27 --- /dev/null +++ b/samples/gtk/README.md @@ -0,0 +1,12 @@ +# GTK application + + This example shows how one may use _Kotlin/Native_ to build GUI + applications with the GTK toolkit. + +To build use `./build.sh` script without arguments (or specify `TARGET` variable if cross-compiling). +To run on Mac install XQuartz X server (https://www.xquartz.org/), and then + + ./Gtk3Demo + +Dialog box with the button will be shown, and application will print message +and terminate on button click. diff --git a/samples/gtk/build.sh b/samples/gtk/build.sh new file mode 100755 index 00000000000..99f155dd346 --- /dev/null +++ b/samples/gtk/build.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +PATH=../../dist/bin:../../bin:$PATH +DIR=. + +IPREFIX_macbook=-I/opt/local/include +IPREFIX_linux=-I/usr/include +LINKER_ARGS_macbook="-L/opt/local/lib -lglib-2.0 -lgdk-3.0 -lgtk-3 -lgio-2.0 -lgobject-2.0" +LINKER_ARGS_linux="-L/usr/lib/x86_64-linux-gnu -lglib-2.0 -lgdk-3.0 -lgtk-3 -lgio-2.0 -lgobject-2.0" + +if [ x$TARGET == x ]; then +case "$OSTYPE" in + darwin*) TARGET=macbook ;; + linux*) TARGET=linux ;; + *) echo "unknown: $OSTYPE" && exit 1;; +esac +fi + +var=IPREFIX_${TARGET} +IPREFIX="${!var}" +var=LINKER_ARGS_${TARGET} +LINKER_ARGS=${!var} +var=COMPILER_ARGS_${TARGET} +COMPILER_ARGS=${!var} # add -opt for an optimized build. + +if [ ! -f $DIR/gtk3.bc ]; then + echo "Generating GTK stubs (once), may take few mins depending on the hardware..." + cinterop -J-Xmx8g -copt $IPREFIX/atk-1.0 -copt $IPREFIX/gdk-pixbuf-2.0 -copt $IPREFIX/cairo -copt $IPREFIX/pango-1.0 \ + -copt -I/opt/local/lib/glib-2.0/include -copt $IPREFIX/gtk-3.0 -copt $IPREFIX/glib-2.0 -def $DIR/gtk3.def \ + -target $TARGET -o $DIR/gtk3.bc || exit 1 +fi +konanc -target $TARGET $DIR/src -library $DIR/gtk3.bc -linkerArgs "$LINKER_ARGS" -o $DIR/Gtk3Demo.kexe || exit 1 + + diff --git a/samples/gtk/gtk3.def b/samples/gtk/gtk3.def new file mode 100644 index 00000000000..8bacdcabddd --- /dev/null +++ b/samples/gtk/gtk3.def @@ -0,0 +1 @@ +headers = gtk/gtk.h diff --git a/samples/gtk/src/Main.kt b/samples/gtk/src/Main.kt new file mode 100644 index 00000000000..273c9ab5a69 --- /dev/null +++ b/samples/gtk/src/Main.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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. + */ + +import kotlinx.cinterop.* +import gtk3.* + +internal fun print_hello(widget: CPointer?, data: gpointer?) { + println("Hi Kotlin") +} + +internal fun finish(widget: CPointer?) { + gtk_widget_destroy(widget) +} + +// Note that all callback parameters must be primitive types or nullable C pointers. +fun g_signal_connect(obj: CPointer<*>, actionName: String, + action: GCallback, data: gpointer? = null, connect_flags: Int = 0) { + g_signal_connect_data(obj.reinterpret(), actionName, action, + data = data, destroy_data = null, connect_flags = connect_flags) + +} + +fun activate(app: CPointer?, user_data: gpointer?) { + val windowWidget = gtk_application_window_new(app)!! + val window = windowWidget.reinterpret() + gtk_window_set_title(window, "Window"); + gtk_window_set_default_size(window, 200, 200) + + val button_box = gtk_button_box_new( + GtkOrientation.GTK_ORIENTATION_HORIZONTAL)!! + gtk_container_add(window.reinterpret(), button_box); + + val button = gtk_button_new_with_label("Konan говорит: click me!")!! + g_signal_connect(button, "clicked", + staticCFunction(::print_hello).reinterpret()); + g_signal_connect(button, "clicked", + staticCFunction(::finish).reinterpret(), window, G_CONNECT_SWAPPED) + gtk_container_add (button_box.reinterpret(), button); + + gtk_widget_show_all(windowWidget) +} + +fun gtkMain(args: Array): Int { + val app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE)!! + g_signal_connect(app, "activate", staticCFunction(::activate).reinterpret()) + val status = memScoped { + g_application_run(app.reinterpret(), + args.size, args.map { it.cstr.getPointer(memScope) }.toCValues()) + } + g_object_unref(app) + return status +} + +fun main(args: Array) { + gtkMain(args) +}