Skip to content

Commit

Permalink
Merge pull request #2221 from forslund/refactor/split-skills-core
Browse files Browse the repository at this point in the history
Split mycroft.skills.core
  • Loading branch information
forslund committed Jul 26, 2019
2 parents 0bde1bc + 0689f60 commit 5eba242
Show file tree
Hide file tree
Showing 9 changed files with 1,874 additions and 1,768 deletions.
15 changes: 13 additions & 2 deletions mycroft/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,19 @@
from mycroft.api import Api
from mycroft.messagebus.message import Message
from mycroft.skills.context import adds_context, removes_context
from mycroft.skills.core import MycroftSkill, FallbackSkill, \
intent_handler, intent_file_handler
from mycroft.skills import (MycroftSkill, FallbackSkill,
intent_handler, intent_file_handler)
from mycroft.skills.intent_service import AdaptIntent

MYCROFT_ROOT_PATH = abspath(join(dirname(__file__), '..'))

__all__ = ['MYCROFT_ROOT_PATH',
'Api',
'Message',
'adds_context',
'removes_context',
'MycroftSkill',
'FallbackSkill',
'intent_handler',
'intent_file_handler',
'AdaptIntent']
268 changes: 268 additions & 0 deletions mycroft/enclosure/gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
# Copyright 2019 Mycroft AI Inc.
#
# 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.
#
""" Interface for interacting with the Mycroft gui qml viewer. """
from os.path import join

from mycroft.configuration import Configuration
from mycroft.messagebus.message import Message
from mycroft.util import resolve_resource_file


class SkillGUI:
"""SkillGUI - Interface to the Graphical User Interface
Values set in this class are synced to the GUI, accessible within QML
via the built-in sessionData mechanism. For example, in Python you can
write in a skill:
self.gui['temp'] = 33
self.gui.show_page('Weather.qml')
Then in the Weather.qml you'd access the temp via code such as:
text: sessionData.time
"""

def __init__(self, skill):
self.__session_data = {} # synced to GUI for use by this skill's pages
self.page = None # the active GUI page (e.g. QML template) to show
self.skill = skill
self.on_gui_changed_callback = None
self.config = Configuration.get()

@property
def remote_url(self):
"""Returns configuration value for url of remote-server."""
return self.config.get('remote-server')

def build_message_type(self, event):
"""Builds a message matching the output from the enclosure."""
return '{}.{}'.format(self.skill.skill_id, event)

def setup_default_handlers(self):
"""Sets the handlers for the default messages."""
msg_type = self.build_message_type('set')
self.skill.add_event(msg_type, self.gui_set)

def register_handler(self, event, handler):
"""Register a handler for GUI events.
When using the triggerEvent method from Qt
triggerEvent("event", {"data": "cool"})
Arguments:
event (str): event to catch
handler: function to handle the event
"""
msg_type = self.build_message_type(event)
self.skill.add_event(msg_type, handler)

def set_on_gui_changed(self, callback):
"""Registers a callback function to run when a value is
changed from the GUI.
Arguments:
callback: Function to call when a value is changed
"""
self.on_gui_changed_callback = callback

def gui_set(self, message):
"""Handler catching variable changes from the GUI.
Arguments:
message: Messagebus message
"""
for key in message.data:
self[key] = message.data[key]
if self.on_gui_changed_callback:
self.on_gui_changed_callback()

def __setitem__(self, key, value):
"""Implements set part of dict-like behaviour with named keys."""
self.__session_data[key] = value

if self.page:
# emit notification (but not needed if page has not been shown yet)
data = self.__session_data.copy()
data.update({'__from': self.skill.skill_id})
self.skill.bus.emit(Message("gui.value.set", data))

def __getitem__(self, key):
"""Implements get part of dict-like behaviour with named keys."""
return self.__session_data[key]

def __contains__(self, key):
"""Implements the "in" operation."""
return self.__session_data.__contains__(key)

def clear(self):
"""Reset the value dictionary, and remove namespace from GUI."""
self.__session_data = {}
self.page = None
self.skill.bus.emit(Message("gui.clear.namespace",
{"__from": self.skill.skill_id}))

def send_event(self, event_name, params={}):
"""Trigger a gui event.
Arguments:
event_name (str): name of event to be triggered
params: json serializable object containing any parameters that
should be sent along with the request.
"""
self.skill.bus.emit(Message("gui.event.send",
{"__from": self.skill.skill_id,
"event_name": event_name,
"params": params}))

def show_page(self, name, override_idle=None):
"""Begin showing the page in the GUI
Arguments:
name (str): Name of page (e.g "mypage.qml") to display
override_idle (boolean, int):
True: Takes over the resting page indefinitely
(int): Delays resting page for the specified number of
seconds.
"""
self.show_pages([name], 0, override_idle)

def show_pages(self, page_names, index=0, override_idle=None):
"""Begin showing the list of pages in the GUI.
Arguments:
page_names (list): List of page names (str) to display, such as
["Weather.qml", "Forecast.qml", "Details.qml"]
index (int): Page number (0-based) to show initially. For the
above list a value of 1 would start on "Forecast.qml"
override_idle (boolean, int):
True: Takes over the resting page indefinitely
(int): Delays resting page for the specified number of
seconds.
"""
if not isinstance(page_names, list):
raise ValueError('page_names must be a list')

