StefanSalewski / gintro

High level GObject-Introspection based GTK3/GTK4 bindings for Nim language
MIT License
293 stars 20 forks source link

Gintro and low level GTK C code like the listview_clocks.c example #141

Open StefanSalewski opened 3 years ago

StefanSalewski commented 3 years ago

Recently we get a question how we can port low level C code examples to gintro, like the listviewclocks.c example. See https://github.com/StefanSalewski/gintro/issues/134#issuecomment-852328853

Unfortunately gintro does not support yet all the low level stuff including the nested macros that are used in GTK to create new widgets or to subclass existing widgets on the gobject level with new properties.

Indeed this is not really needed for most apps. But as currently Nim supports linking C code, as described in

https://nim-lang.org/docs/backends.html#nim-code-calling-the-backend-c-invocation-example

we can just have a separate C file for our Nim app that does all the low level stuff. As an example we have just split the file https://gitlab.gnome.org/GNOME/gtk/-/blob/master/demos/gtk-demo/listview_clocks.c

into a C part and a Nim part:

// lvc.c
/* Lists/Clocks
 * #Keywords: GtkGridView, GtkListItemFactory, GListModel
 *
 * This demo displays the time in different timezones.
 *
 * The goal is to show how to set up expressions that track changes
 * in objects and make them update widgets. For that, we create a
 * clock object that updates its time every second and then use
 * various ways to display that time.
 *
 * Typically, this will be done using GtkBuilder .ui files with the
 * help of the <binding> tag, but this demo shows the code that runs
 * behind that.
 */

#include <gtk/gtk.h>
#include <stdio.h>

#define GTK_TYPE_CLOCK (gtk_clock_get_type ())
G_DECLARE_FINAL_TYPE (GtkClock, gtk_clock, GTK, CLOCK, GObject)

/* This is our object. It's just a timezone */
typedef struct _GtkClock GtkClock;
struct _GtkClock
{
  GObject parent_instance;

  /* We allow this to be NULL for the local timezone */
  GTimeZone *timezone;
  /* Name of the location we're displaying time for */
  char *location;
};

enum {
  PROP_0,
  PROP_LOCATION,
  PROP_TIME,
  PROP_TIMEZONE,

  N_PROPS
};

/* This function returns the current time in the clock's timezone.
 * Note that this returns a new object every time, so we need to
 * remember to unref it after use.
 */
static GDateTime *
gtk_clock_get_time (GtkClock *clock)
{
  if (clock->timezone)
    return g_date_time_new_now (clock->timezone);
  else
    return g_date_time_new_now_local ();
}

/* Here, we implement the functionality required by the GdkPaintable
 * interface. This way we have a trivial way to display an analog clock.
 * It also allows demonstrating how to directly use objects in the
 * listview later by making this object do something interesting.
 */
