[gnome-builder/wip/tingping/meson-templates] meson-templates: Various template improvements



commit 4946365ec07f5c9f1eeebd7cdfdd898ee6654e33
Author: Patrick Griffis <tingping tingping se>
Date:   Sat Aug 12 20:43:05 2017 -0400

    meson-templates: Various template improvements

 .../meson-templates/meson_templates/__init__.py    |  123 +++++++--
 .../resources/data/hello.appdata.xml.in            |    7 +
 .../resources/data/hello.desktop.in                |    7 +
 .../resources/data/hello.gschema.xml               |    5 +
 .../meson_templates/resources/data/meson.build     |   41 +++
 .../meson_templates/resources/flatpak.json         |   52 ++++
 .../meson_templates/resources/meson.build          |   19 +-
 .../resources/meson_post_install.py                |   21 ++
 .../meson_templates/resources/po/POTFILES          |   12 +-
 .../meson_templates/resources/po/meson.build       |   17 +--
 .../meson_templates/resources/src/gi_composites.py |  273 ++++++++++++++++++++
 .../meson_templates/resources/src/hello-window.c   |   29 ++
 .../meson_templates/resources/src/hello-window.h   |   12 +
 .../meson_templates/resources/src/hello-window.ui  |   22 ++
 .../resources/src/hello.gresource.xml              |    6 +
 .../meson_templates/resources/src/hello.js.in      |    9 +
 .../meson_templates/resources/src/hello.py.in      |   26 ++
 .../resources/src/hello.src.gresource.xml          |    7 +
 .../meson_templates/resources/src/helloWindow.js   |   20 ++
 .../meson_templates/resources/src/hello_window.py  |   13 +
 .../resources/src/javascript-meson.build           |   32 +++
 .../meson_templates/resources/src/main.c           |   77 +++++-
 .../meson_templates/resources/src/main.js          |   30 +++
 .../meson_templates/resources/src/main.py          |   25 ++
 .../meson_templates/resources/src/main.vala        |   29 ++
 .../meson_templates/resources/src/meson.build      |   14 +-
 .../resources/src/python-meson.build               |   35 +++
 27 files changed, 901 insertions(+), 62 deletions(-)