if index > len(page_names):
raise ValueError('Default index is larger than page list length')

self.page = page_names[index]

# First sync any data...
data = self.__session_data.copy()
data.update({'__from': self.skill.skill_id})
self.skill.bus.emit(Message("gui.value.set", data))

# Convert pages to full reference
page_urls = []
for name in page_names:
if name.startswith("SYSTEM"):
page = resolve_resource_file(join('ui', name))
else:
page = self.skill.find_resource(name, 'ui')
if page:
if self.config.get('remote'):
page_urls.append(self.remote_url + "/" + page)
else:
page_urls.append("file://" + page)
else:
raise FileNotFoundError("Unable to find page: {}".format(name))

self.skill.bus.emit(Message("gui.page.show",
{"page": page_urls,
"index": index,
"__from": self.skill.skill_id,
"__idle": override_idle}))

def remove_page(self, page):
"""Remove a single page from the GUI.
Arguments:
page (str): Page to remove from the GUI
"""
return self.remove_pages([page])

def remove_pages(self, page_names):
"""Remove a list of pages in the GUI.
Arguments:
page_names (list): List of page names (str) to display, such as
["Weather.qml", "Forecast.qml", "Other.qml"]
"""
if not isinstance(page_names, list):
raise ValueError('page_names must be a list')

# Convert pages to full reference
page_urls = []
for name in page_names:
page = self.skill.find_resource(name, 'ui')
if page:
page_urls.append("file://" + page)
else:
raise FileNotFoundError("Unable to find page: {}".format(name))

self.skill.bus.emit(Message("gui.page.delete",
{"page": page_urls,
"__from": self.skill.skill_id}))

def show_text(self, text, title=None, override_idle=None):
"""Display a GUI page for viewing simple text.
Arguments:
text (str): Main text content. It will auto-paginate
title (str): A title to display above the text content.
"""
self.clear()
self["text"] = text
self["title"] = title
self.show_page("SYSTEM_TextFrame.qml", override_idle)

def show_image(self, url, caption=None,
title=None, fill=None,
override_idle=None):
"""Display a GUI page for viewing an image.
Arguments:
url (str): Pointer to the image
caption (str): A caption to show under the image
title (str): A title to display above the image content
fill (str): Fill type supports 'PreserveAspectFit',
'PreserveAspectCrop', 'Stretch'
"""
self.clear()
self["image"] = url
self["title"] = title
self["caption"] = caption
self["fill"] = fill
self.show_page("SYSTEM_ImageFrame.qml", override_idle)

def show_html(self, html, resource_url=None, override_idle=None):
"""Display an HTML page in the GUI.
Arguments:
html (str): HTML text to display
resource_url (str): Pointer to HTML resources
"""
self.clear()
self["html"] = html
self["resourceLocation"] = resource_url
self.show_page("SYSTEM_HtmlFrame.qml", override_idle)

def show_url(self, url, override_idle=None):
"""Display an HTML page in the GUI.
Arguments:
url (str): URL to render
"""
self.clear()
self["url"] = url
self.show_page("SYSTEM_UrlFrame.qml", override_idle)
24 changes: 24 additions & 0 deletions mycroft/skills/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,27 @@
# 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.

""" Mycroft skills module, collection of tools for building skills.
These classes, decorators and functions are used to build skills for Mycroft.
"""


from .mycroft_skill import (MycroftSkill, intent_handler, intent_file_handler,
resting_screen_handler)
from .fallback_skill import FallbackSkill
from .common_iot_skill import CommonIoTSkill
from .common_play_skill import CommonPlaySkill, CPSMatchLevel
from .common_query_skill import CommonQuerySkill, CQSMatchLevel

__all__ = ['MycroftSkill',
'intent_handler',
'intent_file_handler',
'resting_screen_handler',
'FallbackSkill',
'CommonIoTSkill',
'CommonPlaySkill',
'CPSMatchLevel',
'CommonQuerySkill',
'CQSMatchLevel']
2 changes: 1 addition & 1 deletion mycroft/skills/common_iot_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from functools import total_ordering, wraps
from itertools import count

from mycroft import MycroftSkill
from .mycroft_skill import MycroftSkill
from mycroft.messagebus.message import Message


Expand Down
4 changes: 2 additions & 2 deletions mycroft/skills/common_play_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
import re
from enum import Enum
from abc import ABC, abstractmethod
from mycroft import MycroftSkill
from mycroft.skills.audioservice import AudioService
from mycroft.messagebus.message import Message
from .mycroft_skill import MycroftSkill
from .audioservice import AudioService


class CPSMatchLevel(Enum):
Expand Down
3 changes: 1 addition & 2 deletions mycroft/skills/common_query_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@

from enum import IntEnum
from abc import ABC, abstractmethod
from mycroft import MycroftSkill
from mycroft.messagebus.message import Message
from .mycroft_skill import MycroftSkill


class CQSMatchLevel(IntEnum):
Expand Down
Loading

0 comments on commit 5eba242

Please sign in to comment.