static void
gtk_clock_snapshot (GdkPaintable *paintable,
                    GdkSnapshot  *snapshot,
                    double        width,
                    double        height)
{
  GtkClock *self = GTK_CLOCK (paintable);
  GDateTime *time;
  GskRoundedRect outline;

#define BLACK ((GdkRGBA) { 0, 0, 0, 1 })

  /* save/restore() is necessary so we can undo the transforms we start
   * out with.
   */
  gtk_snapshot_save (snapshot);

  /* First, we move the (0, 0) point to the center of the area so
   * we can draw everything relative to it.
   */
  gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (width / 2, height / 2));

  /* Next we scale it, so that we can pretend that the clock is
   * 100px in size. That way, we don't need to do any complicated
   * math later. We use MIN() here so that we use the smaller
   * dimension for sizing. That way we don't overdraw but keep
   * the aspect ratio.
   */
  gtk_snapshot_scale (snapshot, MIN (width, height) / 100.0, MIN (width, height) / 100.0);

  /* Now we have a circle with diameter 100px (and radius 50px) that
   * has its (0, 0) point at the center. Let's draw a simple clock into it.
   */
  time = gtk_clock_get_time (self);

  /* First, draw a circle. This is a neat little trick to draw a circle
   * without requiring Cairo.
   */
  gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-50, -50, 100, 100), 50);
  gtk_snapshot_append_border (snapshot,
                              &outline,
                              (float[4]) { 4, 4, 4, 4 },
                              (GdkRGBA [4]) { BLACK, BLACK, BLACK, BLACK });

  /* Next, draw the hour hand.
   * We do this using transforms again: Instead of computing where the angle
   * points to, we just rotate everything and then draw the hand as if it
   * was :00. We don't even need to care about am/pm here because rotations
   * just work.
   */
  gtk_snapshot_save (snapshot);
  gtk_snapshot_rotate (snapshot, 30 * g_date_time_get_hour (time) + 0.5 * g_date_time_get_minute (time));
  gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -23, 4, 25), 2);
  gtk_snapshot_push_rounded_clip (snapshot, &outline);
  gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds);
  gtk_snapshot_pop (snapshot);
  gtk_snapshot_restore (snapshot);

  /* And the same as above for the minute hand. Just make this one longer
   * so people can tell the hands apart.
   */
  gtk_snapshot_save (snapshot);
  gtk_snapshot_rotate (snapshot, 6 * g_date_time_get_minute (time));
  gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -43, 4, 45), 2);
  gtk_snapshot_push_rounded_clip (snapshot, &outline);
  gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds);
  gtk_snapshot_pop (snapshot);
  gtk_snapshot_restore (snapshot);

  /* and finally, the second indicator. */
  gtk_snapshot_save (snapshot);
  gtk_snapshot_rotate (snapshot, 6 * g_date_time_get_second (time));
  gsk_rounded_rect_init_from_rect (&outline, &GRAPHENE_RECT_INIT(-2, -43, 4, 10), 2);
  gtk_snapshot_push_rounded_clip (snapshot, &outline);
  gtk_snapshot_append_color (snapshot, &BLACK, &outline.bounds);
  gtk_snapshot_pop (snapshot);
  gtk_snapshot_restore (snapshot);

  /* And finally, don't forget to restore the initial save() that
   * we did for the initial transformations.
   */
  gtk_snapshot_restore (snapshot);

  g_date_time_unref (time);
}

/* Our desired size is 100px. That sounds okay for an analog clock */
static int
gtk_clock_get_intrinsic_width (GdkPaintable *paintable)
{
  return 100;
}

static int
gtk_clock_get_intrinsic_height (GdkPaintable *paintable)
{
  return 100;
}

/* Initialize the paintable interface. This way we turn our clocks
 * into objects that can be drawn. There are more functions to this
 * interface to define desired size, but this is enough.
 */
static void
gtk_clock_paintable_init (GdkPaintableInterface *iface)
{
  iface->snapshot = gtk_clock_snapshot;
  iface->get_intrinsic_width = gtk_clock_get_intrinsic_width;
  iface->get_intrinsic_height = gtk_clock_get_intrinsic_height;
}

/* Finally, we define the type. The important part is adding the
 * paintable interface, so GTK knows that this object can indeed
 * be drawn.
 */
G_DEFINE_TYPE_WITH_CODE (GtkClock, gtk_clock, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
                                                gtk_clock_paintable_init))

static GParamSpec *properties[N_PROPS] = { NULL, };