---
diff --git a/plugins/meson-templates/meson_templates/__init__.py 
b/plugins/meson-templates/meson_templates/__init__.py
index a8f8969..fd407e1 100644
--- a/plugins/meson-templates/meson_templates/__init__.py
+++ b/plugins/meson-templates/meson_templates/__init__.py
@@ -35,15 +35,18 @@ from gi.repository import (
 
 _ = Ide.gettext
 
+
 def get_module_data_path(name):
     engine = Peas.Engine.get_default()
     plugin = engine.get_plugin_info('meson_templates')
     data_dir = plugin.get_data_dir()
     return path.join(data_dir, name)
 
+
 class LibraryTemplateProvider(GObject.Object, Ide.TemplateProvider):
     def do_get_project_templates(self):
-        return [EmptyProjectTemplate()]
+        return [GnomeProjectTemplate()]
+
 
 class MesonTemplateLocator(Template.TemplateLocator):
     license = None
@@ -57,7 +60,7 @@ class MesonTemplateLocator(Template.TemplateLocator):
             manager = GtkSource.LanguageManager.get_default()
             language = manager.guess_language(filename, None)
 
-            if self.license == None or language == None:
+            if self.license is None or language is None:
                 return self.empty()
 
             header = Ide.language_format_header(language, self.license)
@@ -67,11 +70,6 @@ class MesonTemplateLocator(Template.TemplateLocator):
 
         return super().do_locate(self, path)
 
-# Map builder langs to meson ones
-LANGUAGE_MAP = {
-    'c': 'c',
-    'c++': 'cpp',
-}
 
 class MesonTemplate(Ide.TemplateBase, Ide.ProjectTemplate):
     def __init__(self, id, name, icon_name, description, languages):
@@ -110,7 +108,7 @@ class MesonTemplate(Ide.TemplateBase, Ide.ProjectTemplate):
         else:
             self.language = 'c'
 
-        if self.language not in ('c', 'c++'):
+        if self.language not in ('c', 'javascript', 'python', 'vala'):
             task.return_error(GLib.Error('Language %s not supported' %
                                          self.language))
             return
@@ -129,35 +127,68 @@ class MesonTemplate(Ide.TemplateBase, Ide.ProjectTemplate):
         scope.get('template').assign_string(self.id)
 
         name = params['name'].get_string().lower()
-        name_ = name.lower().replace('-','_')
+        name_ = name.lower().replace('-', '_')
         scope.get('name').assign_string(name)
         scope.get('name_').assign_string(name_)
 
+        # TODO: Support setting app id
+        appid = 'org.gnome.' + name.title()
+        appid_path = '/' + appid.replace('.', '/')
+        scope.get('appid').assign_string(appid)
+        scope.get('appid_path').assign_string(appid_path)
+
+        prefix = name if not name.endswith('-glib') else name[:-5]
+        PREFIX = prefix.upper().replace('-','_')
+        prefix_ = prefix.lower().replace('-','_')
+        PreFix = ''.join([word.capitalize() for word in prefix.lower().split('-')])
+
+        scope.get('prefix').assign_string(prefix)
+        scope.get('Prefix').assign_string(prefix.capitalize())
+        scope.get('PreFix').assign_string(PreFix)
+        scope.get('prefix_').assign_string(prefix_)
+        scope.get('PREFIX').assign_string(PREFIX)
+
         scope.get('project_version').assign_string('0.1.0')
         scope.get('enable_i18n').assign_boolean(True)
-        scope.get('language').assign_string(LANGUAGE_MAP[self.language])
+        scope.get('language').assign_string(self.language)
         scope.get('author').assign_string(author_name)
 
+        # Just avoiding dealing with template bugs
+        if self.language == 'javascript':
+            ui_file = prefix + 'Window.ui'
+        elif self.language == 'python':
+            ui_file = prefix + '_window.ui'
+        else:
+            ui_file = prefix + '-window.ui'
+        scope.get('ui_file').assign_string(ui_file)
+
+        exec_name = appid if self.language == 'javascript' else name
+        scope.get('exec_name').assign_string(exec_name)
+
         modes = {
+            'resources/src/hello.js.in': 0o750,
+            'resources/src/hello.py.in': 0o750,
+            'resources/meson_post_install.py': 0o750,
         }
 
         expands = {
+            'prefix': prefix,
+            'appid': appid,
+            'prefix_': prefix_,
+            'name_': name_,
         }
 
         files = {
+            # Build files
             'resources/meson.build': 'meson.build',
-            'resources/src/meson.build': 'src/meson.build',
+            'resources/meson_post_install.py': 'meson_post_install.py',
 
             # Translations
             'resources/po/LINGUAS': 'po/LINGUAS',
             'resources/po/meson.build': 'po/meson.build',
             'resources/po/POTFILES': 'po/POTFILES',
         }
-
-        if self.language == 'c':
-            files['resources/src/main.c'] = 'src/main.c'
-        elif self.language == 'c++':
-            files['resources/src/main.c'] = 'src/main.cpp'
+        self.prepare_files(files)
 
         if 'license_full' in params:
             license_full_path = params['license_full'].get_string()
@@ -177,7 +208,7 @@ class MesonTemplate(Ide.TemplateBase, Ide.ProjectTemplate):
 
         for src, dst in files.items():
             destination = directory.get_child(dst % expands)
-            if src.startswith("resource://"):
+            if src.startswith('resource://'):
                 self.add_resource(src[11:], destination, scope, modes.get(src, 0))
             else:
                 path = get_module_data_path(src)
@@ -199,14 +230,60 @@ class MesonTemplate(Ide.TemplateBase, Ide.ProjectTemplate):
                 task.return_error(GLib.Error(repr(exc)))
 
 
-class EmptyProjectTemplate(MesonTemplate):
+class GnomeProjectTemplate(MesonTemplate):
     def __init__(self):
         super().__init__(
-            'empty-meson',
-            _('Empty Meson Project'),
-            'pattern-library',
-            _('Create a new empty meson project'),
-            ['C', 'C++']
+            'gnome-app',
+            _('GNOME Application'),
+            'pattern-gnome',
+            _('Create a new GNOME application'),
+            ['C', 'Python', 'JavaScript', 'Vala']
          )
 
+    def prepare_files(self, files):
+        # Shared files
+        files['resources/flatpak.json'] = '%(appid)s.json'
+        files['resources/data/hello.desktop.in'] = 'data/%(appid)s.desktop.in'
+        files['resources/data/hello.appdata.xml.in'] = 'data/%(appid)s.appdata.xml.in'
+        files['resources/data/hello.gschema.xml'] = 'data/%(appid)s.gschema.xml'
+        files['resources/data/meson.build'] = 'data/meson.build'
+        window_ui_name = 'src/%(prefix)s-window.ui'
+        resource_name = 'src/%(prefix)s.gresource.xml'
+        meson_file = 'resources/src/meson.build'
+
+        if self.language == 'c':
+            files['resources/src/main.c'] = 'src/main.c'
+            files['resources/src/hello-window.c'] = 'src/%(prefix)s-window.c'
+            files['resources/src/hello-window.h'] = 'src/%(prefix)s-window.h'
+        elif self.language == 'vala':
+            files['resources/src/main.vala'] = 'src/main.vala'
+        elif self.language == 'javascript':
+            files['resources/src/main.js'] = 'src/main.js'
+            files['resources/src/hello.js.in'] = 'src/%(appid)s.in'
+            files['resources/src/helloWindow.js'] = 'src/%(prefix)sWindow.js'
+            files['resources/src/hello.src.gresource.xml'] = 'src/%(appid)s.src.gresource.xml'
+            resource_name = 'src/%(appid)s.data.gresource.xml'
+            window_ui_name = 'src/%(prefix)sWindow.ui'
+            meson_file = 'resources/src/javascript-meson.build'
+        elif self.language == 'python':
+            files['resources/src/gi_composites.py'] = 'src/gi_composites.py'
+            files['resources/src/hello.py.in'] = 'src/%(name_)s.in'
+            files['resources/src/main.py'] = 'src/main.py'
+            files['resources/src/__init__.py'] = 'src/__init__.py'
+            files['resources/src/hello_window.py'] = 'src/%(prefix_)s_window.py'
+            window_ui_name = 'src/%(prefix)s_window.ui'
+            meson_file = 'resources/src/python-meson.build'
+
+        files['resources/src/hello.gresource.xml'] = resource_name
+        files['resources/src/hello-window.ui'] = window_ui_name
+        files[meson_file] = 'src/meson.build'
 
+class EmptyProjectTemplate(MesonTemplate):
+    def __init__(self):
+        super().__init__(
+            'empty',
+            _('Empty Project'),
+            'pattern-library',
+            _('Create a new empty project'),
+            ['C']
+         )     
diff --git a/plugins/meson-templates/meson_templates/resources/data/hello.appdata.xml.in 
b/plugins/meson-templates/meson_templates/resources/data/hello.appdata.xml.in
new file mode 100644
index 0000000..1ba8c0f
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/data/hello.appdata.xml.in
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop">
+       <id>{{appid}}.desktop</id>
+       <licence>CC0</licence>
+       <description>
+       </description>
+</component>
diff --git a/plugins/meson-templates/meson_templates/resources/data/hello.desktop.in 
b/plugins/meson-templates/meson_templates/resources/data/hello.desktop.in
new file mode 100644
index 0000000..654dbf5
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/data/hello.desktop.in
@@ -0,0 +1,7 @@
+[Desktop Entry]
+Name={{name}}
+Exec={{exec_name}}
+Terminal=false
+Type=Application
+Categories=GTK;
+StartupNotify=true
diff --git a/plugins/meson-templates/meson_templates/resources/data/hello.gschema.xml 
b/plugins/meson-templates/meson_templates/resources/data/hello.gschema.xml
new file mode 100644
index 0000000..3116681
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/data/hello.gschema.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schemalist gettext-domain="{{name}}">
+       <schema id="{{appid}}" path="{{appid_path}}/">
+       </schema>
+</schemalist>
diff --git a/plugins/meson-templates/meson_templates/resources/data/meson.build 
b/plugins/meson-templates/meson_templates/resources/data/meson.build
new file mode 100644
index 0000000..779d0b6
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/data/meson.build
@@ -0,0 +1,41 @@
+desktop_file = i18n.merge_file(
+  input: '{{appid}}.desktop.in',
+  output: '{{appid}}.desktop',
+  type: 'desktop',
+  po_dir: '../po',
+  install: true,
+  install_dir: join_paths(get_option('datadir'), 'applications')
+)
+
+desktop_utils = find_program('desktop-file-validate', required: false)
+if desktop_utils.found()
+  test('Validate desktop file', desktop_utils,
+    args: [desktop_file]
+  )
+endif
+
+appstream_file = i18n.merge_file(
+  input: '{{appid}}.appdata.xml.in',
+  output: '{{appid}}.appdata.xml',
+  po_dir: '../po',
+  install: true,
+  install_dir: join_paths(get_option('datadir'), 'appdata')
+)
+
+appstream_util = find_program('appstream-util', required: false)
+if appstream_util.found()
+  test('Validate appstream file', appstream_util,
+    args: ['validate', appstream_file]
+  )
+endif
+
+install_data('{{appid}}.gschema.xml',
+  install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
+)
+
+compile_schemas = find_program('glib-compile-schemas', required: false)
+if compile_schemas.found()
+  test('Validate schema file', compile_schemas,
+    args: ['--strict', '--dry-run', meson.current_source_dir()]
+  )
+endif
diff --git a/plugins/meson-templates/meson_templates/resources/flatpak.json 
b/plugins/meson-templates/meson_templates/resources/flatpak.json
new file mode 100644
index 0000000..a1c6e1e
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/flatpak.json
@@ -0,0 +1,52 @@
+{
+  "app-id": "{{appid}}",
+  "runtime": "org.gnome.Platform",
+  "runtime-version": "3.24",
+  "sdk": "org.gnome.Sdk",
+  "command": "{{exec_name}}",
+  "finish-args": [
+    "--share=network",
+    "--share=ipc",
+    "--socket=x11",
+    "--socket=wayland",
+    "--filesystem=xdg-run/dconf",
+    "--filesystem=~/.config/dconf:ro",
+    "--talk-name=ca.desrt.dconf",
+    "--env=DCONF_USER_CONFIG_DIR=.config/dconf"
+  ],
+  "build-options": {
+    "cflags": "-O2 -g",
+    "cxxflags": "-O2 -g",
+    "env": {
+      "V": "1"
+    }
+  },
+  "cleanup": [
+    "/include",
+    "/lib/pkgconfig",
+    "/man",
+    "/share/doc",
+    "/share/gtk-doc",
+    "/share/man",
+    "/share/pkgconfig",
+{{if language == "vala"}}    "/share/vala",{{end}}
+    "*.la",
+    "*.a"
+  ],
+  "modules": [
+    {
+      "name": "{{name}}",
+      "buildsystem": "meson",
+      "config-opts": [
+        "--libdir=lib"
+      ],
+      "builddir": true,
+      "sources": [
+        {
+          "type": "git",
+          "url": "file://{{project_path}}"
+        }
+      ]
+    }
+  ]
+}
diff --git a/plugins/meson-templates/meson_templates/resources/meson.build 
b/plugins/meson-templates/meson_templates/resources/meson.build
index af6194b..d2232c8 100644
--- a/plugins/meson-templates/meson_templates/resources/meson.build
+++ b/plugins/meson-templates/meson_templates/resources/meson.build
@@ -1,19 +1,24 @@
-project('{{name}}', '{{language}}',
+project('{{name}}',{{if language == "c"}}'c',{{else if language == "vala"}}'c', 'vala',{{end}}
   version: '{{project_version}}',
-  meson_version: '>= 0.36.0',
+  meson_version: '>= 0.40.0',
 )
 
-config_h = configuration_data()
+i18n = import('i18n')
+
+{{if language == "c"}}config_h = configuration_data()
 config_h.set_quoted('GETTEXT_PACKAGE', '{{name}}')
 config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir')))
 configure_file(
-  output: 'config.h',
+  output: '{{prefix}}-config.h',
   configuration: config_h,
 )
-add_global_arguments([
-  '-DHAVE_CONFIG_H',
+add_project_arguments([
   '-I' + meson.build_root(),
 ], language: '{{language}}')
 
+{{end}}
+subdir('data')
 subdir('src')
-{{if enable_i18n}}subdir('po'){{end}}
+subdir('po')
+
+meson.add_install_script('meson_post_install.py')
diff --git a/plugins/meson-templates/meson_templates/resources/meson_post_install.py 
b/plugins/meson-templates/meson_templates/resources/meson_post_install.py
new file mode 100644
index 0000000..6a3ea97
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/meson_post_install.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+from os import environ, path
+from subprocess import call
+
+prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local')
+datadir = path.join(prefix, 'share')
+destdir = environ.get('DESTDIR', '')
+
+# Package managers set this so we don't need to run
+if not destdir:
+    print('Updating icon cache...')
+    call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')])
+
+    print('Updating desktop database...')
+    call(['update-desktop-database', '-q', path.join(datadir, 'applications')])
+
+    print('Compiling GSettings schemas...')
+    call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')])
+
+
diff --git a/plugins/meson-templates/meson_templates/resources/po/POTFILES 
b/plugins/meson-templates/meson_templates/resources/po/POTFILES
index ef47360..d49f056 100644
--- a/plugins/meson-templates/meson_templates/resources/po/POTFILES
+++ b/plugins/meson-templates/meson_templates/resources/po/POTFILES
@@ -1 +1,11 @@
-src/{{if language == "c"}}main.c{{else if language == "cpp"}}main.cpp{{end}}
+data/{{appid}}.desktop.in
+data/{{appid}}.appdata.xml.in
+data/{{appid}}.gschema.xml
+{{if language == "c"}}src/main.c{{end}}
+{{if language == "c"}}src/{{prefix}}-window.c{{end}}
+{{if language == "c"}}src/{{prefix}}-window.ui{{end}}
+{{if language == "vala"}}src/main.vala{{end}}
+{{if language == "vala"}}src/{{prefix}}-window.ui{{end}}
+{{if language == "javascript"}}src/main.js{{end}}
+{{if language == "javascript"}}src/{{prefix}}Window.js{{end}}
+{{if language == "javascript"}}src/{{prefix}}Window.ui{{end}}
diff --git a/plugins/meson-templates/meson_templates/resources/po/meson.build 
b/plugins/meson-templates/meson_templates/resources/po/meson.build
index cf0948b..411d59f 100644
--- a/plugins/meson-templates/meson_templates/resources/po/meson.build
+++ b/plugins/meson-templates/meson_templates/resources/po/meson.build
@@ -1,16 +1 @@
-i18n = import('i18n')
-
-langs = [
-  # TODO: Translate app
-]
-
-if langs.length() > 0
-  i18n.gettext('{{name}}',
-    languages: langs,
-    args: [
-      '--from-code=UTF-8',
-      '--keyword=g_dngettext:2,3',
-      '--add-comments',
-    ],
-  )
-endif
+i18n.gettext('{{name}}', preset: 'glib')
diff --git a/plugins/meson-templates/meson_templates/resources/src/__init__.py 
b/plugins/meson-templates/meson_templates/resources/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/plugins/meson-templates/meson_templates/resources/src/gi_composites.py 
b/plugins/meson-templates/meson_templates/resources/src/gi_composites.py
new file mode 100644
index 0000000..857e584
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/gi_composites.py
@@ -0,0 +1,273 @@
+#
+# Copyright (C) 2015 Dustin Spicuzza <dustin virtualroadside com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
+# USA
+
+from os.path import abspath, join
+
+import inspect
+import warnings
+
+from gi.repository import Gio
+from gi.repository import GLib
+from gi.repository import GObject
+from gi.repository import Gtk
+
+__all__ = ['GtkTemplate']
+
+class GtkTemplateWarning(UserWarning):
+    pass
+
+def _connect_func(builder, obj, signal_name, handler_name,
+                  connect_object, flags, cls):
+    '''Handles GtkBuilder signal connect events'''
+
+    if connect_object is None:
+        extra = ()
+    else:
+        extra = (connect_object,)
+
+    # The handler name refers to an attribute on the template instance,
+    # so ask GtkBuilder for the template instance
+    template_inst = builder.get_object(cls.__gtype_name__)
+
+    if template_inst is None: # This should never happen
+        errmsg = "Internal error: cannot find template instance! obj: %s; " \
+                 "signal: %s; handler: %s; connect_obj: %s; class: %s" % \
+                 (obj, signal_name, handler_name, connect_object, cls)
+        warnings.warn(errmsg, GtkTemplateWarning)
+        return
+
+    handler = getattr(template_inst, handler_name)
+
+    if flags == GObject.ConnectFlags.AFTER:
+        obj.connect_after(signal_name, handler, *extra)
+    else:
+        obj.connect(signal_name, handler, *extra)
+
+    template_inst.__connected_template_signals__.add(handler_name)
+
+
+def _register_template(cls, template_bytes):
+    '''Registers the template for the widget and hooks init_template'''
+
+    # This implementation won't work if there are nested templates, but
+    # we can't do that anyways due to PyGObject limitations so it's ok
+
+    if not hasattr(cls, 'set_template'):
+        raise TypeError("Requires PyGObject 3.13.2 or greater")
+
+    cls.set_template(template_bytes)
+
+    bound_methods = set()
+    bound_widgets = set()
+
+    # Walk the class, find marked callbacks and child attributes
+    for name in dir(cls):
+
+        o = getattr(cls, name, None)
+
+        if inspect.ismethod(o):
+            if hasattr(o, '_gtk_callback'):
+                bound_methods.add(name)
+                # Don't need to call this, as connect_func always gets called
+                #cls.bind_template_callback_full(name, o)
+        elif isinstance(o, _Child):
+            cls.bind_template_child_full(name, True, 0)
+            bound_widgets.add(name)
+
+    # Have to setup a special connect function to connect at template init
+    # because the methods are not bound yet
+    cls.set_connect_func(_connect_func, cls)
+
+    cls.__gtemplate_methods__ = bound_methods
+    cls.__gtemplate_widgets__ = bound_widgets
+
+    base_init_template = cls.init_template
+    cls.init_template = lambda s: _init_template(s, cls, base_init_template)
+
+
+def _init_template(self, cls, base_init_template):
+    '''This would be better as an override for Gtk.Widget'''
+
+    # TODO: could disallow using a metaclass.. but this is good enough
+    # .. if you disagree, feel free to fix it and issue a PR :)
+    if self.__class__ is not cls:
+        raise TypeError("Inheritance from classes with @GtkTemplate decorators "
+                        "is not allowed at this time")
+
+    connected_signals = set()
+    self.__connected_template_signals__ = connected_signals
+
+    base_init_template(self)
+
+    for name in self.__gtemplate_widgets__:
+        widget = self.get_template_child(cls, name)
+        self.__dict__[name] = widget
+
+        if widget is None:
+            # Bug: if you bind a template child, and one of them was
+            #      not present, then the whole template is broken (and
+            #      it's not currently possible for us to know which
+            #      one is broken either -- but the stderr should show
+            #      something useful with a Gtk-CRITICAL message)
+            raise AttributeError("A missing child widget was set using "
+                                 "GtkTemplate.Child and the entire "
+                                 "template is now broken (widgets: %s)" %
+                                 ', '.join(self.__gtemplate_widgets__))
+
+    for name in self.__gtemplate_methods__.difference(connected_signals):
+        errmsg = ("Signal '%s' was declared with @GtkTemplate.Callback " +
+                  "but was not present in template") % name
+        warnings.warn(errmsg, GtkTemplateWarning)
+
+
+# TODO: Make it easier for IDE to introspect this
+class _Child(object):
+    '''
+        Assign this to an attribute in your class definition and it will
+        be replaced with a widget defined in the UI file when init_template
+        is called
+    '''
+
+    __slots__ = []
+
+    @staticmethod
+    def widgets(count):
+        '''
+            Allows declaring multiple widgets with less typing::
+
+                button    \
+                label1    \
+                label2    = GtkTemplate.Child.widgets(3)
+        '''
+        return [_Child() for _ in range(count)]
+
+
+class _GtkTemplate(object):
+    '''
+        Use this class decorator to signify that a class is a composite
+        widget which will receive widgets and connect to signals as
+        defined in a UI template. You must call init_template to
+        cause the widgets/signals to be initialized from the template::
+
+            @GtkTemplate(ui='foo.ui')
+            class Foo(Gtk.Box):
+
+                def __init__(self):
+                    super(Foo, self).__init__()
+                    self.init_template()
+
+        The 'ui' parameter can either be a file path or a GResource resource
+        path::
+
+            @GtkTemplate(ui='/org/example/foo.ui')
+            class Foo(Gtk.Box):
+                pass
+
+        To connect a signal to a method on your instance, do::
+
+            @GtkTemplate.Callback
+            def on_thing_happened(self, widget):
+                pass
+
+        To create a child attribute that is retrieved from your template,
+        add this to your class definition::
+
+            @GtkTemplate(ui='foo.ui')
+            class Foo(Gtk.Box):
+
+                widget = GtkTemplate.Child()
+
+
+        Note: This is implemented as a class decorator, but if it were
+        included with PyGI I suspect it might be better to do this
+        in the GObject metaclass (or similar) so that init_template
+        can be called automatically instead of forcing the user to do it.
+
+        .. note:: Due to limitations in PyGObject, you may not inherit from
+                  python objects that use the GtkTemplate decorator.
+    '''
+
+    __ui_path__ = None
+
+    @staticmethod
+    def Callback(f):
+        '''
+            Decorator that designates a method to be attached to a signal from
+            the template
+        '''
+        f._gtk_callback = True
+        return f
+
+
+    Child = _Child
+
+    @staticmethod
+    def set_ui_path(*path):
+        '''
+            If using file paths instead of resources, call this *before*
+            loading anything that uses GtkTemplate, or it will fail to load
+            your template file
+
+            :param path: one or more path elements, will be joined together
+                         to create the final path
+
+            TODO: Alternatively, could wait until first class instantiation
+                  before registering templates? Would need a metaclass...
+        '''
+        _GtkTemplate.__ui_path__ = abspath(join(*path))
+
+
+    def __init__(self, ui):
+        self.ui = ui
+
+    def __call__(self, cls):
+
+        if not issubclass(cls, Gtk.Widget):
+            raise TypeError("Can only use @GtkTemplate on Widgets")
+
+        # Nested templates don't work
+        if hasattr(cls, '__gtemplate_methods__'):
+            raise TypeError("Cannot nest template classes")
+
+        # Load the template either from a resource path or a file
+        # - Prefer the resource path first
+
+        try:
+            template_bytes = Gio.resources_lookup_data(self.ui, Gio.ResourceLookupFlags.NONE)
+        except GLib.GError:
+            ui = self.ui
+            if isinstance(ui, (list, tuple)):
+                ui = join(ui)
+
+            if _GtkTemplate.__ui_path__ is not None:
+                ui = join(_GtkTemplate.__ui_path__, ui)
+
+            with open(ui, 'rb') as fp:
+                template_bytes = GLib.Bytes.new(fp.read())
+
+        _register_template(cls, template_bytes)
+        return cls
+
+
+
+# Future shim support if this makes it into PyGI?
+#if hasattr(Gtk, 'GtkTemplate'):
+#    GtkTemplate = lambda c: c
+#else:
+GtkTemplate = _GtkTemplate
+    
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello-window.c 
b/plugins/meson-templates/meson_templates/resources/src/hello-window.c
new file mode 100644
index 0000000..7d24dc0
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello-window.c
@@ -0,0 +1,29 @@
+{{include "license.c"}}
+
+#include <gtk/gtk.h>
+#include "{{prefix}}-config.h"
+#include "{{prefix}}-window.h"
+
+struct _{{Prefix}}Window
+{
+  GtkWindow     parent_instance;
+  GtkHeaderBar *header_bar;
+  GtkLabel     *label;
+};
+
+G_DEFINE_TYPE ({{Prefix}}Window, {{prefix}}_window, GTK_TYPE_WINDOW)
+
+static void
+{{prefix}}_window_class_init ({{Prefix}}WindowClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "{{appid_path}}/{{prefix}}-window.ui");
+  gtk_widget_class_bind_template_child (widget_class, {{Prefix}}Window, label);
+}
+
+static void
+{{prefix}}_window_init ({{Prefix}}Window *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello-window.h 
b/plugins/meson-templates/meson_templates/resources/src/hello-window.h
new file mode 100644
index 0000000..3d0b423
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello-window.h
@@ -0,0 +1,12 @@
+{{include "license.h"}}
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define {{PREFIX}}_TYPE_WINDOW ({{prefix}}_window_get_type())
+G_DECLARE_FINAL_TYPE ({{Prefix}}Window, {{prefix}}_window, {{PREFIX}}, WINDOW, GtkWindow)
+
+G_END_DECLS
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello-window.ui 
b/plugins/meson-templates/meson_templates/resources/src/hello-window.ui
new file mode 100644
index 0000000..b19485b
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello-window.ui
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="{{Prefix}}Window" parent="GtkApplicationWindow">
+    <child type="titlebar">
+      <object class="GtkHeaderBar" id="headerbar">
+        <property name="visible">true</property>
+        <property name="show-close-button">true</property>
+        <property name="title">Hello, World!</property>
+      </object>
+    </child>
+    <child>
+      <object class="GtkLabel" id="label">
+        <property name="label">Hello, World!</property>
+        <property name="visible">true</property>
+        <attributes>
+          <attribute name="weight" value="bold"/>
+          <attribute name="scale" value="2"/>
+        </attributes>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello.gresource.xml 
b/plugins/meson-templates/meson_templates/resources/src/hello.gresource.xml
new file mode 100644
index 0000000..ff0db93
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="{{appid_path}}">
+    <file>{{ui_file}}</file>
+  </gresource>
+</gresources>
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello.js.in 
b/plugins/meson-templates/meson_templates/resources/src/hello.js.in
new file mode 100755
index 0000000..3951186
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello.js.in
@@ -0,0 +1,9 @@
+#!@GJS@
+imports.package.init({
+  name: "@PACKAGE_NAME@",
+  version: "@PACKAGE_VERSION@",
+  prefix: "@prefix@",
+  libdir: "@libdir@",
+  datadir: "@datadir@",
+});
+imports.package.run(imports.main);
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello.py.in 
b/plugins/meson-templates/meson_templates/resources/src/hello.py.in
new file mode 100644
index 0000000..c9a7e9c
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello.py.in
@@ -0,0 +1,26 @@
+#!@PYTHON@
+
+{{include "license.py"}}
+
+import os
+import sys
+import signal
+import gettext
+
+VERSION = '@VERSION@'
+pkgdatadir = '@pkgdatadir@'
+localedir = '@localedir@'
+
+sys.path.insert(1, pkgdatadir)
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+gettext.install('{{name}}', localedir)
+
+if __name__ == '__main__':
+    import gi
+
+    from gi.repository import Gio
+    resource = Gio.Resource.load(os.path.join(pkgdatadir, '{{name}}.gresource'))
+    resource._register()
+
+    from {{name_}} import main
+    sys.exit(main.main(VERSION))
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello.src.gresource.xml 
b/plugins/meson-templates/meson_templates/resources/src/hello.src.gresource.xml
new file mode 100644
index 0000000..08b4469
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello.src.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="{{appid_path}}/js">
+    <file>main.js</file>
+    <file>{{prefix}}Window.js</file>
+  </gresource>
+</gresources>
diff --git a/plugins/meson-templates/meson_templates/resources/src/helloWindow.js 
b/plugins/meson-templates/meson_templates/resources/src/helloWindow.js
new file mode 100644
index 0000000..3f6edbc
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/helloWindow.js
@@ -0,0 +1,20 @@
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Gtk = imports.gi.Gtk;
+const Lang = imports.lang;
+
+var {{Prefix}}Window = new Lang.Class({
+    Name: '{{Prefix}}Window',
+    GTypeName: '{{Prefix}}Window',
+    Extends: Gtk.ApplicationWindow,
+    Template: 'resource://{{appid_path}}/{{prefix}}Window.ui',
+
+    _init(application) {
+        this.parent({
+            application,
+            default_width: 600,
+            default_height: 300,
+        });
+    },
+});
+
diff --git a/plugins/meson-templates/meson_templates/resources/src/hello_window.py 
b/plugins/meson-templates/meson_templates/resources/src/hello_window.py
new file mode 100644
index 0000000..8fb0e02
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/hello_window.py
@@ -0,0 +1,13 @@
+{{include "license.py"}}
+
+from gi.repository import Gtk
+from .gi_composites import GtkTemplate
+
+@GtkTemplate(ui='{{appid_path}}/{{ui_file}}')
+class {{Prefix}}Window(Gtk.ApplicationWindow):
+    __gtype_name__ = '{{Prefix}}Window'
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+        self.init_template()
+
diff --git a/plugins/meson-templates/meson_templates/resources/src/javascript-meson.build 
b/plugins/meson-templates/meson_templates/resources/src/javascript-meson.build
new file mode 100644
index 0000000..d277070
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/javascript-meson.build
@@ -0,0 +1,32 @@
+pkgdatadir = join_paths(get_option('datadir'), meson.project_name())
+gnome = import('gnome')
+
+src_res = gnome.compile_resources('{{appid}}.src',
+  '{{appid}}.src.gresource.xml',
+  gresource_bundle: true,
+  install: true,
+  install_dir: pkgdatadir,
+)
+
+data_res = gnome.compile_resources('{{appid}}.data',
+  '{{appid}}.data.gresource.xml',
+  gresource_bundle: true,
+  install: true,
+  install_dir: pkgdatadir,
+)
+
+bin_conf = configuration_data()
+bin_conf.set('GJS', find_program('gjs').path())
+bin_conf.set('PACKAGE_VERSION', meson.project_version())
+bin_conf.set('PACKAGE_NAME', meson.project_name())
+bin_conf.set('prefix', get_option('prefix'))
+bin_conf.set('libdir', join_paths(get_option('prefix'), get_option('libdir')))
+bin_conf.set('datadir', join_paths(get_option('prefix'), get_option('datadir')))
+
+configure_file(
+  input: '{{appid}}.in',
+  output: '{{appid}}',
+  configuration: bin_conf,
+  install: true,
+  install_dir: get_option('bindir')
+)
diff --git a/plugins/meson-templates/meson_templates/resources/src/main.c 
b/plugins/meson-templates/meson_templates/resources/src/main.c
index a501eb2..cb59190 100644
--- a/plugins/meson-templates/meson_templates/resources/src/main.c
+++ b/plugins/meson-templates/meson_templates/resources/src/main.c
@@ -1,21 +1,72 @@
 {{include "license.c"}}
 
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
+#include "{{prefix}}-config.h"
+#include "{{prefix}}-window.h"
 
-{{if enable_i18n}}#include <glib/gi18n.h>{{end}}
-#include <glib.h>
+static void
+on_activate (GtkApplication *app)
+{
+  GtkWindow *window;
+
+  /* It's good practice to check your parameters at the beginning of the
+   * function. It helps catch errors early and in development instead of
+   * by your users.
+   */
+  g_assert (GTK_IS_APPLICATION (app));
+
+  /*
+   * Get the current window. If there is not one, we will create it.
+   */
+  window = gtk_application_get_active_window (app);
+  if (window == NULL)
+    window = g_object_new ({{PREFIX}}_TYPE_WINDOW,
+                           "application", app,
+                           "default-width", 600,
+                           "default-height", 300,
+                           NULL);
+
+  /*
+   * Ask the window manager/compositor to present the window to the user.
+   */
+  gtk_window_present (window);
+}
 
 int
-main(int   argc,
-     char *argv[])
+main (int   argc,
+      char *argv[])
 {
-{{if enable_i18n}}
-  bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
-  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
-  textdomain (GETTEXT_PACKAGE);
-{{end}}
+  g_autoptr(GtkApplication) app = NULL;
+  int ret;
+
+  /*
+   * Create a new GtkApplication. The application manages our main loop,
+   * application windows, integration with the window manager/compositor, and
+   * desktop features such as file opening and single-instance applications.
+   */
+  app = gtk_application_new ("{{appid}}", G_APPLICATION_FLAGS_NONE);
+
+  /*
+   * We connect to the activate signal to create a window when the application
+   * has been lauched. Additionally, this signal notifies us when the user
+   * tries to launch a "second instance" of the application. When they try
+   * to do that, we'll just present any existing window.
+   *
+   * Because we can't pass a pointer to any function type, we have to cast
+   * our "on_activate" function to a GCallback.
+   */
+  g_signal_connect (app, "activate", G_CALLBACK (on_activate), NULL);
+
+  /*
+   * Run the application. This function will block until the applicaiton
+   * exits. Upon return, we have our exit code to return to the shell. (This
+   * is the code you see when you do `echo $?` after running a command in a
+   * terminal.
+   *
+   * Since GtkApplication inherits from GApplication, we use the parent class
+   * method "run". But we need to cast, which is what the "G_APPLICATION()"
+   * macro does.
+   */
+  ret = g_application_run (G_APPLICATION (app), argc, argv);
 
-  return 0;
+  return ret;
 }
diff --git a/plugins/meson-templates/meson_templates/resources/src/main.js 
b/plugins/meson-templates/meson_templates/resources/src/main.js
new file mode 100644
index 0000000..57dad50
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/main.js
@@ -0,0 +1,30 @@
+pkg.initGettext();
+pkg.initFormat();
+pkg.require({
+  'Gio': '2.0',
+  'Gtk': '3.0'
+});
+
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+
+const Window = imports.{{prefix}}Window;
+
+function main(argv) {
+
+  let app = new Gtk.Application({
+      application_id: '{{appid}}',
+      flags: Gio.ApplicationFlags.FLAGS_NONE,
+  });
+
+  app.connect('activate', app => {
+      let win = app.active_window;
+
+      if (!win)
+          win = new Window.{{Prefix}}Window(app);
+
+      win.present();
+  });
+
+  return app.run(argv);
+}
diff --git a/plugins/meson-templates/meson_templates/resources/src/main.py 
b/plugins/meson-templates/meson_templates/resources/src/main.py
new file mode 100755
index 0000000..2be450a
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/main.py
@@ -0,0 +1,25 @@
+{{include "license.py"}}
+
+import sys
+import gi
+
+gi.require_version('Gtk', '3.0')
+
+from gi.repository import Gtk, Gio
+
+from .{{prefix_}}_window import {{Prefix}}Window
+
+class Application(Gtk.Application):
+    def __init__(self):
+        super().__init__(application_id='{{appid}}',
+                         flags = Gio.ApplicationFlags.FLAGS_NONE)
+
+    def do_activate(self):
+        win = self.props.active_window
+        if not win:
+            win = {{Prefix}}Window(application=self)
+        win.present()
+
+def main(version):
+    app = Application()
+    return app.run(sys.argv)
diff --git a/plugins/meson-templates/meson_templates/resources/src/main.vala 
b/plugins/meson-templates/meson_templates/resources/src/main.vala
new file mode 100644
index 0000000..154b66b
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/main.vala
@@ -0,0 +1,29 @@
+using Gtk;
+
+namespace {{Prefix}} {
+       [GtkTemplate (ui = "{{appid_path}}/{{prefix}}-window.ui")]
+       public class Window : Gtk.Window {
+               [GtkChild]
+               Label label;
+       
+               [GtkChild]
+               HeaderBar headerbar;
+               
+               public Window (Gtk.Application app) {
+                       Object(application: app);
+               }
+       }
+}
+
+int main (string[] args) {
+       var app = new Gtk.Application ("{{appid}}", ApplicationFlags.FLAGS_NONE);
+       app.activate.connect (() => {
+               if (app.active_window == null) {
+                       new {{Prefix}}.Window (app).show_all();
+               }
+               app.active_window.present ();
+       });
+       int ret = app.run (args);
+
+       return ret;
+}
diff --git a/plugins/meson-templates/meson_templates/resources/src/meson.build 
b/plugins/meson-templates/meson_templates/resources/src/meson.build
index cc421d4..d2e4f2f 100644
--- a/plugins/meson-templates/meson_templates/resources/src/meson.build
+++ b/plugins/meson-templates/meson_templates/resources/src/meson.build
@@ -1,12 +1,22 @@
 {{name_}}_sources = [
-  '{{if language == "c"}}main.c{{else if language == "cpp"}}main.cpp{{end}}',
+  {{if language == "c"}}'main.c',
+  '{{prefix}}-window.c',{{else if  language == "vala"}}'main.vala'{{end}}
 ]
 
 {{name_}}_deps = [
-  dependency('glib-2.0'),
+  dependency('gio-2.0', version: '>= 2.50'),
+  dependency('gtk+-3.0', version: '>= 3.22'),
 ]
 
+gnome = import('gnome')
+
+{{name_}}_sources += gnome.compile_resources('{{prefix}}-resources',
+  '{{prefix}}.gresource.xml',
+  c_name: '{{prefix}}'
+)
+
 executable('{{name}}', {{name_}}_sources,
+{{if language == "vala"}}  vala_args: '--target-glib=2.50',{{end}}
   dependencies: {{name_}}_deps,
   install: true,
 )
diff --git a/plugins/meson-templates/meson_templates/resources/src/python-meson.build 
b/plugins/meson-templates/meson_templates/resources/src/python-meson.build
new file mode 100644
index 0000000..552c0e0
--- /dev/null
+++ b/plugins/meson-templates/meson_templates/resources/src/python-meson.build
@@ -0,0 +1,35 @@
+pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
+moduledir = join_paths(pkgdatadir, '{{name_}}')
+gnome = import('gnome')
+
+gnome.compile_resources('{{name}}',
+  '{{name}}.gresource.xml',
+  gresource_bundle: true,
+  install: true,
+  install_dir: pkgdatadir,
+)
+
+python3 = import('python3')
+
+conf = configuration_data()
+conf.set('PYTHON', python3.find_python().path())
+conf.set('VERSION', meson.project_version())
+conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir')))
+conf.set('pkgdatadir', pkgdatadir)
+
+configure_file(
+  input: '{{name}}.in',
+  output: '{{name}}',
+  configuration: conf,
+  install: true,
+  install_dir: get_option('bindir')
+)
+
+{{name_}}_sources = [
+  '__init__.py',
+  'gi_composites.py',
+  'main.py',
+  '{{prefix_}}_window.py',
+]
+
+install_data({{name_}}_sources, install_dir: moduledir)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]