static void
gtk_clock_get_property (GObject    *object,
                        guint       property_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
  GtkClock *self = GTK_CLOCK (object);

  switch (property_id)
    {
    case PROP_LOCATION:
      g_value_set_string (value, self->location);
      break;

    case PROP_TIME:
      g_value_take_boxed (value, gtk_clock_get_time (self));
      break;

    case PROP_TIMEZONE:
      g_value_set_boxed (value, self->timezone);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_clock_set_property (GObject      *object,
                        guint         property_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
  GtkClock *self = GTK_CLOCK (object);

  switch (property_id)
    {
    case PROP_LOCATION:
      self->location = g_value_dup_string (value);
      break;

    case PROP_TIMEZONE:
      self->timezone = g_value_dup_boxed (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

/* This is the list of all the ticking clocks */
static GSList *ticking_clocks = NULL;

/* This is the ID of the timeout source that is updating all
 * ticking clocks.
 */
static guint ticking_clock_id = 0;

/* Every second, this function is called to tell everybody that
 * the clocks are ticking.
 */
static gboolean
gtk_clock_tick (gpointer unused)
{
  // printf("gtk_clock_tick\n");
  GSList *l;

  for (l = ticking_clocks; l; l = l->next)
    {
      GtkClock *clock = l->data;

      /* We will now return a different value for the time property,
       * so notify about that.
       */
      g_object_notify_by_pspec (G_OBJECT (clock), properties[PROP_TIME]);

      /* We will also draw the hands of the clock differently.
       * So notify about that, too.
       */
      gdk_paintable_invalidate_contents (GDK_PAINTABLE (clock));
    }

  return G_SOURCE_CONTINUE;
}

static void
gtk_clock_stop_ticking (GtkClock *self)
{
  ticking_clocks = g_slist_remove (ticking_clocks, self);

  /* If no clock is remaining, stop running the tick updates */
  if (ticking_clocks == NULL && ticking_clock_id != 0)
    g_clear_handle_id (&ticking_clock_id, g_source_remove);
}

static void
gtk_clock_start_ticking (GtkClock *self)
{
  /* if no clock is ticking yet, start */
  if (ticking_clock_id == 0)
    ticking_clock_id = g_timeout_add_seconds (1, gtk_clock_tick, NULL);

  ticking_clocks = g_slist_prepend (ticking_clocks, self);
}

static void
gtk_clock_finalize (GObject *object)
{
  GtkClock *self = GTK_CLOCK (object);

  gtk_clock_stop_ticking (self);

  g_free (self->location);
  g_clear_pointer (&self->timezone, g_time_zone_unref);

  G_OBJECT_CLASS (gtk_clock_parent_class)->finalize (object);
}

static void
gtk_clock_class_init (GtkClockClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gtk_clock_get_property;
  gobject_class->set_property = gtk_clock_set_property;
  gobject_class->finalize = gtk_clock_finalize;

  properties[PROP_LOCATION] =
    g_param_spec_string ("location", NULL, NULL, NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  properties[PROP_TIME] =
    g_param_spec_boxed ("time", NULL, NULL, G_TYPE_DATE_TIME, G_PARAM_READABLE);
  properties[PROP_TIMEZONE] =
    g_param_spec_boxed ("timezone", NULL, NULL, G_TYPE_TIME_ZONE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);

  g_object_class_install_properties (gobject_class, N_PROPS, properties);
}

static void
gtk_clock_init (GtkClock *self)
{
  gtk_clock_start_ticking (self);
}

//static GtkClock *
GtkClock *
gtk_clock_new (const char *location,
               GTimeZone  *_tz)
{
  GtkClock *result;

  result = g_object_new (GTK_TYPE_CLOCK,
                         "location", location,
                         "timezone", _tz,
                         NULL);

  g_clear_pointer (&_tz, g_time_zone_unref);

  return result;
}

static char *
convert_time_to_string (GObject   *image,
                        GDateTime *time,
                        gpointer   unused)
{
  return g_date_time_format (time, "%x\n%X");
}

/* And this function is the crux for this whole demo.
 * It shows how to use expressions to set up bindings.
 */
void
setup_listitem_cb (GtkListItemFactory *factory,
                   GtkListItem        *list_item)
{
  GtkWidget *box, *picture, *location_label, *time_label;
  GtkExpression *clock_expression, *expression;

  box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_list_item_set_child (list_item, box);

  /* First, we create an expression that gets us the clock from the listitem:
   * 1. Create an expression that gets the list item.
   * 2. Use that expression's "item" property to get the clock
   */
  expression = gtk_constant_expression_new (GTK_TYPE_LIST_ITEM, list_item);
  clock_expression = gtk_property_expression_new (GTK_TYPE_LIST_ITEM, expression, "item");

  /* Bind the clock's location to a label.
   * This is easy: We just get the "location" property of the clock.
   */
  expression = gtk_property_expression_new (GTK_TYPE_CLOCK,
                                            gtk_expression_ref (clock_expression),
                                            "location");
  /* Now create the label and bind the expression to it. */
  location_label = gtk_label_new (NULL);
  gtk_expression_bind (expression, location_label, "label", location_label);
  gtk_box_append (GTK_BOX (box), location_label);

  /* Here we bind the item itself to a GdkPicture.
   * This is simply done by using the clock expression itself.
   */
  expression = gtk_expression_ref (clock_expression);
  /* Now create the widget and bind the expression to it. */
  picture = gtk_picture_new ();
  gtk_expression_bind (expression, picture, "paintable", picture);
  gtk_box_append (GTK_BOX (box), picture);

  /* And finally, everything comes together.
   * We create a label for displaying the time as text.
   * For that, we need to transform the "GDateTime" of the
   * time property into a string so that the label can display it.
   */
  expression = gtk_property_expression_new (GTK_TYPE_CLOCK,
                                            gtk_expression_ref (clock_expression),
                                            "time");
  expression = gtk_cclosure_expression_new (G_TYPE_STRING,
                                            NULL,
                                            1, (GtkExpression *[1]) { expression },
                                            G_CALLBACK (convert_time_to_string),
                                            NULL, NULL);
  /* Now create the label and bind the expression to it. */
  time_label = gtk_label_new (NULL);
  gtk_expression_bind (expression, time_label, "label", time_label);
  gtk_box_append (GTK_BOX (box), time_label);

  gtk_expression_unref (clock_expression);
}
# ~/gtk/demos/gtk-demo/listview_clocks.c
#
# Lists/Clocks
# Keywords: GtkGridView, GtkListItemFactory, GListModel
#
# This demo displays the time in different timezones.
#
# The goal is to show how to set up expressions that track changes
# in objects and make them update widgets. For that, we create a
# clock object that updates its time every second and then use
# various ways to display that time.
#
# Typically, this will be done using GtkBuilder .ui files with the
# help of the <binding> tag, but this demo shows the code that runs
# behind that.
#
import gintro/[gtk4, glib, gio, gobject, gdk4, gsk, graphene]

type
  Clock = ref object of gobject.Object # the Nim proxy object
  Clock00 = object of gobject.Object00 # the GTK object

# we import the low level functions from C
proc gtk_clock_get_type(): GType  {.importc.}
proc gtk_clock_new(location: cstring; tz: ptr TimeZone00): ptr Clock00  {.importc.}
proc setup_listitem_cb(factory: ptr ListItemFactory00; list_item: ptr ListItem00)   {.importc.}

# a constructor proc similar to the ones in gtk4.nim, i.e. newButton() 
proc newClock(location: string;  tz: glib.TimeZone): Clock =
  var tz0: ptr glib.TimeZone00
  if tz == nil:
    tz0 = nil
  else:
    tz0 = cast[ptr glib.TimeZone00](g_boxed_copy(g_time_zone_get_type(), tz.impl)) # needed as gtk_clock_new() does a transfer_full for tz0
  let gobj = gtk_clock_new(location, tz0)
  let qdata = g_object_get_qdata(gobj, Quark)
  if qdata != nil:
    result = cast[type(result)](qdata)
    assert(result.impl == gobj)
  else:
    fnew(result, gtk4.finalizeGObject)
    result.impl = gobj
    GC_ref(result)
    discard g_object_ref_sink(result.impl)
    g_object_add_toggle_ref(result.impl, toggleNotify, addr(result[]))
    g_object_unref(result.impl)
    assert(g_object_get_qdata(result.impl, Quark) == nil)
    g_object_set_qdata(result.impl, Quark, addr(result[]))

proc createClocksModel: gio.ListModel =
  var result = newListStore(gtk_clock_get_type())
  # local time
  result.append(newClock("local", nil))
  # UTC time
  result.append(newClock("UTC", newTimeZoneUtc()))
  # A bunch of timezones with GTK hackers
  result.append(newClock("San Francisco", newTimeZone("America/Los_Angeles")))
  result.append(newClock("Xalapa",  newTimeZone("America/Mexico_City")))
  result.append(newClock("Boston",  newTimeZone("America/New_York")))
  result.append(newClock("London",  newTimeZone("Europe/London")))
  result.append(newClock("Berlin",  newTimeZone("Europe/Berlin")))
  result.append(newClock("Moscow",  newTimeZone("Europe/Moscow")))
  result.append(newClock("New Delhi",  newTimeZone("Asia/Kolkata")))
  result.append(newClock("Shanghai",  newTimeZone("Asia/Shanghai")))
  return cast[ListModel](result)

# this has currently to be a low level function
proc convert_time_to_string(image: ptr gobject.Object00; time: ptr glib.DateTime00; unused: pointer): pointer  {.cdecl.} =
  return glib.g_date_time_format(time, "%x\n%X")

# And this function is the crux for this whole demo.
# It shows how to use expressions to set up bindings.
#
proc setupListitemCb(factory: SignalListItemFactory; listItem: ListItem) =
  var picture, location_label, time_label: Widget
  var clock_expression, expression: Expression
  let box: gtk4.Box = newBox(Orientation.vertical)
  listItem.setChild(box)

  # First, we create an expression that gets us the clock from the listitem:
  # 1. Create an expression that gets the list item.
  # 2. Use that expression's "item" property to get the clock
  expression = newConstantExpression(gtk_list_item_get_type(), listItem)
  clock_expression = newPropertyExpression(gtkListItemGetType(), expression, "item")

  # Bind the clock's location to a label.
  #This is easy: We just get the "location" property of the clock.
  expression = newPropertyExpression(gtk_clock_get_type(), clock_expression, "location")
  # Now create the label and bind the expression to it.
  location_label = newLabel()
  discard gtk4.bind(expression, location_label, "label", location_label)
  box.append(location_label)
  # Here we bind the item itself to a GdkPicture.
  # This is simply done by using the clock expression itself.

  # Now create the widget and bind the expression to it.
  picture = newPicture()
  discard gtk4.bind(clock_expression, picture, "paintable", picture)
  box.append(picture)

  #And finally, everything comes together.
  # We create a label for displaying the time as text.
  # For that, we need to transform the "GDateTime" of the
  # time property into a string so that the label can display it.
  expression = newPropertyExpression(gtk_clock_get_type(), clock_expression, "time")

  # this is still ugly, newCclosureExpression() is low level, it takes ownership of expression, so we have to call GC_ref(). 
  GC_ref(expression)
  expression = newCclosureExpression(gStringGetType(), nil, 1, addr expression.impl, cast[Callback](convert_time_to_string), nil, nil)

  # Now create the label and bind the expression to it.
  time_label = newLabel()
  discard gtk4.bind(expression, time_label, "label", time_label)
  box.append(timeLabel)

var window: Window = nil

proc doListviewClocks(doWidget: Widget): Window =
  if window == nil:
    # This is the normal window setup code every demo does 
    window = newWindow()
    window.setTitle("Clocks")
    window.setDefaultSize(600, 400)
    window.setDisplay(doWidget.getDisplay)
    #g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window); # what is this ?
    # List widgets go into a scrolled window. Always.
    let sw: ScrolledWindow = newScrolledWindow()
    window.setChild(sw)
    # Create the factory that creates the listitems. Because we
    # used bindings above during setup, we only need to connect
    # to the setup signal.
    # The bindings take care of the bind step.
    let factory: SignalListItemFactory = newSignalListItemFactory()
    factory.connect("setup", setupListitemCb)
    let model: SelectionModel = cast[SelectionModel](newNoSelection(createClocksModel()))
    let gridview: GridView = newGridView(model, factory)
    gridView.setHscrollPolicy(ScrollablePolicy.natural)
    gridView.setVscrollPolicy(ScrollablePolicy.natural)
    sw.setChild(gridview)
  if not window.getVisible:
    window.show
  else:
    window.destroy
  GC_fullcollect()
  return window

proc activate(app: gtk4.Application) =
  let window = newApplicationWindow(app)
  let box = doListviewClocks(window)
  box.setApplication(app)
  box.present
  window.destroy

proc main =
  let app = newApplication("org.gtk.example", {})
  app.connect("activate", activate)
  let status = app.run
  quit(status)

main()

For testing this we need the most recent gintro version which we can install with

nimble install gintro@#head

We build the app with these three commands:

gcc -c lvc.c  `pkg-config --cflags --libs gtk4`
ar rvs lvc.a lvc.o
nim c --gc:arc --passL:lvc.a --passL:"/opt/gtk/lib64/libgtk-4.so" --passL:"/usr/lib64/libglib-2.0.so" --passL:"/usr/lib64/libgobject-2.0.so"  listview_clocks.nim

Well that is not really nice of course. One ugly point is that we have to pass the full path to the dynamic link libraries when building. And the Nim code contains some ugly casts and some more ugly stuff. But when you have some Nim and GTK knowledge then you should be able to understand the code and maybe to improve it.

Of course we could try to convert the C part to Nim also. First step would be to expand the C macros like G_DECLARE_FINAL_TYPE() and recreate them in Nim. But finally the code would look not really cleaner. It is just the way how GTK creates widgets, and when we really have to create new widgets with new properties then we will have to write such low level code. I can not imagine a way to really abstract all this. Generally this is not necessary at all, and when you write a Nim GUI app with a few thousand lines of code, then it should be not a real problem when a few hundred lines are low level C code. The advantage of the C code is, that you can follow the C GTK examples straight.

FInally I have to admit that I do not understand the listview_clocks.c example yet, and I even have no idea for what it may be good. This issue is only intended to show how one can do it in principle.

gavr123456789 commented 3 years ago

We need to experiment with creating as small or same example using Vala + Nim, and building on Meson.(Update, no we dont) Meson unfortunately does not support nim https://forum.nim-lang.org/t/7540, which in the future will complicate the creation of full-fledged applications(Which need to place their own shortcut and GSettings), but for now we can configure the nim build via custom target as it was done here
P.S. also Flatpak

aeldemery commented 3 years ago

I gave this issue a little bit of thoughts over the last couple of days. I have explored how the python and javascript solve the issue, only to find that the inner workings of the bindings on those two languages are implemented in C and C++ respectively. So they have naturaly access to the low level stuff inside the toolkit.

Then I have read how the GObject system works.

The macros used to generate and register new Objects can be written in code like in the previous link. You have to define two structures and provide members and function pointers in the structures. This represent the TypeInfo struct in your bindings.

type
  TypeInfo* {.pure, byRef.} = object
    classSize*: uint16
    baseInit*: BaseInitFunc
    baseFinalize*: BaseFinalizeFunc
    classInit*: ClassInitFunc
    classFinalize*: ClassFinalizeFunc
    classData*: pointer
    instanceSize*: uint16
    nPreallocs*: uint16
    instanceInit*: InstanceInitFunc
    valueTable*: ptr TypeValueTable00

and using the corresponding API to register the type

GType g_type_register_static (GType             parent_type,
                              const gchar      *type_name,
                              const GTypeInfo  *info,
                              GTypeFlags        flags);
GType g_type_register_fundamental (GType                       type_id,
                                   const gchar                *type_name,
                                   const GTypeInfo            *info,
                                   const GTypeFundamentalInfo *finfo,
                                   GTypeFlags                  flags);

But does this really worth it? I don't know. Theoretically its doable using nim macros.

Nim or any other languages provide its own type system and object hierarchies/inheritance. This is besides the two memory management systems inside both worlds, which complicate things.

At the end I think you can use the bindings only to use the provided widgets/objects by the library, but creating another objects inside nim and hooking those inside the type system of GObject to be able to use it inside c would be a daunting task. I think you are right. If the needs come to build custom objects, one might find it easier to create those in c or better vala and link it with the code in nim.

I hear some tone of disrepair in your talk, that you probably want to abandon gintro, I think it would be a great loss.

Thank you for your efforts.