diff --git a/MANIFEST.in b/MANIFEST.in index 006a72b..dccd76b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,2 @@ -recursive-include PyXA/apps * \ No newline at end of file +recursive-include PyXA/apps * +recursive-include PyXA/Additions * \ No newline at end of file diff --git a/PyXA/Additions/__init__.py b/PyXA/Additions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/PyXA/XABase.py b/PyXA/XABase.py index d74b15c..50bc879 100644 --- a/PyXA/XABase.py +++ b/PyXA/XABase.py @@ -3815,24 +3815,6 @@ def text(self) -> 'XAText': def text(self, text: str): self.set_property("text", text) - def set_text(self, new_text: str) -> 'XATextDocument': - """Sets the text of the document. - - :param new_text: The new text of the document - :type new_text: str - :return: A reference to the document object. - :rtype: XATextDocument - - .. seealso:: :func:`prepend`, :func:`append` - - .. deprecated:: 0.1.0 - Directly set the text attribute instead. - - .. versionadded:: 0.0.1 - """ - self.set_property("text", new_text) - return self - def prepend(self, text: str) -> 'XATextDocument': """Inserts the provided text at the beginning of the document. diff --git a/docs/.buildinfo b/docs/.buildinfo index c58298a..decc719 100644 --- a/docs/.buildinfo +++ b/docs/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: e66641e5564ff243b6ce56fa6327a42b +config: a9ded26fdd0bd00c428ae81c71202a6c tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_images/PyXALogoTransparent.png b/docs/_images/PyXALogoTransparent.png new file mode 100644 index 0000000..6d8a9aa Binary files /dev/null and b/docs/_images/PyXALogoTransparent.png differ diff --git a/docs/_modules/PyXA/PyXA 2.html b/docs/_modules/PyXA/PyXA 2.html new file mode 100644 index 0000000..c62c626 --- /dev/null +++ b/docs/_modules/PyXA/PyXA 2.html @@ -0,0 +1,541 @@ + + + + + + PyXA.PyXA — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.PyXA

+from datetime import datetime
+from time import sleep
+from typing import Any, Callable, List, Union
+import importlib
+
+import AppKit
+from Quartz import CGWindowListCopyWindowInfo, kCGNullWindowID, kCGWindowListOptionAll
+
+from .XABase import *
+from PyXA import XABaseScriptable
+from .apps import application_classes
+
+from .XAErrors import ApplicationNotFoundError
+
+VERSION = "0.1.0" #: The installed version of PyXA
+
+supported_applications: List[str] = list(application_classes.keys()) #: A list of names of supported scriptable applications
+
+
[docs]class XAApplicationList(XAList): + """A wrapper around a list of applications. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAApplication, filter) + + if filter is not None: + self.xa_elem = XAPredicate().from_dict(filter).evaluate(self.xa_elem) + +
[docs] def first(self) -> XAObject: + """Retrieves the first element of the list as a wrapped PyXA application object. + + :return: The wrapped object + :rtype: XAObject + + .. versionadded:: 0.0.5 + """ + return self.__getitem__(0)
+ +
[docs] def last(self) -> XAObject: + """Retrieves the last element of the list as a wrapped PyXA application object. + + :return: The wrapped object + :rtype: XAObject + + .. versionadded:: 0.0.5 + """ + return self.__getitem__(-1)
+ +
[docs] def pop(self, index: int = -1) -> XAObject: + """Removes the application at the specified index from the list and returns it. + + .. versionadded:: 0.0.5 + """ + removed = self.xa_elem.lastObject() + self.xa_elem.removeLastObject() + app_name = removed["kCGWindowOwnerName"] + return Application(app_name)
+ + def __getitem__(self, key: Union[int, slice]): + """Retrieves the wrapped application object(s) at the specified key.""" + if isinstance(key, slice): + arr = AppKit.NSArray.alloc().initWithArray_([self.xa_elem[index] for index in range(key.start, key.stop, key.step or 1)]) + return self._new_element(arr, self.__class__) + app_name = self.xa_elem[key]["kCGWindowOwnerName"] + return Application(app_name) + +
[docs] def bundle_identifier(self) -> List[str]: + """Gets the bundle identifier of every application in the list. + + :return: The list of application bundle identifiers + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ + return [app.bundle_identifier for app in self]
+ +
[docs] def bundle_url(self) -> List[XAURL]: + """Gets the bundle URL of every application in the list. + + :return: The list of application bundle URLs + :rtype: List[XAURL] + + .. versionadded:: 0.0.5 + """ + return [XAURL(app.bundle_url)for app in self]
+ +
[docs] def executable_url(self) -> List[XAURL]: + """Gets the executable URL of every application in the list. + + :return: The list of application executable URLs + :rtype: List[XAURL] + + .. versionadded:: 0.0.5 + """ + return [XAURL(app.executable_url) for app in self]
+ +
[docs] def launch_date(self) -> List[datetime]: + """Gets the launch date of every application in the list. + + :return: The list of application launch dates + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ + return [app.launch_date for app in self]
+ +
[docs] def localized_name(self) -> List[str]: + """Gets the localized name of every application in the list. + + :return: The list of application localized names + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ + return [x.get("kCGWindowOwnerName") for x in self.xa_elem]
+ +
[docs] def process_identifier(self) -> List[str]: + """Gets the process identifier of every application in the list. + + :return: The list of application process identifiers + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ + return [x.get("kCGWindowOwnerPID") for x in self.xa_elem]
+ +
[docs] def hide(self): + """Hides all applications in the list. + + :Example 1: Hide all visible running applications + + >>> import PyXA + >>> apps = PyXA.running_applications() + >>> apps.hide() + + .. seealso:: :func:`unhide` + + .. versionadded:: 0.0.5 + """ + for app in self: + app.hide()
+ +
[docs] def unhide(self): + """Unhides all applications in the list. + + :Example 1: Hide then unhide all visible running applications + + >>> import PyXA + >>> apps = PyXA.running_applications() + >>> apps.hide() + >>> apps.unhide() + + .. seealso:: :func:`hide` + + .. versionadded:: 0.0.5 + """ + for app in self: + app.unhide()
+ +
[docs] def terminate(self): + """Quits (terminates) all applications in the list. Synonymous with :func:`quit`. + + :Example 1: Terminate all visible running applications + + >>> import PyXA + >>> apps = PyXA.running_applications() + >>> apps.terminate() + + .. versionadded:: 0.0.5 + """ + for app in self: + app.terminate()
+ +
[docs] def quit(self): + """Quits (terminates) all applications in the list. Synonymous with :func:`terminate`. + + :Example 1: Quit all visible running applications + + >>> import PyXA + >>> apps = PyXA.running_applications() + >>> apps.quit() + + .. versionadded:: 0.0.5 + """ + for app in self: + app.terminate()
+ +
[docs] def windows(self) -> 'XACombinedWindowList': + """Retrieves a list of every window belonging to each application in the list. + + Operations on the list of windows will specialized to scriptable and non-scriptable application window operations as necessary. + + :return: A list containing both scriptable and non-scriptable windows + :rtype: XACombinedWindowList + + :Example: + + >>> import PyXA + >>> windows = PyXA.running_applications().windows() + >>> windows.collapse() + >>> sleep(1) + >>> windows.uncollapse() + + .. versionadded:: 0.0.5 + """ + ls = [] + for app in self: + ls.extend(app.windows().xa_elem) + ls = AppKit.NSArray.alloc().initWithArray_(ls) + window_list = self._new_element(ls, XACombinedWindowList) + return window_list
+ + def __iter__(self): + return (Application(object["kCGWindowOwnerName"]) for object in self.xa_elem.objectEnumerator()) + + def __repr__(self): + return "<" + str(type(self)) + str(self.localized_name()) + ">"
+ +
[docs]class Application(XAObject): + shared_app = AppKit.NSApplication.sharedApplication() + workspace = AppKit.NSWorkspace.sharedWorkspace() + app_paths: List[str] = [] #: A list containing the path to each application + + def __init__(self, app_name: str): + """Creates a new application object. + + :param app_name: The name of the target application + :type app_name: str + + .. versionadded:: 0.1.0 + """ + # Elevate to XAApplication + new_self = self.__get_application(app_name) + self.__class__ = new_self.__class__ + self.__dict__.update(new_self.__dict__) + + def __xa_get_path_to_app(self, app_identifier: str) -> str: + self.__xa_load_app_paths() + for path in self.app_paths: + if app_identifier.lower() in path.lower(): + return path + + raise ApplicationNotFoundError(app_identifier) + + def __xa_load_app_paths(self): + if self.app_paths == []: + search = XASpotlight() + search.predicate = "kMDItemContentType == 'com.apple.application-bundle'" + search.run() + self.app_paths = [x.path for x in search.results] + + def __get_application(self, app_identifier: str) -> XAApplication: + """Retrieves a PyXA application object representation of the target application without launching or activating the application. + + :param app_identifier: The name of the application to get an object of. + :type app_identifier: str + :return: A PyXA application object referencing the target application. + :rtype: XAApplication + + .. versionadded:: 0.0.1 + """ + app_identifier_l = app_identifier.lower() + + def _match_open_app(obj, index, stop): + return (obj.localizedName().lower() == app_identifier_l, stop) + + idx_set = self.workspace.runningApplications().indexesOfObjectsPassingTest_(_match_open_app) + if idx_set.count() == 1: + index = idx_set.firstIndex() + app = self.workspace.runningApplications()[index] + properties = { + "parent": None, + "appspace": self.shared_app, + "workspace": self.workspace, + "element": app, + "appref": app, + } + + app_obj = application_classes.get(app_identifier_l, XAApplication) + if isinstance(app_obj, tuple): + module = importlib.import_module("PyXA.apps." + app_obj[0]) + app_class = getattr(module, app_obj[1], None) + if app_class is not None: + application_classes[app_identifier_l] = app_class + app = app_class + else: + raise NotImplementedError() + + # Check if the app is supported by PyXA + app_ref = application_classes.get(app_identifier_l, XAApplication)(properties) + return app_ref + + app_path = app_identifier + if not app_identifier.startswith("/"): + app_path = self.__xa_get_path_to_app(app_identifier) + bundle = AppKit.NSBundle.alloc().initWithPath_(app_path) + url = self.workspace.URLForApplicationWithBundleIdentifier_(bundle.bundleIdentifier()) + + config = AppKit.NSWorkspaceOpenConfiguration.alloc().init() + config.setActivates_(False) + config.setHides_(True) + + app_ref = None + def _launch_completion_handler(app, _error): + nonlocal app_ref + properties = { + "parent": None, + "appspace": self.shared_app, + "workspace": self.workspace, + "element": app, + "appref": app, + } + + app_obj = application_classes.get(app_identifier_l, None) + if isinstance(app_obj, tuple): + module = importlib.import_module("PyXA.apps." + app_obj[0]) + app_class = getattr(module, app_obj[1], None) + if app_class is not None: + application_classes[app_identifier_l] = app_class + app = app_class + else: + raise NotImplementedError() + + app_ref = application_classes.get(app_identifier_l, XAApplication)(properties) + + self.workspace.openApplicationAtURL_configuration_completionHandler_(url, config, _launch_completion_handler) + while app_ref is None: + sleep(0.01) + return app_ref
+ + + + +
[docs]class XACombinedWindowList(XAList): + """A wrapper around a combined list of both scriptable and non-scriptable windows. + + This class contains methods that specialize to XAWindow and XASBWindow methods as necessary. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAWindow, filter) + +
[docs] def collapse(self) -> 'XACombinedWindowList': + """Collapses all windows in the list. + + :return: The window list object + :rtype: XACombinedWindowList + + :Example 1: Collapse all windows for all currently visible running applications + + >>> import PyXA + >>> apps = PyXA.running_applications() + >>> apps.windows().collapse() + + .. versionadded:: 0.0.5 + """ + for window in self: + if not hasattr(window.xa_elem, "buttons"): + # Specialize to XASBWindow + window = self.xa_prnt._new_element(window.xa_elem, XABaseScriptable.XASBWindow) + window.collapse() + return self
+ +
[docs] def uncollapse(self) -> 'XACombinedWindowList': + """Uncollapses all windows in the list. + + :return: The window list object + :rtype: XACombinedWindowList + + .. versionadded:: 0.1.0 + """ + for window in self: + if not hasattr(window.xa_elem, "buttons"): + # Specialize to XASBWindow + window = self.xa_prnt._new_element(window.xa_elem, XABaseScriptable.XASBWindow) + window.uncollapse() + return self
+ + + + +
[docs]def running_applications() -> List[XAApplication]: + """Gets PyXA references to all currently visible (not hidden or minimized) running applications whose app bundles are stored in typical application directories. + + :return: A list of PyXA application objects. + :rtype: List[XAApplication] + + :Example 1: Get the name of each running application + + >>> import PyXA + >>> apps = PyXA.running_applications() + >>> print(apps.localized_name()) + ['GitHub Desktop', 'Safari', 'Code', 'Terminal', 'Notes', 'Messages', 'TV'] + + .. versionadded:: 0.0.1 + """ + windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID) + ls = XAPredicate.evaluate_with_format(windows, "kCGWindowIsOnscreen == 1 && kCGWindowLayer == 0") + properties = { + "appspace": AppKit.NSApplication.sharedApplication(), + "workspace": AppKit.NSWorkspace.sharedWorkspace(), + "element": ls, + } + arr = XAApplicationList(properties) + return arr
+ + + + +
[docs]def current_application() -> XAApplication: + """Retrieves a PyXA representation of the frontmost application. + + :return: A PyXA application object referencing the current application. + :rtype: XAApplication + + .. versionadded:: 0.0.1 + """ + return Application(AppKit.NSWorkspace.sharedWorkspace().frontmostApplication().localizedName())
+ + + + +
[docs]def application(app_identifier: str) -> XAApplication: + """Retrieves a PyXA application object representation of the target application without launching or activating the application. + + :param app_identifier: The name of the application to get an object of. + :type app_identifier: str + :return: A PyXA application object referencing the target application. + :rtype: XAApplication + + .. deprecated:: 0.1.0 + + Use :class:`Application` instead. + + .. versionadded:: 0.0.1 + """ + return Application(app_identifier)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/PyXA.html b/docs/_modules/PyXA/PyXA.html index 0b076cd..ee03f46 100644 --- a/docs/_modules/PyXA/PyXA.html +++ b/docs/_modules/PyXA/PyXA.html @@ -3,7 +3,7 @@ - PyXA.PyXA — PyXA 0.0.9 documentation + PyXA.PyXA — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -69,185 +71,22 @@

Source code for PyXA.PyXA

 from datetime import datetime
-from enum import Enum
-import os
 from time import sleep
 from typing import Any, Callable, List, Union
 import importlib
 
 import AppKit
-from AppKit import (
-    NSWorkspace,
-    NSApplication,
-    NSPasteboard,
-    NSArray,
-    NSPasteboardTypeString,
-    NSAppleScript,
-)
-from Foundation import NSURL, NSBundle
-
 from Quartz import CGWindowListCopyWindowInfo, kCGNullWindowID, kCGWindowListOptionAll
 
 from .XABase import *
-from .XABaseScriptable import *
-from .XAErrors import ApplicationNotFoundError
-from .apps import application_classes
 from PyXA import XABaseScriptable
+from .apps import application_classes
 
-VERSION = "0.0.9"
-DATE = str(datetime.now())
-
-appspace = NSApplication.sharedApplication()
-workspace = NSWorkspace.sharedWorkspace()
-app_paths: List[str] = []
-
-scriptable_applications: List[str] = list(application_classes.keys()) #: A list of names of scriptable applications
-
-# class _staticproperty(staticmethod):
-#     def __get__(self, *_):         
-#         return self.__func__()
-
-# class Application(XAObject):
-#     shared_app = NSApplication.sharedApplication()
-#     workspace = NSWorkspace.sharedWorkspace()
-#     app_paths: List[str] = [] #: A list containing the path to each application
-#     current_application: XAApplication #: The currently active application
-
-#     def __init__(self, app_identifier: str):
-#         """Creates an application object.
-
-#         :param app_identifier: The name of an application
-#         :type app_identifier: str
-
-#         .. versionadded:: 0.0.9
-#         """
-#         self.app = 
-
-#     def _get_path_to_app(app_identifier: str) -> str:
-#         Application._xa_load_app_paths()
-#         for path in app_paths:
-#             if app_identifier in path:
-#                 return path
-
-#         raise ApplicationNotFoundError(app_identifier)
-
-#     def _xa_load_app_paths():
-#         if Application.app_paths == []:
-#             search = XASpotlight()
-#             search.predicate = "kMDItemContentType == 'com.apple.application-bundle'"
-#             search.run()
-#             app_paths = [x.path for x in search.results]
-
-#     @_staticproperty
-#     def current_application() -> XAApplication:
-#         return application(workspace.frontmostApplication().localizedName())
-
-#     def _xa_get_open_app(app_identifier: str):
-#         def _match_open_app(obj, index, stop):
-#             return (obj.localizedName() == app_identifier, stop)
-
-#         idx_set = workspace.runningApplications().indexesOfObjectsPassingTest_(_match_open_app)
-#         if idx_set.count() == 1:
-#             index = idx_set.firstIndex()
-#             app = workspace.runningApplications()[index]
-#             return Application._xa_get_app_ref(app_identifier.lower(), app)
-
-#     def _xa_get_app_ref(app_identifier: str, app_object):
-#         properties = {
-#             "parent": None,
-#             "appspace": Application.shared_app,
-#             "workspace": Application.workspace,
-#             "element": app_object,
-#             "appref": app_object,
-#         }
-
-#         app_obj = application_classes.get(app_identifier, XAApplication)
-#         if isinstance(app_obj, tuple):
-#             module = importlib.import_module("PyXA.apps." + app_obj[0])
-#             app_class = getattr(module, app_obj[1], None)
-#             if app_class is not None:
-#                 application_classes[app_identifier] = app_class
-#                 app = app_class
-#             else:
-#                 raise NotImplementedError()
-
-#         return application_classes.get(app_identifier, XAApplication)(properties)
-
-#     def _xa_get_application(app_identifier: str) -> XAApplication:
-#         """Retrieves a PyXA application object representation of the target application without launching or activating the application.
-
-#         :param app_identifier: The name of the application to get an object of.
-#         :type app_identifier: str
-#         :return: A PyXA application object referencing the target application.
-#         :rtype: XAApplication
-
-#         .. versionadded:: 0.0.9
-#         """
-#         open_app = Application._xa_get_open_app(app_identifier)
-#         if open_app is not None:
-#             return open_app
-
-#         app_path = _get_path_to_app(app_identifier)
-#         bundle = NSBundle.alloc().initWithPath_(app_path)
-#         url = workspace.URLForApplicationWithBundleIdentifier_(bundle.bundleIdentifier())
-
-#         config = AppKit.NSWorkspaceOpenConfiguration.alloc().init()
-#         config.setActivates_(False)
-#         config.setHides_(True)
-
-#         app_ref = None
-#         def _launch_completion_handler(app, _error):
-#             nonlocal app_ref
-#             app_ref = Application._xa_get_app_ref(app_identifier.lower(), app)
-
-#         workspace.openApplicationAtURL_configuration_completionHandler_(url, config, _launch_completion_handler)
-#         while app_ref is None:
-#             sleep(0.01)
-#         return app_ref
-
-
-def _xa_get_path_to_app(app_identifier: str) -> str:
-    _xa_load_app_paths()
-    for path in app_paths:
-        if app_identifier in path:
-            return path
-
-    raise ApplicationNotFoundError(app_identifier)
-
-def _xa_load_app_paths():
-    global app_paths
-    if app_paths == []:
-        search = XASpotlight()
-        search.predicate = "kMDItemContentType == 'com.apple.application-bundle'"
-        search.run()
-        app_paths = [x.path for x in search.results]
-
-
-
[docs]def running_applications() -> List[XAApplication]: - """Gets PyXA references to all currently visible (not hidden or minimized) running applications whose app bundles are stored in typical application directories. - - :return: A list of PyXA application objects. - :rtype: List[XAApplication] +from .XAErrors import ApplicationNotFoundError - :Example 1: Get the name of each running application - - >>> import PyXA - >>> apps = PyXA.running_applications() - >>> print(apps.localized_name()) - ['GitHub Desktop', 'Safari', 'Code', 'Terminal', 'Notes', 'Messages', 'TV'] - - .. versionadded:: 0.0.1 - """ - windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID) - ls = XAPredicate.evaluate_with_format(windows, "kCGWindowIsOnscreen == 1 && kCGWindowLayer == 0") - properties = { - "appspace": appspace, - "workspace": workspace, - "element": ls, - } - arr = XAApplicationList(properties) - return arr
+VERSION = "0.1.0" #: The installed version of PyXA +supported_applications: List[str] = list(application_classes.keys()) #: A list of names of supported scriptable applications
[docs]class XAApplicationList(XAList): """A wrapper around a list of applications. @@ -288,7 +127,7 @@

Source code for PyXA.PyXA

         removed = self.xa_elem.lastObject()
         self.xa_elem.removeLastObject()
         app_name = removed["kCGWindowOwnerName"]
-        return application(app_name)
+ return Application(app_name)
def __getitem__(self, key: Union[int, slice]): """Retrieves the wrapped application object(s) at the specified key.""" @@ -296,24 +135,66 @@

Source code for PyXA.PyXA

             arr = AppKit.NSArray.alloc().initWithArray_([self.xa_elem[index] for index in range(key.start, key.stop, key.step or 1)])
             return self._new_element(arr, self.__class__)
         app_name = self.xa_elem[key]["kCGWindowOwnerName"]
-        return application(app_name)
+        return Application(app_name)
 
 
[docs] def bundle_identifier(self) -> List[str]: + """Gets the bundle identifier of every application in the list. + + :return: The list of application bundle identifiers + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ return [app.bundle_identifier for app in self]
-
[docs] def bundle_url(self) -> List[str]: - return [app.bundle_url for app in self]
+
[docs] def bundle_url(self) -> List[XAURL]: + """Gets the bundle URL of every application in the list. + + :return: The list of application bundle URLs + :rtype: List[XAURL] -
[docs] def executable_url(self) -> List[str]: - return [app.executable_url for app in self]
+ .. versionadded:: 0.0.5 + """ + return [XAURL(app.bundle_url)for app in self]
+ +
[docs] def executable_url(self) -> List[XAURL]: + """Gets the executable URL of every application in the list. + + :return: The list of application executable URLs + :rtype: List[XAURL] + + .. versionadded:: 0.0.5 + """ + return [XAURL(app.executable_url) for app in self]
[docs] def launch_date(self) -> List[datetime]: + """Gets the launch date of every application in the list. + + :return: The list of application launch dates + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ return [app.launch_date for app in self]
[docs] def localized_name(self) -> List[str]: + """Gets the localized name of every application in the list. + + :return: The list of application localized names + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ return [x.get("kCGWindowOwnerName") for x in self.xa_elem]
[docs] def process_identifier(self) -> List[str]: + """Gets the process identifier of every application in the list. + + :return: The list of application process identifiers + :rtype: List[str] + + .. versionadded:: 0.0.5 + """ return [x.get("kCGWindowOwnerPID") for x in self.xa_elem]
[docs] def hide(self): @@ -385,6 +266,14 @@

Source code for PyXA.PyXA

         :return: A list containing both scriptable and non-scriptable windows
         :rtype: XACombinedWindowList
 
+        :Example:
+
+        >>> import PyXA
+        >>> windows = PyXA.running_applications().windows()
+        >>> windows.collapse()
+        >>> sleep(1)
+        >>> windows.uncollapse()
+
         .. versionadded:: 0.0.5
         """
         ls = []
@@ -395,11 +284,140 @@ 

Source code for PyXA.PyXA

         return window_list
def __iter__(self): - return (application(object["kCGWindowOwnerName"]) for object in self.xa_elem.objectEnumerator()) + return (Application(object["kCGWindowOwnerName"]) for object in self.xa_elem.objectEnumerator()) + + def __contains__(self, item): + if isinstance(item, XAApplication): + return item.process_identifier in self.process_identifier() def __repr__(self): return "<" + str(type(self)) + str(self.localized_name()) + ">"
+
[docs]class Application(XAObject): + _shared_app = None + _workspace = None + app_paths: List[str] = [] #: A list containing the path to each application + + def __init__(self, app_name: str): + """Creates a new application object. + + :param app_name: The name of the target application + :type app_name: str + + .. versionadded:: 0.1.0 + """ + # Elevate to XAApplication + new_self = self.__get_application(app_name) + self.__class__ = new_self.__class__ + self.__dict__.update(new_self.__dict__) + + @property + def shared_app(self): + if Application._shared_app == None: + Application._shared_app = AppKit.NSApplication.sharedApplication() + yield Application._shared_app + + @property + def workspace(self): + if Application._workspace == None: + Application._workspace = AppKit.NSWorkspace.sharedWorkspace() + return Application._workspace + + def __xa_get_path_to_app(self, app_identifier: str) -> str: + self.__xa_load_app_paths() + for path in self.app_paths: + if app_identifier.lower() in path.lower(): + return path + + raise ApplicationNotFoundError(app_identifier) + + def __xa_load_app_paths(self): + if self.app_paths == []: + search = XASpotlight() + search.predicate = "kMDItemContentType == 'com.apple.application-bundle'" + search.run() + self.app_paths = [x.path for x in search.results] + + def __get_application(self, app_identifier: str) -> XAApplication: + """Retrieves a PyXA application object representation of the target application without launching or activating the application. + + :param app_identifier: The name of the application to get an object of. + :type app_identifier: str + :return: A PyXA application object referencing the target application. + :rtype: XAApplication + + .. versionadded:: 0.0.1 + """ + app_identifier_l = app_identifier.lower() + + def _match_open_app(obj, index, stop): + res = obj.localizedName().lower() == app_identifier_l + return res, res + + idx_set = self.workspace.runningApplications().indexesOfObjectsPassingTest_(_match_open_app) + if idx_set.count() == 1: + index = idx_set.firstIndex() + app = self.workspace.runningApplications()[index] + properties = { + "parent": None, + "appspace": self.shared_app, + "workspace": self.workspace, + "element": app, + "appref": app, + } + + app_obj = application_classes.get(app_identifier_l, XAApplication) + if isinstance(app_obj, tuple): + module = importlib.import_module("PyXA.apps." + app_obj[0]) + app_class = getattr(module, app_obj[1], None) + if app_class is not None: + application_classes[app_identifier_l] = app_class + app = app_class + else: + raise NotImplementedError() + + # Check if the app is supported by PyXA + app_ref = application_classes.get(app_identifier_l, XAApplication)(properties) + return app_ref + + app_path = app_identifier + if not app_identifier.startswith("/"): + app_path = self.__xa_get_path_to_app(app_identifier) + bundle = AppKit.NSBundle.alloc().initWithPath_(app_path) + url = self.workspace.URLForApplicationWithBundleIdentifier_(bundle.bundleIdentifier()) + + config = AppKit.NSWorkspaceOpenConfiguration.alloc().init() + config.setActivates_(False) + config.setHides_(True) + + app_ref = None + def _launch_completion_handler(app, _error): + nonlocal app_ref + properties = { + "parent": None, + "appspace": self.shared_app, + "workspace": self.workspace, + "element": app, + "appref": app, + } + + app_obj = application_classes.get(app_identifier_l, None) + if isinstance(app_obj, tuple): + module = importlib.import_module("PyXA.apps." + app_obj[0]) + app_class = getattr(module, app_obj[1], None) + if app_class is not None: + application_classes[app_identifier_l] = app_class + app = app_class + else: + raise NotImplementedError() + + app_ref = application_classes.get(app_identifier_l, XAApplication)(properties) + + self.workspace.openApplicationAtURL_configuration_completionHandler_(url, config, _launch_completion_handler) + while app_ref is None: + sleep(0.01) + return app_ref
+ @@ -413,9 +431,12 @@

Source code for PyXA.PyXA

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAWindow, filter)
 
-
[docs] def collapse(self): +
[docs] def collapse(self) -> 'XACombinedWindowList': """Collapses all windows in the list. + :return: The window list object + :rtype: XACombinedWindowList + :Example 1: Collapse all windows for all currently visible running applications >>> import PyXA @@ -428,7 +449,53 @@

Source code for PyXA.PyXA

             if not hasattr(window.xa_elem, "buttons"):
                 # Specialize to XASBWindow
                 window = self.xa_prnt._new_element(window.xa_elem, XABaseScriptable.XASBWindow)
-            window.collapse()
+ window.collapse() + return self
+ +
[docs] def uncollapse(self) -> 'XACombinedWindowList': + """Uncollapses all windows in the list. + + :return: The window list object + :rtype: XACombinedWindowList + + .. versionadded:: 0.1.0 + """ + for window in self: + if not hasattr(window.xa_elem, "buttons"): + # Specialize to XASBWindow + window = self.xa_prnt._new_element(window.xa_elem, XABaseScriptable.XASBWindow) + window.uncollapse() + return self
+ + + + +
[docs]def running_applications() -> List[XAApplication]: + """Gets PyXA references to all currently visible (not hidden or minimized) running applications whose app bundles are stored in typical application directories. + + :return: A list of PyXA application objects. + :rtype: List[XAApplication] + + :Example 1: Get the name of each running application + + >>> import PyXA + >>> apps = PyXA.running_applications() + >>> print(apps.localized_name()) + ['GitHub Desktop', 'Safari', 'Code', 'Terminal', 'Notes', 'Messages', 'TV'] + + .. versionadded:: 0.0.1 + """ + windows = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID) + ls = XAPredicate.evaluate_with_format(windows, "kCGWindowIsOnscreen == 1 && kCGWindowLayer == 0") + properties = { + "appspace": AppKit.NSApplication.sharedApplication(), + "workspace": AppKit.NSWorkspace.sharedWorkspace(), + "element": ls, + } + arr = XAApplicationList(properties) + return arr
+ +
[docs]def current_application() -> XAApplication: @@ -439,7 +506,9 @@

Source code for PyXA.PyXA

 
     .. versionadded:: 0.0.1
     """
-    return application(workspace.frontmostApplication().localizedName())
+ return Application(AppKit.NSWorkspace.sharedWorkspace().frontmostApplication().localizedName())
+ +
[docs]def application(app_identifier: str) -> XAApplication: @@ -450,74 +519,13 @@

Source code for PyXA.PyXA

     :return: A PyXA application object referencing the target application.
     :rtype: XAApplication
 
+    .. deprecated:: 0.1.0
+    
+       Use :class:`Application` instead.
+
     .. versionadded:: 0.0.1
     """
-    app_identifier_l = app_identifier.lower()
-
-    def _match_open_app(obj, index, stop):
-        return (obj.localizedName() == app_identifier, stop)
-
-    idx_set = workspace.runningApplications().indexesOfObjectsPassingTest_(_match_open_app)
-    if idx_set.count() == 1:
-        index = idx_set.firstIndex()
-        app = workspace.runningApplications()[index]
-        properties = {
-            "parent": None,
-            "appspace": appspace,
-            "workspace": workspace,
-            "element": app,
-            "appref": app,
-        }
-
-        app_obj = application_classes.get(app_identifier_l, XAApplication)
-        if isinstance(app_obj, tuple):
-            module = importlib.import_module("PyXA.apps." + app_obj[0])
-            app_class = getattr(module, app_obj[1], None)
-            if app_class is not None:
-                application_classes[app_identifier_l] = app_class
-                app = app_class
-            else:
-                raise NotImplementedError()
-
-        app_ref = application_classes.get(app_identifier_l, XAApplication)(properties)
-        return app_ref
-
-    app_path = _xa_get_path_to_app(app_identifier)
-    bundle = NSBundle.alloc().initWithPath_(app_path)
-    url = workspace.URLForApplicationWithBundleIdentifier_(bundle.bundleIdentifier())
-
-    config = AppKit.NSWorkspaceOpenConfiguration.alloc().init()
-    config.setActivates_(False)
-    config.setHides_(True)
-
-    app_ref = None
-    def _launch_completion_handler(app, _error):
-        nonlocal app_ref
-        properties = {
-            "parent": None,
-            "appspace": appspace,
-            "workspace": workspace,
-            "element": app,
-            "appref": app,
-        }
-
-        app_obj = application_classes.get(app_identifier_l, None)
-        if isinstance(app_obj, tuple):
-            module = importlib.import_module("PyXA.apps." + app_obj[0])
-            app_class = getattr(module, app_obj[1], None)
-            if app_class is not None:
-                application_classes[app_identifier_l] = app_class
-                app = app_class
-            else:
-                raise NotImplementedError()
-
-        app_ref = application_classes.get(app_identifier_l, XAApplication)(properties)
-
-    
-    workspace.openApplicationAtURL_configuration_completionHandler_(url, config, _launch_completion_handler)
-    while app_ref is None:
-        sleep(0.01)
-    return app_ref
+ return Application(app_identifier)
diff --git a/docs/_modules/PyXA/XABase 2.html b/docs/_modules/PyXA/XABase 2.html new file mode 100644 index 0000000..2c6d99d --- /dev/null +++ b/docs/_modules/PyXA/XABase 2.html @@ -0,0 +1,10232 @@ + + + + + + PyXA.XABase — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.XABase

+""".. versionadded:: 0.0.1
+
+General classes and methods applicable to any PyXA object.
+"""
+
+from datetime import datetime, timedelta
+from enum import Enum
+import importlib
+import math
+from pprint import pprint
+import random
+import tempfile
+import time, os, sys
+from typing import Any, Callable, Literal, Union, Self
+import threading
+from bs4 import BeautifulSoup, element
+import requests
+import subprocess
+import objc
+import xml.etree.ElementTree as ET
+
+from PyXA.apps import application_classes
+
+from PyObjCTools import AppHelper
+import appscript
+
+import Foundation
+import CoreMedia
+import AppKit
+import NaturalLanguage
+import LatentSemanticMapping
+from Quartz import CGImageSourceRef, CGImageSourceCreateWithData, CFDataRef, CGRectMake
+from CoreLocation import CLLocation
+from ScriptingBridge import SBApplication, SBElementArray
+import ScriptingBridge
+import Speech
+import AVFoundation
+import CoreLocation
+import Quartz
+import Vision
+
+import threading
+
+from PyXA.XAErrors import InvalidPredicateError
+from .XAProtocols import XACanOpenPath, XAClipboardCodable, XAPathLike
+
+
[docs]def OSType(s: str): + return int.from_bytes(s.encode("UTF-8"), "big")
+ +
[docs]def unOSType(i: int): + return i.to_bytes((i.bit_length() + 7) // 8, 'big').decode()
+ + +PYXA_VERSION = "0.1.0" + + +############### +### General ### +############### +# XAObject, XAApplication +
[docs]class XAObject(): + """A general class for PyXA scripting objects. + + .. seealso:: :class:`XABaseScriptable.XASBObject` + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties: dict = None): + """Instantiates a PyXA scripting object. + + :param properties: A dictionary of properties to assign to this object. + :type properties: dict, optional + + .. versionchanged:: 0.0.3 + Removed on-the-fly creation of class attributes. All objects should concretely define their properties. + + .. versionadded:: 0.0.1 + """ + if properties is not None: + self.xa_prnt = properties.get("parent", None) + self.xa_apsp = properties.get("appspace", None) + self.xa_wksp = properties.get("workspace", None) + self.xa_elem = properties.get("element", None) + self.xa_scel = properties.get("scriptable_element", None) + self.xa_aref = properties.get("appref", None) + self.xa_sevt = properties.get("system_events", SBApplication.alloc().initWithBundleIdentifier_("com.apple.systemevents")) + + self.properties: dict #: The scriptable properties dictionary for the object + + def _exec_suppresed(self, f: Callable[..., Any], *args: Any) -> Any: + """Silences unwanted and otherwise unavoidable warning messages. + + Taken from: https://stackoverflow.com/a/3946828 + + :param f: The function to execute + :type f: Callable[...] + :param args: The parameters to pass to the specified function + :type args: Any + :raises error: Any exception that occurs while trying to run the specified function + :return: The value returned by the specified function upon execution + :rtype: Any + + .. versionadded:: 0.0.2 + """ + error = None + value = None + + old_stderr = os.dup(sys.stderr.fileno()) + fd = os.open('/dev/null', os.O_CREAT | os.O_WRONLY) + os.dup2(fd, sys.stderr.fileno()) + try: + value = f(*args) + except Exception as e: + error = e + os.dup2(old_stderr, sys.stderr.fileno()) + + if error is not None: + raise error + return value + + def _new_element(self, obj: AppKit.NSObject, obj_class: type = 'XAObject', *args: list[Any]) -> 'XAObject': + """Wrapper for creating a new PyXA object. + + :param folder_obj: The Objective-C representation of an object. + :type folder_obj: NSObject + :return: The PyXA representation of the object. + :rtype: XAObject + + .. versionadded:: 0.0.1 + """ + properties = { + "parent": self, + "appspace": getattr(self, "xa_apsp", None), + "workspace": getattr(self, "xa_wksp", None), + "element": obj, + "appref": getattr(self, "xa_aref", None), + "system_events": getattr(self, "xa_sevt", None), + } + return obj_class(properties, *args) + + def _spawn_thread(self, function: Callable[..., Any], args: Union[list[Any], None] = None, kwargs: Union[list[Any], None] = None, daemon: bool = True) -> threading.Thread: + """Spawns a new thread running the specified function. + + :param function: The function to run in the new thread + :type function: Callable[..., Any] + :param args: Arguments to pass to the function + :type args: list[Any] + :param kwargs: Keyword arguments to pass to the function + :type kwargs: list[Any] + :param daemon: Whether the thread should be a daemon thread, defaults to True + :type daemon: bool, optional + :return: The thread object + :rtype: threading.Thread + + .. versionadded:: 0.0.9 + """ + new_thread = threading.Thread(target=function, args=args or [], kwargs=kwargs or {}, daemon=daemon) + new_thread.start() + return new_thread + +
[docs] def has_element(self) -> bool: + """Whether this object has an AppleScript/JXA/Objective-C scripting element associated with it. + + :return: True if this object's element attribute is set, False otherwise. + :rtype: bool + + .. deprecated:: 0.0.9 + + Perform this check manually instead. + + .. versionadded:: 0.0.1 + """ + return self.xa_elem is not None
+ +
[docs] def has_element_properties(self) -> bool: + """Whether the scripting element associated with this object has properties attached to it. + + :return: True if this object's properties attribute is set, False otherwise. + :rtype: bool + + .. deprecated:: 0.0.8 + All elements now have a properties dictionary, even if it is empty. + + .. versionadded:: 0.0.1 + """ + return self.element_properties != None
+ +
[docs] def set_element(self, element: 'XAObject') -> 'XAObject': + """Sets the element attribute to the supplied element and updates the properties attribute accordingly. + + :param element: The new scripting element to reference via the element attribute. + :type element: XAObject + :return: A reference to this PyXA object. + :rtype: XAObject + + .. deprecated:: 0.0.9 + + Set the element attribute directly instead. + + .. versionadded:: 0.0.1 + """ + self.xa_elem = element + return self
+ +
[docs] def set_properties(self, properties: dict) -> 'XAObject': + """Updates the value of multiple properties of the scripting element associated with this object. + + :param properties: A dictionary defining zero or more property names and updated values as key-value pairs. + :type properties: dict + :return: A reference to this PyXA object. + :rtype: XAObject + + .. versionadded:: 0.0.1 + """ + property_dict = {} + for key in properties: + parts = key.split("_") + titled_parts = [part.title() for part in parts[1:]] + property_name = parts[0] + "".join(titled_parts) + property_dict[property_name] = properties[key] + self.xa_elem.setValuesForKeysWithDictionary_(property_dict) + return self
+ +
[docs] def set_property(self, property_name: str, value: Any) -> 'XAObject': + """Updates the value of a single property of the scripting element associated with this object. + + :param property: The name of the property to assign a new value to. + :type property: str + :param value: The value to assign to the specified property. + :type value: Any + :return: A reference to this PyXA object. + :rtype: XAObject + + .. versionadded:: 0.0.1 + """ + parts = property_name.split("_") + titled_parts = [part.title() for part in parts[1:]] + property_name = parts[0] + "".join(titled_parts) + self.xa_elem.setValue_forKey_(value, property_name) + return self
+ +
[docs] def set_scriptable_property(self, property_name: str, value: Any) -> 'XAObject': + """Updates the value of a single scriptable element property of the scripting element associated with this object. + + :param property: The name of the property to assign a new value to. + :type property: str + :param value: The value to assign to the specified property. + :type value: Any + :return: A reference to this PyXA object. + :rtype: XAObject + + .. versionadded:: 0.1.0 + """ + parts = property_name.split("_") + titled_parts = [part.title() for part in parts[1:]] + property_name = parts[0] + "".join(titled_parts) + self.xa_scel.setValue_forKey_(value, property_name) + return self
+ + + + +
[docs]class XAApplication(XAObject, XAClipboardCodable): + """A general application class for both officially scriptable and non-scriptable applications. + + .. seealso:: :class:`XASBApplication`, :class:`XAWindow` + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XAWindow + + predicate = AppKit.NSPredicate.predicateWithFormat_("displayedName == %@", self.xa_elem.localizedName()) + process = self.xa_sevt.processes().filteredArrayUsingPredicate_(predicate)[0] + + properties = { + "parent": self, + "appspace": self.xa_apsp, + "workspace": self.xa_wksp, + "element": process, + "appref": self.xa_aref, + "system_events": self.xa_sevt, + "window_class": self.xa_wcls + } + self.xa_prcs = XAProcess(properties) + + self.bundle_identifier: str #: The bundle identifier for the application + self.bundle_url: str #: The file URL of the application bundle + self.executable_url: str #: The file URL of the application's executable + self.frontmost: bool #: Whether the application is the active application + self.launch_date: datetime #: The date and time that the application was launched + self.localized_name: str #: The application's name + self.owns_menu_bar: bool #: Whether the application owns the top menu bar + self.process_identifier: str #: The process identifier for the application instance + + self.xa_apsc: appscript.GenericApp + + def __getattr__(self, attr): + if attr in self.__dict__: + # If possible, use PyXA attribute + return super().__getattribute__(attr) + else: + # Otherwise, fall back to appscript + return getattr(self.xa_apsc, attr) + + @property + def xa_apsc(self) -> appscript.GenericApp: + return appscript.app(self.bundle_url.path()) + + @property + def bundle_identifier(self) -> str: + return self.xa_elem.bundleIdentifier() + + @property + def bundle_url(self) -> str: + return self.xa_elem.bundleURL() + + @property + def executable_url(self) -> str: + return self.xa_elem.executableURL() + + @property + def frontmost(self) -> bool: + return self.xa_elem.isActive() + + @frontmost.setter + def frontmost(self, frontmost: bool): + if frontmost is True: + self.xa_elem.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps) + + @property + def launch_date(self) -> datetime: + return self.xa_elem.launchDate() + + @property + def localized_name(self) -> str: + return self.xa_elem.localizedName() + + @property + def owns_menu_bar(self) -> bool: + return self.xa_elem.ownsMenuBar() + + @property + def process_identifier(self) -> str: + return self.xa_elem.processIdentifier() + +
[docs] def activate(self) -> 'XAApplication': + """Activates the application, bringing its window(s) to the front and launching the application beforehand if necessary. + + :return: A reference to the PyXA application object. + :rtype: XAApplication + + .. seealso:: :func:`terminate`, :func:`unhide`, :func:`focus` + + .. versionadded:: 0.0.1 + """ + self.xa_elem.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps) + return self
+ +
[docs] def terminate(self) -> 'XAApplication': + """Quits the application. Synonymous with quit(). + + :return: A reference to the PyXA application object. + :rtype: XAApplication + + :Example: + + >>> import PyXA + >>> safari = PyXA.application("Safari") + >>> safari.terminate() + + .. seealso:: :func:`quit`, :func:`activate` + + .. versionadded:: 0.0.1 + """ + self.xa_elem.terminate() + return self
+ +
[docs] def quit(self) -> 'XAApplication': + """Quits the application. Synonymous with terminate(). + + :return: A reference to the PyXA application object. + :rtype: XAApplication + + :Example: + + >>> import PyXA + >>> safari = PyXA.application("Safari") + >>> safari.quit() + + .. seealso:: :func:`terminate`, :func:`activate` + + .. versionadded:: 0.0.1 + """ + self.xa_elem.terminate() + return self
+ +
[docs] def hide(self) -> 'XAApplication': + """Hides all windows of the application. + + :return: A reference to the PyXA application object. + :rtype: XAApplication + + :Example: + + >>> import PyXA + >>> safari = PyXA.application("Safari") + >>> safari.hide() + + .. seealso:: :func:`unhide` + + .. versionadded:: 0.0.1 + """ + self.xa_elem.hide() + return self
+ +
[docs] def unhide(self) -> 'XAApplication': + """Unhides (reveals) all windows of the application, but does not does not activate them. + + :return: A reference to the PyXA application object. + :rtype: XAApplication + + :Example: + + >>> import PyXA + >>> safari = PyXA.application("Safari") + >>> safari.unhide() + + .. seealso:: :func:`hide` + + .. versionadded:: 0.0.1 + """ + self.xa_elem.unhide() + return self
+ +
[docs] def focus(self) -> 'XAApplication': + """Hides the windows of all applications except this one. + + :return: A reference to the PyXA application object. + :rtype: XAApplication + + :Example: + + >>> import PyXA + >>> safari = PyXA.application("Safari") + >>> safari.focus() + + .. seealso:: :func:`unfocus` + + .. versionadded:: 0.0.1 + """ + for app in self.xa_wksp.runningApplications(): + if app.localizedName() != self.xa_elem.localizedName(): + app.hide() + else: + app.unhide() + return self
+ +
[docs] def unfocus(self) -> 'XAApplication': + """Unhides (reveals) the windows of all other applications, but does not activate them. + + :return: A reference to the PyXA application object. + :rtype: XAApplication + + :Example: + + >>> import PyXA + >>> safari = PyXA.application("Safari") + >>> safari.unfocus() + + .. seealso:: :func:`focus` + + .. versionadded:: 0.0.1 + """ + for app in self.xa_wksp.runningApplications(): + app.unhide() + return self
+ + def _get_processes(self, processes): + for process in self.xa_sevt.processes(): + processes.append(process) + +
[docs] def windows(self, filter: dict = None) -> list['XAWindow']: + return self.xa_prcs.windows(filter)
+ + @property + def front_window(self) -> 'XAWindow': + return self.xa_prcs.front_window + +
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': + return self._new_element(self.xa_prcs.xa_elem.menuBars(), XAUIMenuBarList, filter)
+ +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL, AppKit.NSImage]]: + """Gets a clipboard-codable representation of the application. + + When the clipboard content is set to an application, three items are placed on the clipboard: + 1. The application's name + 2. The URL to the application bundle + 3. The application icon + + After copying an application to the clipboard, pasting will have the following effects: + - In Finder: Paste a copy of the application bundle in the current directory + - In Terminal: Paste the name of the application followed by the path to the application + - In iWork: Paste the application name + - In Safari: Paste the application name + - In Notes: Attach a copy of the application bundle to the active note + The pasted content may different for other applications. + + :return: The clipboard-codable representation + :rtype: list[Union[str, AppKit.NSURL, AppKit.NSImage]] + + .. versionadded:: 0.0.8 + """ + return [self.xa_elem.localizedName(), self.xa_elem.bundleURL(), self.xa_elem.icon()]
+ + + + +###################### +### PyXA Utilities ### +###################### +# SDEFParser, XAList, XAPredicate, XAURL, XAPath +
[docs]class SDEFParser(XAObject): + def __init__(self, sdef_file: Union['XAPath', str]): + if isinstance(sdef_file, str): + sdef_file = XAPath(sdef_file) + self.file = sdef_file #: The full path to the SDEF file to parse + + self.app_name = "" + self.scripting_suites = [] + +
[docs] def parse(self): + app_name = self.file.path.split("/")[-1][:-5].title() + xa_prefix = "XA" + app_name + + tree = ET.parse(self.file.path) + + suites = [] + + scripting_suites = tree.findall("suite") + for suite in scripting_suites: + classes = [] + commands = {} + + ### Class Extensions + class_extensions = suite.findall("class-extension") + for extension in class_extensions: + properties = [] + elements = [] + responds_to_commands = [] + + class_name = xa_prefix + extension.attrib.get("extends", "").title() + class_comment = extension.attrib.get("description", "") + + ## Class Extension Properties + class_properties = extension.findall("property") + for property in class_properties: + property_type = property.attrib.get("type", "") + if property_type == "text": + property_type = "str" + elif property_type == "boolean": + property_type = "bool" + elif property_type == "number": + property_type = "float" + elif property_type == "integer": + property_type = "int" + elif property_type == "rectangle": + property_type = "tuple[int, int, int, int]" + else: + property_type = "XA" + app_name + property_type.title() + + property_name = property.attrib.get("name", "").replace(" ", "_").lower() + property_comment = property.attrib.get("description", "") + + properties.append({ + "type": property_type, + "name": property_name, + "comment": property_comment + }) + + ## Class Extension Elements + class_elements = extension.findall("element") + for element in class_elements: + element_name = (element.attrib.get("type", "") + "s").replace(" ", "_").lower() + element_type = "XA" + app_name + element.attrib.get("type", "").title() + + elements.append({ + "name": element_name, + "type": element_type + }) + + ## Class Extension Responds-To Commands + class_responds_to_commands = extension.findall("responds-to") + for command in class_responds_to_commands: + command_name = command.attrib.get("command", "").replace(" ", "_").lower() + responds_to_commands.append(command_name) + + classes.append({ + "name": class_name, + "comment": class_comment, + "properties": properties, + "elements": elements, + "responds-to": responds_to_commands + }) + + ### Classes + scripting_classes = suite.findall("class") + for scripting_class in scripting_classes: + properties = [] + elements = [] + responds_to_commands = [] + + class_name = xa_prefix + scripting_class.attrib.get("name", "").title() + class_comment = scripting_class.attrib.get("description", "") + + ## Class Properties + class_properties = scripting_class.findall("property") + for property in class_properties: + property_type = property.attrib.get("type", "") + if property_type == "text": + property_type = "str" + elif property_type == "boolean": + property_type = "bool" + elif property_type == "number": + property_type = "float" + elif property_type == "integer": + property_type = "int" + elif property_type == "rectangle": + property_type = "tuple[int, int, int, int]" + else: + property_type = "XA" + app_name + property_type.title() + + property_name = property.attrib.get("name", "").replace(" ", "_").lower() + property_comment = property.attrib.get("description", "") + + properties.append({ + "type": property_type, + "name": property_name, + "comment": property_comment + }) + + ## Class Elements + class_elements = scripting_class.findall("element") + for element in class_elements: + element_name = (element.attrib.get("type", "") + "s").replace(" ", "_").lower() + element_type = "XA" + app_name + element.attrib.get("type", "").title() + + elements.append({ + "name": element_name, + "type": element_type + }) + + ## Class Responds-To Commands + class_responds_to_commands = scripting_class.findall("responds-to") + for command in class_responds_to_commands: + command_name = command.attrib.get("command", "").replace(" ", "_").lower() + responds_to_commands.append(command_name) + + classes.append({ + "name": class_name, + "comment": class_comment, + "properties": properties, + "elements": elements, + "responds-to": responds_to_commands + }) + + + ### Commands + script_commands = suite.findall("command") + for command in script_commands: + command_name = command.attrib.get("name", "").lower().replace(" ", "_") + command_comment = command.attrib.get("description", "") + + parameters = [] + direct_param = command.find("direct-parameter") + if direct_param is not None: + direct_parameter_type = direct_param.attrib.get("type", "") + if direct_parameter_type == "specifier": + direct_parameter_type = "XABase.XAObject" + + direct_parameter_comment = direct_param.attrib.get("description") + + parameters.append({ + "name": "direct_param", + "type": direct_parameter_type, + "comment": direct_parameter_comment + }) + + if not "_" in command_name and len(parameters) > 0: + command_name += "_" + + command_parameters = command.findall("parameter") + for parameter in command_parameters: + parameter_type = parameter.attrib.get("type", "") + if parameter_type == "specifier": + parameter_type = "XAObject" + + parameter_name = parameter.attrib.get("name", "").lower().replace(" ", "_") + parameter_comment = parameter.attrib.get("description", "") + + parameters.append({ + "name": parameter_name, + "type": parameter_type, + "comment": parameter_comment, + }) + + commands[command_name] = { + "name": command_name, + "comment": command_comment, + "parameters": parameters + } + + suites.append({ + "classes": classes, + "commands": commands + }) + + self.scripting_suites = suites + return suites
+ +
[docs] def export(self, output_file: Union['XAPath', str]): + if isinstance(output_file, XAPath): + output_file = output_file.path + + lines = [] + + lines.append("from typing import Any, Callable, Union") + lines.append("\nfrom PyXA import XABase") + lines.append("from PyXA.XABase import OSType") + lines.append("from PyXA import XABaseScriptable") + + for suite in self.scripting_suites: + for scripting_class in suite["classes"]: + lines.append("\n\n") + lines.append("class " + scripting_class["name"].replace(" ", "") + "List:") + lines.append("\t\"\"\"A wrapper around lists of " + scripting_class["name"].lower() + "s that employs fast enumeration techniques.") + lines.append("\n\tAll properties of tabs can be called as methods on the wrapped list, returning a list containing each tab's value for the property.") + lines.append("\n\t.. versionadded:: " + PYXA_VERSION) + lines.append("\t\"\"\"") + + lines.append("\tdef __init__(self, properties: dict, filter: Union[dict, None] = None):") + lines.append("\t\tsuper().__init__(properties, " + scripting_class["name"].replace(" ", "") + ", filter)") + + for property in scripting_class["properties"]: + lines.append("") + lines.append("\tdef " + property["name"] + "(self) -> list['" + property["type"].replace(" ", "") + "']:") + lines.append("\t\t\"\"\"" + property["comment"] + "\n\n\t\t.. versionadded:: " + PYXA_VERSION + "\n\t\t\"\"\"") + lines.append("\t\treturn list(self.xa_elem.arrayByApplyingSelector_(\"" + property["name"] + "\"))") + + for property in scripting_class["properties"]: + lines.append("") + lines.append("\tdef by_" + property["name"] + "(self, " + property["name"] + ") -> '" + scripting_class["name"].replace(" ", "") + "':") + lines.append("\t\t\"\"\"Retrieves the " + scripting_class["comment"] + "whose " + property["name"] + " matches the given " + property["name"] + ".\n\n\t\t.. versionadded:: " + PYXA_VERSION + "\n\t\t\"\"\"") + lines.append("\t\treturn self.by_property(\"" + property["name"] + "\", " + property["name"] + ")") + + + lines.append("") + lines.append("class " + scripting_class["name"].replace(" ", "") + ":") + lines.append("\t\"\"\"" + scripting_class["comment"] + "\n\n\t.. versionadded:: " + PYXA_VERSION + "\n\t\"\"\"") + + for property in scripting_class["properties"]: + lines.append("") + lines.append("\t@property") + lines.append("\tdef " + property["name"] + "(self) -> '" + property["type"].replace(" ", "") + "':") + lines.append("\t\t\"\"\"" + property["comment"] + "\n\n\t\t.. versionadded:: " + PYXA_VERSION + "\n\t\t\"\"\"") + lines.append("\t\treturn self.xa_elem." + property["name"] + "()") + + for element in scripting_class["elements"]: + lines.append("") + lines.append("\tdef " + element["name"].replace(" ", "") + "(self, filter: Union[dict, None] = None) -> '" + element["type"].replace(" ", "") + "':") + lines.append("\t\t\"\"\"Returns a list of " + element["name"] + ", as PyXA objects, matching the given filter.") + lines.append("\n\t\t.. versionadded:: " + PYXA_VERSION) + lines.append("\t\t\"\"\"") + lines.append("\t\tself._new_element(self.xa_elem." + element["name"] + "(), " + element["type"].replace(" ", "") + "List, filter)") + + for command in scripting_class["responds-to"]: + if command in suite["commands"]: + lines.append("") + command_str = "\tdef " + suite["commands"][command]["name"] + "(self, " + + for parameter in suite["commands"][command]["parameters"]: + command_str += parameter["name"] + ": '" + parameter["type"] + "', " + + command_str = command_str[:-2] + "):" + lines.append(command_str) + + lines.append("\t\t\"\"\"" + suite["commands"][command]["comment"]) + lines.append("\n\t\t.. versionadded:: " + PYXA_VERSION) + lines.append("\t\t\"\"\"") + + cmd_call_str = "self.xa_elem." + suite["commands"][command]["name"] + "(" + + if len(suite["commands"][command]["parameters"]) > 0: + for parameter in suite["commands"][command]["parameters"]: + cmd_call_str += parameter["name"] + ", " + + cmd_call_str = cmd_call_str[:-2] + ")" + else: + cmd_call_str += ")" + + lines.append("\t\t" + cmd_call_str) + + data = "\n".join(lines) + with open(output_file, "w") as f: + f.write(data)
+ +
[docs]class XAList(XAObject): + """A wrapper around NSArray and NSMutableArray objects enabling fast enumeration and lazy evaluation of Objective-C objects. + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties: dict, object_class: type = None, filter: Union[dict, None] = None): + """Creates an efficient wrapper object around a list of scriptable elements. + + :param properties: PyXA properties passed to this object for utility purposes + :type properties: dict + :param object_class: _description_, defaults to None + :type object_class: type, optional + :param filter: A dictionary of properties and values to filter items by, defaults to None + :type filter: Union[dict, None], optional + + .. versionchanged:: 0.0.8 + The filter property is deprecated and will be removed in a future version. Use the :func:`filter` method instead. + + .. versionadded:: 0.0.3 + """ + super().__init__(properties) + self.xa_ocls = object_class + + if not isinstance(self.xa_elem, AppKit.NSArray): + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(self.xa_elem) + + if filter is not None: + self.xa_elem = XAPredicate().from_dict(filter).evaluate(self.xa_elem) + +
[docs] def by_property(self, property: str, value: Any) -> XAObject: + """Retrieves the first element whose property value matches the given value, if one exists. + + :param property: The property to match + :type property: str + :param value: The value to match + :type value: Any + :return: The matching element, if one is found + :rtype: XAObject + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Photos") + >>> photo = app.media_items().by_property("id", "CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001") + >>> print(photo) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001> + + .. versionadded:: 0.0.6 + """ + predicate = XAPredicate() + predicate.add_eq_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + if hasattr(ls, "get"): + ls = predicate.evaluate(self.xa_elem).get() + obj = ls[0] + return self._new_element(obj, self.xa_ocls)
+ +
[docs] def equalling(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value equals the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("TV") + >>> print(app.tracks().equalling("playedCount", 0)) + <<class 'PyXA.apps.TV.XATVTrackList'>['Frozen', 'Sunshine', 'The Hunger Games: Mockingjay - Part 2', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_eq_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def not_equalling(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value does not equal the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("TV") + >>> print(app.tracks().not_equalling("playedCount", 0)) + <<class 'PyXA.apps.TV.XATVTrackList'>['The Avatar State', 'The Cave of Two Lovers', 'Return to Omashu', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_neq_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def containing(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value contains the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Reminders") + >>> print(app.reminders().containing("name", "PyXA")) + <<class 'PyXA.apps.Reminders.XARemindersReminderList'>['PyXA v0.1.0 release']> + + .. versionadded:: 0.0.6 + """ + predicate = XAPredicate() + predicate.add_contains_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def not_containing(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value does not contain the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Reminders") + >>> print(app.reminders().not_containing("name", " ")) + <<class 'PyXA.apps.Reminders.XARemindersReminderList'>['Trash', 'Thing', 'Reminder', ...]> + + .. versionadded:: 0.1.0 + """ + ls = XAPredicate.evaluate_with_format(self.xa_elem, f"NOT {property} CONTAINS \"{value}\"") + return self._new_element(ls, self.__class__)
+ +
[docs] def beginning_with(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value begins with the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("System Events") + >>> print(app.downloads_folder.files().beginning_with("name", "Example")) + <<class 'PyXA.apps.SystemEvents.XASystemEventsFileList'>['Example.png', 'ExampleImage.png', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_begins_with_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def ending_with(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value ends with the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("System Events") + >>> print(app.downloads_folder.files().ending_with("name", ".png")) + <<class 'PyXA.apps.SystemEvents.XASystemEventsFileList'>['Example.png', 'Image.png', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_ends_with_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def greater_than(self, property: str, value: Union[int, float]) -> XAObject: + """Retrieves all elements whose property value is greater than the given value. + + :param property: The property to match + :type property: str + :param value: The value to compare against + :type value: Union[int, float] + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Photos") + >>> print(app.media_items().greater_than("altitude", 10000)[0].spotlight()) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=53B0F28E-0B39-446B-896C-484CD0DC2D3C/L0/001> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_gt_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def less_than(self, property: str, value: Union[int, float]) -> XAObject: + """Retrieves all elements whose property value is less than the given value. + + :param property: The property to match + :type property: str + :param value: The value to compare against + :type value: Union[int, float] + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> app = PyXA.Application("Music") + >>> tracks = app.tracks() + >>> print(tracks.less_than("playedCount", 5).name()) + ['Outrunning Karma', 'Death of a Hero', '1994', 'Mind Is a Prison'] + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_lt_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def between(self, property: str, value1: Union[int, float], value2: Union[int, float]) -> XAObject: + """Retrieves all elements whose property value is between the given values. + + :param property: The property to match + :type property: str + :param value1: The lower-end of the range to match + :type value1: Union[int, float] + :param value2: The upper-end of the range to match + :type value2: Union[int, float] + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> from datetime import datetime, timedelta + >>> + >>> app = PyXA.Application("Calendar") + >>> events = app.calendars()[3].events() + >>> now = datetime.now() + >>> print(events.between("startDate", now, now + timedelta(days=1))) + <<class 'PyXA.apps.Calendar.XACalendarEventList'>['Capstone Meeting', 'Lunch with Dan']> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_gt_condition(property, value1) + predicate.add_lt_condition(property, value2) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def filter(self, filter: str, comparison_operation: Union[str, None] = None, value1: Union[Any, None] = None, value2: Union[Any, None] = None) -> 'XAList': + """Filters the list by the given parameters. + + The filter may be either a format string, used to create an NSPredicate, or up to 4 arguments specifying the filtered property name, the comparison operation, and up to two values to compare against. + + :param filter: A format string or a property name + :type filter: str + :param comparison_operation: The symbol or name of a comparison operation, such as > or <, defaults to None + :type comparison_operation: Union[str, None], optional + :param value1: The first value to compare each list item's property value against, defaults to None + :type value1: Union[Any, None], optional + :param value2: The second value to compare each list item's property value against, defaults to None + :type value2: Union[Any, None], optional + :return: The filter XAList object + :rtype: XAList + + :Example 1: Get the last file sent by you (via this machine) in Messages.app + + >>> import PyXA + >>> app = PyXA.application("Messages") + >>> last_file_transfer = app.file_transfers().filter("direction", "==", app.MessageDirection.OUTGOING)[-1] + >>> print(last_file_transfer) + <<class 'PyXA.apps.Messages.XAMessagesFileTransfer'>Test.jpg> + + :Example 2: Get the list of favorite photos/videos from Photos.app + + >>> import PyXA + >>> app = PyXA.application("Photos") + >>> favorites = app.media_items().filter("favorite", "==", True) + >>> print(favorites) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItemList'>['CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001', 'EFEB7F37-8373-4972-8E43-21612F597185/L0/001', ...]> + + .. note:: + + For properties that appear to be boolean but fail to return expected filter results, try using the corresponding 0 or 1 value instead. + + :Example 3: Provide a custom format string + + >>> import PyXA + >>> app = PyXA.application("Photos") + >>> photo = app.media_items().filter("id == 'CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001'")[0] + >>> print(photo) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001> + + .. versionadded:: 0.0.8 + """ + if comparison_operation is not None and value1 is not None: + predicate = XAPredicate() + if comparison_operation in ["=", "==", "eq", "EQ", "equals", "EQUALS"]: + predicate.add_eq_condition(filter, value1) + elif comparison_operation in ["!=", "!==", "neq", "NEQ", "not equal to", "NOT EQUAL TO"]: + predicate.add_neq_condition(filter, value1) + elif comparison_operation in [">", "gt", "GT", "greater than", "GREATER THAN"]: + predicate.add_gt_condition(filter, value1) + elif comparison_operation in ["<", "lt", "LT", "less than", "LESS THAN"]: + predicate.add_lt_condition(filter, value1) + elif comparison_operation in [">=", "geq", "GEQ", "greater than or equal to", "GREATER THAN OR EQUAL TO"]: + predicate.add_geq_condition(filter, value1) + elif comparison_operation in ["<=", "leq", "LEQ", "less than or equal to", "LESS THAN OR EQUAL TO"]: + predicate.add_leq_condition(filter, value1) + elif comparison_operation in ["begins with", "beginswith", "BEGINS WITH", "BEGINSWITH"]: + predicate.add_begins_with_condition(filter, value1) + elif comparison_operation in ["contains", "CONTAINS"]: + predicate.add_contains_condition(filter, value1) + elif comparison_operation in ["ends with", "endswith", "ENDS WITH", "ENDSWITH"]: + predicate.add_ends_with_condition(filter, value1) + elif comparison_operation in ["between", "BETWEEN"]: + predicate.add_between_condition(filter, value1, value2) + elif comparison_operation in ["matches", "MATCHES"]: + predicate.add_match_condition(filter, value1) + + filtered_list = predicate.evaluate(self.xa_elem) + return self._new_element(filtered_list, self.__class__) + else: + filtered_list = XAPredicate.evaluate_with_format(self.xa_elem, filter) + return self._new_element(filtered_list, self.__class__)
+ +
[docs] def at(self, index: int) -> XAObject: + """Retrieves the element at the specified index. + + :param index: The index of the desired element + :type index: int + :return: The PyXA-wrapped element object + :rtype: XAObject + + .. versionadded:: 0.0.6 + """ + return self._new_element(self.xa_elem[index], self.xa_ocls)
+ + @property + def first(self) -> XAObject: + """Retrieves the first element of the list as a wrapped PyXA object. + + :return: The wrapped object + :rtype: XAObject + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_elem.firstObject(), self.xa_ocls) + + @property + def last(self) -> XAObject: + """Retrieves the last element of the list as a wrapped PyXA object. + + :return: The wrapped object + :rtype: XAObject + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_elem.lastObject(), self.xa_ocls) + +
[docs] def shuffle(self) -> 'XAList': + """Randomizes the order of objects in the list. + + :return: A reference to the shuffled XAList + :rtype: XAList + + .. versionadded:: 0.0.3 + """ + try: + self.xa_elem = self.xa_elem.shuffledArray() + except AttributeError: + random.shuffle(self.xa_elem) + return self
+ +
[docs] def push(self, *elements: list[XAObject]): + """Appends the object referenced by the provided PyXA wrapper to the end of the list. + + .. versionadded:: 0.0.3 + """ + for element in elements: + self.xa_elem.addObject_(element.xa_elem)
+ +
[docs] def insert(self, element: XAObject, index: int): + """Inserts the object referenced by the provided PyXA wrapper at the specified index. + + .. versionadded:: 0.0.3 + """ + self.xa_elem.insertObject_atIndex_(element.xa_elem, index)
+ +
[docs] def pop(self, index: int = -1) -> XAObject: + """Removes the object at the specified index from the list and returns it. + + .. versionadded:: 0.0.3 + """ + removed = self.xa_elem.lastObject() + self.xa_elem.removeLastObject() + return self._new_element(removed, self.xa_ocls)
+ + def __getitem__(self, key: Union[int, slice]): + if isinstance(key, slice): + arr = AppKit.NSMutableArray.alloc().initWithArray_([self.xa_elem[index] for index in range(key.start, key.stop, key.step or 1)]) + return self._new_element(arr, self.__class__) + return self._new_element(self.xa_elem.objectAtIndex_(key), self.xa_ocls) + + def __len__(self): + if hasattr(self.xa_elem, "count"): + return self.xa_elem.count() + return len(self.xa_elem) + + def __reversed__(self): + self.xa_elem = self.xa_elem.reverseObjectEnumerator().allObjects() + return self + + def __iter__(self): + return (self._new_element(object, self.xa_ocls) for object in self.xa_elem.objectEnumerator()) + + def __repr__(self): + return "<" + str(type(self)) + str(self.xa_elem) + ">"
+ + + + +
[docs]class XAPredicate(XAObject, XAClipboardCodable): + """A predicate used to filter arrays. + + .. versionadded:: 0.0.4 + """ + def __init__(self): + self.keys: list[str] = [] + self.operators: list[str] = [] + self.values: list[str] = [] + +
[docs] def from_dict(self, ref_dict: dict) -> 'XAPredicate': + """Populates the XAPredicate object from the supplied dictionary. + + The predicate will use == for all comparisons. + + :param ref_dict: A specification of key, value pairs + :type ref_dict: dict + :return: The populated predicate object + :rtype: XAPredicate + + .. versionadded:: 0.0.4 + """ + for key, value in ref_dict.items(): + self.keys.append(key) + self.operators.append("==") + self.values.append(value) + return self
+ +
[docs] def from_args(self, *args) -> 'XAPredicate': + """Populates the XAPredicate object from the supplied key, value argument pairs. + + The number of keys and values must be equal. The predicate will use == for all comparisons. + + :raises InvalidPredicateError: Raised when the number of keys does not match the number of values + :return: The populated predicate object + :rtype: XAPredicate + + .. versionadded:: 0.0.4 + """ + arg_num = len(args) + if arg_num % 2 != 0: + raise InvalidPredicateError("The number of keys and values must be equal; the number of arguments must be an even number.") + + for index, value in enumerate(args): + if index % 2 == 0: + self.keys.append(value) + self.operators.append("==") + self.values.append(args[index + 1]) + return self
+ +
[docs] def evaluate(self, target: Union[AppKit.NSArray, XAList]) -> AppKit.NSArray: + """Evaluates the predicate on the given array. + + :param target: The array to evaluate against the predicate + :type target: AppKit.NSArray + :return: The filtered array + :rtype: AppKit.NSArray + + .. versionadded:: 0.0.4 + """ + target_list = target + if isinstance(target, XAList): + target_list = target.xa_elem + + placeholders = ["%@"] * len(self.values) + expressions = [" ".join(expr) for expr in zip(self.keys, self.operators, placeholders)] + format = "( " + " ) && ( ".join(expressions) + " )" + + predicate = AppKit.NSPredicate.predicateWithFormat_(format, *self.values) + predicate = str(predicate) # Not sure why this is necessary sometimes, but it is. + ls = target_list.filteredArrayUsingPredicate_(AppKit.NSPredicate.predicateWithFormat_(predicate)) + + if isinstance(target, XAList): + return target.__class__({ + "parent": target, + "appspace": AppKit.NSApplication.sharedApplication(), + "workspace": AppKit.NSWorkspace.sharedWorkspace(), + "element": ls, + "appref": AppKit.NSApplication.sharedApplication(), + }) + return ls
+ +
[docs] def evaluate_with_format(target: Union[AppKit.NSArray, XAList], fmt: str) -> AppKit.NSArray: + """Evaluates the specified array against a predicate with the given format. + + :param target: The array to filter + :type target: AppKit.NSArray + :param fmt: The format string for the predicate + :type fmt: str + :return: The filtered array + :rtype: AppKit.NSArray + + .. versionadded:: 0.0.4 + """ + target_list = target + if isinstance(target, XAList): + target_list = target.xa_elem + + predicate = AppKit.NSPredicate.predicateWithFormat_(fmt) + ls = target_list.filteredArrayUsingPredicate_(predicate) + + if isinstance(target, XAList): + return target.__class__({ + "parent": target, + "appspace": AppKit.NSApplication.sharedApplication(), + "workspace": AppKit.NSWorkspace.sharedWorkspace(), + "element": ls, + "appref": AppKit.NSApplication.sharedApplication(), + }) + return ls
+ +
[docs] def evaluate_with_dict(target: Union[AppKit.NSArray, XAList], properties_dict: dict) -> AppKit.NSArray: + """Evaluates the specified array against a predicate constructed from the supplied dictionary. + + The predicate will use == for all comparisons. + + :param target: The array to filter + :type target: AppKit.NSArray + :param properties_dict: The specification of key, value pairs + :type properties_dict: dict + :return: The filtered array + :rtype: AppKit.NSArray + + .. versionadded:: 0.0.4 + """ + target_list = target + if isinstance(target, XAList): + target_list = target.xa_elem + + fmt = "" + for key, value in properties_dict.items(): + if isinstance(value, str): + value = "'" + value + "'" + fmt += f"( {key} == {value} ) &&" + + predicate = AppKit.NSPredicate.predicateWithFormat_(fmt[:-3]) + ls = target_list.filteredArrayUsingPredicate_(predicate) + + if isinstance(target, XAList): + return target.__class__({ + "parent": target, + "appspace": AppKit.NSApplication.sharedApplication(), + "workspace": AppKit.NSWorkspace.sharedWorkspace(), + "element": ls, + "appref": AppKit.NSApplication.sharedApplication(), + }) + return ls
+ + # EQUAL +
[docs] def add_eq_condition(self, property: str, value: Any): + """Appends an `==` condition to the end of the predicate format. + + The added condition will have the form `property == value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("==") + self.values.append(value)
+ +
[docs] def insert_eq_condition(self, index: int, property: str, value: Any): + """Inserts an `==` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property == value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "==") + self.values.insert(index, value)
+ + # NOT EQUAL +
[docs] def add_neq_condition(self, property: str, value: Any): + """Appends a `!=` condition to the end of the predicate format. + + The added condition will have the form `property != value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("!=") + self.values.append(value)
+ +
[docs] def insert_neq_condition(self, index: int, property: str, value: Any): + """Inserts a `!=` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property != value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "!=") + self.values.insert(index, value)
+ + # GREATER THAN OR EQUAL +
[docs] def add_geq_condition(self, property: str, value: Any): + """Appends a `>=` condition to the end of the predicate format. + + The added condition will have the form `property >= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append(">=") + self.values.append(value)
+ +
[docs] def insert_geq_condition(self, index: int, property: str, value: Any): + """Inserts a `>=` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property >= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, ">=") + self.values.insert(index, value)
+ + # LESS THAN OR EQUAL +
[docs] def add_leq_condition(self, property: str, value: Any): + """Appends a `<=` condition to the end of the predicate format. + + The added condition will have the form `property <= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("<=") + self.values.append(value)
+ +
[docs] def insert_leq_condition(self, index: int, property: str, value: Any): + """Inserts a `<=` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property <= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "<=") + self.values.insert(index, value)
+ + # GREATER THAN +
[docs] def add_gt_condition(self, property: str, value: Any): + """Appends a `>` condition to the end of the predicate format. + + The added condition will have the form `property > value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append(">") + self.values.append(value)
+ +
[docs] def insert_gt_condition(self, index: int, property: str, value: Any): + """Inserts a `>` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property > value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, ">") + self.values.insert(index, value)
+ + # LESS THAN +
[docs] def add_lt_condition(self, property: str, value: Any): + """Appends a `<` condition to the end of the predicate format. + + The added condition will have the form `property < value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("<") + self.values.append(value)
+ +
[docs] def insert_lt_condition(self, index: int, property: str, value: Any): + """Inserts a `<` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property < value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "<") + self.values.insert(index, value)
+ + # BETWEEN +
[docs] def add_between_condition(self, property: str, value1: Union[int, float], value2: Union[int, float]): + """Appends a `BETWEEN` condition to the end of the predicate format. + + The added condition will have the form `property BETWEEN [value1, value2]`. + + :param property: A property of an object to check the condition against + :type property: str + :param value1: The lower target value of the condition + :type value1: Union[int, float] + :param value2: The upper target value of the condition + :type value2: Union[int, float] + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("BETWEEN") + self.values.append([value1, value2])
+ +
[docs] def insert_between_condition(self, index: int, property: str, value1: Union[int, float], value2: Union[int, float]): + """Inserts a `BETWEEN` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property BETWEEN [value1, value2]`. + + :param property: A property of an object to check the condition against + :type property: str + :param value1: The lower target value of the condition + :type value1: Union[int, float] + :param value2: The upper target value of the condition + :type valu2e: Union[int, float] + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "BETWEEN") + self.values.insert(index, [value1, value2])
+ + # BEGINSWITH +
[docs] def add_begins_with_condition(self, property: str, value: Any): + """Appends a `BEGINSWITH` condition to the end of the predicate format. + + The added condition will have the form `property BEGINSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("BEGINSWITH") + self.values.append(value)
+ +
[docs] def insert_begins_with_condition(self, index: int, property: str, value: Any): + """Inserts a `BEGINSWITH` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property BEGINSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "BEGINSWITH") + self.values.insert(index, value)
+ + # ENDSWITH +
[docs] def add_ends_with_condition(self, property: str, value: Any): + """Appends a `ENDSWITH` condition to the end of the predicate format. + + The added condition will have the form `property ENDSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("ENDSWITH") + self.values.append(value)
+ +
[docs] def insert_ends_with_condition(self, index: int, property: str, value: Any): + """Inserts a `ENDSWITH` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property ENDSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "ENDSWITH") + self.values.insert(index, value)
+ + # CONTAINS +
[docs] def add_contains_condition(self, property: str, value: Any): + """Appends a `CONTAINS` condition to the end of the predicate format. + + The added condition will have the form `property CONTAINS value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("CONTAINS") + self.values.append(value)
+ +
[docs] def insert_contains_condition(self, index: int, property: str, value: Any): + """Inserts a `CONTAINS` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property CONTAINS value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "CONTAINS") + self.values.insert(index, value)
+ + # MATCHES +
[docs] def add_match_condition(self, property: str, value: Any): + """Appends a `MATCHES` condition to the end of the predicate format. + + The added condition will have the form `property MATCHES value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("MATCHES") + self.values.append(value)
+ +
[docs] def insert_match_condition(self, index: int, property: str, value: Any): + """Inserts a `MATCHES` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property MATCHES value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "MATCHES") + self.values.insert(index, value)
+ +
[docs] def get_clipboard_representation(self) -> str: + """Gets a clipboard-codable representation of the predicate. + + When a predicate is copied to the clipboard, the string representation of the predicate is added to the clipboard. + + :return: The string representation of the predicate + :rtype: str + + .. versionadded:: 0.0.8 + """ + placeholders = ["%@"] * len(self.values) + expressions = [" ".join(expr) for expr in zip(self.keys, self.operators, placeholders)] + format = "( " + " ) && ( ".join(expressions) + " )" + predicate = AppKit.NSPredicate.predicateWithFormat_(format, *self.values) + return predicate.predicateFormat()
+ + + + +
[docs]class XAURL(XAObject, XAClipboardCodable): + """A URL using any scheme recognized by the system. This can be a file URL. + + .. versionadded:: 0.0.5 + """ + def __init__(self, url: Union[str, AppKit.NSURL]): + super().__init__() + self.parameters: str #: The query parameters of the URL + self.scheme: str #: The URI scheme of the URL + self.fragment: str #: The fragment identifier following a # symbol in the URL + self.port: int #: The port that the URL points to + self.html: element.tag #: The html of the URL + self.title: str #: The title of the URL + self.soup: BeautifulSoup = None #: The bs4 object for the URL, starts as None until a bs4-related action is made + self.url: str = url #: The string form of the URL + if isinstance(url, str): + url = url.replace(" ", "%20") + url = AppKit.NSURL.alloc().initWithString_(url) + self.xa_elem = url + + @property + def base_url(self) -> str: + return self.xa_elem.host() + + @property + def parameters(self) -> str: + return self.xa_elem.query() + + @property + def scheme(self) -> str: + return self.xa_elem.scheme() + + @property + def fragment(self) -> str: + return self.xa_elem.fragment() + + @property + def html(self) -> element.Tag: + if self.soup is None: + self.__get_soup() + return self.soup.html + + @property + def title(self) -> str: + if self.soup is None: + self.__get_soup() + return self.soup.title.text + + def __get_soup(self): + req = requests.get(str(self.xa_elem)) + self.soup = BeautifulSoup(req.text, "html.parser") + +
[docs] def open(self): + """Opens the URL in the appropriate default application. + + .. versionadded:: 0.0.5 + """ + AppKit.NSWorkspace.sharedWorkspace().openURL_(self.xa_elem)
+ +
[docs] def extract_text(self) -> list[str]: + """Extracts the visible text from the webpage that the URL points to. + + :return: The list of extracted lines of text + :rtype: list[str] + + .. versionadded:: 0.0.8 + """ + if self.soup is None: + self.__get_soup() + return self.soup.get_text().splitlines()
+ +
[docs] def extract_images(self) -> list['XAImage']: + """Extracts all images from HTML of the webpage that the URL points to. + + :return: The list of extracted images + :rtype: list[XAImage] + + .. versionadded:: 0.0.8 + """ + data = AppKit.NSData.alloc().initWithContentsOfURL_(AppKit.NSURL.URLWithString_(str(self.xa_elem))) + image = AppKit.NSImage.alloc().initWithData_(data) + + if image is not None: + image_object = XAImage(image, name = self.xa_elem.pathComponents()[-1]) + return [image_object] + else: + if self.soup is None: + self.__get_soup() + + images = self.soup.findAll("img") + image_objects = [] + for image in images: + image_src = image["src"] + if image_src.startswith("/"): + image_src = str(self) + str(image["src"]) + + data = AppKit.NSData.alloc().initWithContentsOfURL_(AppKit.NSURL.URLWithString_(image_src)) + image = AppKit.NSImage.alloc().initWithData_(data) + if image is not None: + image_object = XAImage(image, name = XAURL(image_src).xa_elem.pathComponents()[-1]) + image_objects.append(image_object) + + return image_objects
+ +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of the URL. + + When the clipboard content is set to a URL, the raw URL data and the string representation of the URL are added to the clipboard. + + :return: The clipboard-codable form of the URL + :rtype: Any + + .. versionadded:: 0.0.8 + """ + return [self.xa_elem, str(self.xa_elem)]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.xa_elem) + ">"
+ + + + +
[docs]class XAPath(XAObject, XAClipboardCodable): + """A path to a file on the disk. + + .. versionadded:: 0.0.5 + """ + def __init__(self, path: Union[str, AppKit.NSURL]): + super().__init__() + if isinstance(path, str): + path = AppKit.NSURL.alloc().initFileURLWithPath_(path) + self.xa_elem = path + self.path = path.path() #: The path string without the file:// prefix + self.xa_wksp = AppKit.NSWorkspace.sharedWorkspace() + +
[docs] def open(self): + """Opens the file in its default application. + + .. versionadded: 0.0.5 + """ + self.xa_wksp.openURL_(self.xa_elem)
+ +
[docs] def show_in_finder(self): + """Opens a Finder window showing the folder containing this path, with the associated file selected. Synonymous with :func:`select`. + + .. versionadded: 0.0.9 + """ + self.select()
+ +
[docs] def select(self): + """Opens a Finder window showing the folder containing this path, with the associated file selected. Synonymous with :func:`show_in_finder`. + + .. versionadded: 0.0.5 + """ + self.xa_wksp.activateFileViewerSelectingURLs_([self.xa_elem])
+ +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of the path. + + When the clipboard content is set to a path, the raw file URL data and the string representation of the path are added to the clipboard. + + :return: The clipboard-codable form of the path + :rtype: Any + + .. versionadded:: 0.0.8 + """ + return [self.xa_elem, self.xa_elem.path()]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.xa_elem) + ">"
+ + + + +######################## +### Interoperability ### +######################## +# AppleScript +
[docs]class AppleScript(XAObject): + """A class for constructing and executing AppleScript scripts. + + .. versionadded:: 0.0.5 + """ + def __init__(self, script: Union[str, list[str], None] = None): + """Creates a new AppleScript object. + + :param script: A string or list of strings representing lines of AppleScript code, or the path to a script plaintext file, defaults to None + :type script: Union[str, list[str], None], optional + + .. versionadded:: 0.0.5 + """ + self.script: list[str] #: The lines of AppleScript code contained in the script + self.last_result: Any #: The return value of the last execution of the script + self.file_path: XAPath #: The file path of this script, if one exists + + if isinstance(script, str): + if script.startswith("/"): + with open(script, 'r') as f: + script = f.readlines() + else: + self.script = [script] + elif isinstance(script, list): + self.script = script + elif script == None: + self.script = [] + + @property + def last_result(self) -> Any: + return self.__last_result + + @property + def file_path(self) -> 'XAPath': + return self.__file_path + +
[docs] def add(self, script: Union[str, list[str], 'AppleScript']): + """Adds the supplied string, list of strings, or script as a new line entry in the script. + + :param script: The script to append to the current script string. + :type script: Union[str, list[str], AppleScript] + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript("tell application \"Safari\"") + >>> script.add("print the document of window 1") + >>> script.add("end tell") + >>> script.run() + + .. versionadded:: 0.0.5 + """ + if isinstance(script, str): + self.script.append(script) + elif isinstance(script, list): + self.script.extend(script) + elif isinstance(script, AppleScript): + self.script.extend(script.script)
+ +
[docs] def insert(self, index: int, script: Union[str, list[str], 'AppleScript']): + """Inserts the supplied string, list of strings, or script as a line entry in the script starting at the given line index. + + :param index: The line index to begin insertion at + :type index: int + :param script: The script to insert into the current script + :type script: Union[str, list[str], AppleScript] + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> script.insert(1, "activate") + >>> script.run() + + .. versionadded:: 0.0.9 + """ + if isinstance(script, str): + self.script.insert(index, script) + elif isinstance(script, list): + for line in script: + self.script.insert(index, line) + index += 1 + elif isinstance(script, AppleScript): + for line in script.script: + self.script.insert(index, line) + index += 1
+ +
[docs] def pop(self, index: int = -1) -> str: + """Removes the line at the given index from the script. + + :param index: The index of the line to remove + :type index: int + :return: The text of the removed line + :rtype: str + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> print(script.pop(1)) + get chats + + .. versionadded:: 0.0.9 + """ + return self.script.pop(index)
+ +
[docs] def load(path: Union['XAPath', str]) -> 'AppleScript': + """Loads an AppleScript (.scpt) file as a runnable AppleScript object. + + :param path: The path of the .scpt file to load + :type path: Union[XAPath, str] + :return: The newly loaded AppleScript object + :rtype: AppleScript + + :Example 1: Load and run a script + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> print(script.run()) + { + 'string': None, + 'int': 0, + 'bool': False, + 'float': 0.0, + 'date': None, + 'file_url': None, + 'type_code': 845507684, + 'data': {length = 8962, bytes = 0x646c6532 00000000 6c697374 000022f2 ... 6e756c6c 00000000 }, + 'event': <NSAppleEventDescriptor: [ 'obj '{ ... } ]> + } + + :Example 2: Load, modify, and run a script + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> script.pop(1) + >>> script.insert(1, "activate") + >>> script.run() + + .. versionadded:: 0.0.8 + """ + if isinstance(path, str): + path = XAPath(path) + script = AppKit.NSAppleScript.alloc().initWithContentsOfURL_error_(path.xa_elem, None)[0] + + attributed_string = script.richTextSource() + attributed_string = str(attributed_string).split("}") + parts = [] + for x in attributed_string: + parts.extend(x.split("{")) + + for x in parts: + if "=" in x: + parts.remove(x) + + script = AppleScript("".join(parts).split("\n")) + script.__file_path = path + return script
+ +
[docs] def save(self, path: Union['XAPath', str, None] = None): + """Saves the script to the specified file path, or to the path from which the script was loaded. + + :param path: The path to save the script at, defaults to None + :type path: Union[XAPath, str, None], optional + + :Example 1: Save the script to a specified path + + >>> import PyXA + >>> script = PyXA.AppleScript(f\"\"\" + >>> tell application "Safari" + >>> activate + >>> end tell + >>> \"\"\") + >>> script.save("/Users/exampleUser/Downloads/Example.scpt") + + :Example 2: Load a script, modify it, then save it + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/steven/Downloads/Example.scpt") + >>> script.insert(2, "delay 2") + >>> script.insert(3, "set the miniaturized of window 1 to true") + >>> script.save() + + .. versionadded:: 0.0.9 + """ + if path is None and self.file_path is None: + print("No path to save script to!") + return + + if isinstance(path, str): + path = XAPath(path) + + script = "" + for line in self.script: + script += line + "\n" + script = AppKit.NSAppleScript.alloc().initWithSource_(script) + script.compileAndReturnError_(None) + source = (script.richTextSource().string()) + + if path is not None: + self.__file_path = path + + with open(self.file_path.xa_elem.path(), "w") as f: + f.write(source)
+ +
[docs] def parse_result_data(result: dict) -> list[tuple[str, str]]: + """Extracts string data from an AppleScript execution result dictionary. + + :param result: The execution result dictionary to extract data from + :type result: dict + :return: A list of responses contained in the result structured as tuples + :rtype: list[tuple[str, str]] + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> print(script.script) + >>> result = script.run() + >>> print(PyXA.AppleScript.parse_result_data(result)) + ['tell application "Messages"', '\\tget chats', 'end tell'] + [('ID', 'iMessage;-;+12345678910'), ('ID', 'iMessage;-;+12345678911'), ('ID', 'iMessage;-;example@icloud.com'), ...] + + .. versionadded:: 0.0.9 + """ + result = result["event"] + response_objects = [] + num_responses = result.numberOfItems() + for response_index in range(1, num_responses + 1): + response = result.descriptorAtIndex_(response_index) + + data = () + num_params = response.numberOfItems() + if num_params == 0: + data = response.stringValue().strip() + + else: + for param_index in range(1, num_params + 1): + param = response.descriptorAtIndex_(param_index).stringValue() + if param is not None: + data += (param.strip(), ) + response_objects.append(data) + + return response_objects
+ +
[docs] def run(self) -> Any: + """Compiles and runs the script, returning the result. + + :return: The return value of the script. + :rtype: Any + + :Example: + + import PyXA + script = PyXA.AppleScript(f\"\"\"tell application "System Events" + return 1 + 2 + end tell + \"\"\") + print(script.run()) + { + 'string': '3', + 'int': 3, + 'bool': False, + 'float': 3.0, + 'date': None, + 'file_url': None, + 'type_code': 3, + 'data': {length = 4, bytes = 0x03000000}, + 'event': <NSAppleEventDescriptor: 3> + } + + .. versionadded:: 0.0.5 + """ + script = "" + for line in self.script: + script += line + "\n" + script = AppKit.NSAppleScript.alloc().initWithSource_(script) + + result = script.executeAndReturnError_(None)[0] + if result is not None: + self.__last_result = { + "string": result.stringValue(), + "int": result.int32Value(), + "bool": result.booleanValue(), + "float": result.doubleValue(), + "date": result.dateValue(), + "file_url": result.fileURLValue(), + "type_code": result.typeCodeValue(), + "data": result.data(), + "event": result, + } + return self.last_result
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.script) + ">"
+ + + + +######################## +### System Utilities ### +######################## +# XAClipboard, XANotification, XAProcess, XACommandDetector, XASpeechRecognizer, XASpeech, XASpotlight +
[docs]class XAClipboard(XAObject): + """A wrapper class for managing and interacting with the system clipboard. + + .. versionadded:: 0.0.5 + """ + def __init__(self): + self.xa_elem = AppKit.NSPasteboard.generalPasteboard() + self.content #: The content of the clipboard + + @property + def content(self) -> dict[str, list[Any]]: + info_by_type = {} + for item in self.xa_elem.pasteboardItems(): + for item_type in item.types(): + info_by_type[item_type] = { + "data": item.dataForType_(item_type), + "properties": item.propertyListForType_(item_type), + "strings": item.stringForType_(item_type), + } + return info_by_type + + @content.setter + def content(self, value: list[Any]): + if not isinstance(value, list): + value = [value] + self.xa_elem.clearContents() + for index, item in enumerate(value): + if item == None: + value[index] = "" + elif isinstance(item, XAObject): + if not isinstance(item, XAClipboardCodable): + print(item, "is not a clipboard-codable object.") + continue + if isinstance(item.xa_elem, ScriptingBridge.SBElementArray) and item.xa_elem.get() is None: + value[index] = "" + else: + content = item.get_clipboard_representation() + if isinstance(content, list): + value.pop(index) + value += content + else: + value[index] = content + elif isinstance(item, int) or isinstance(item, float): + value[index] = str(item) + self.xa_elem.writeObjects_(value) + +
[docs] def clear(self): + """Clears the system clipboard. + + .. versionadded:: 0.0.5 + """ + self.xa_elem.clearContents()
+ +
[docs] def get_strings(self) -> list[str]: + """Retrieves string type data from the clipboard, if any such data exists. + + :return: The list of strings currently copied to the clipboard + :rtype: list[str] + + .. versionadded:: 0.0.8 + """ + items = [] + for item in self.xa_elem.pasteboardItems(): + string = item.stringForType_(AppKit.NSPasteboardTypeString) + if string is not None: + items.append(string) + return items
+ +
[docs] def get_urls(self) -> list['XAURL']: + """Retrieves URL type data from the clipboard, as instances of :class:`XAURL` and :class:`XAPath`, if any such data exists. + + :return: The list of file URLs and web URLs currently copied to the clipboard + :rtype: list[XAURL] + + .. versionadded:: 0.0.8 + """ + items = [] + for item in self.xa_elem.pasteboardItems(): + url = None + string = item.stringForType_(AppKit.NSPasteboardTypeURL) + if string is None: + string = item.stringForType_(AppKit.NSPasteboardTypeFileURL) + if string is not None: + url = XAPath(XAURL(string).xa_elem) + else: + url = XAURL(string) + + if url is not None: + items.append(url) + return items
+ +
[docs] def get_images(self) -> list['XAImage']: + """Retrieves image type data from the clipboard, as instances of :class:`XAImage`, if any such data exists. + + :return: The list of images currently copied to the clipboard + :rtype: list[XAImage] + + .. versionadded:: 0.0.8 + """ + image_types = [AppKit.NSPasteboardTypePNG, AppKit.NSPasteboardTypeTIFF, 'public.jpeg', 'com.apple.icns'] + items = [] + for item in self.xa_elem.pasteboardItems(): + for image_type in image_types: + if image_type in item.types(): + img = XAImage(data = item.dataForType_(image_type)) + items.append(img) + return items
+ +
[docs] def set_contents(self, content: list[Any]): + """Sets the content of the clipboard + + :param content: A list of the content to add fill the clipboard with. + :type content: list[Any] + + .. deprecated:: 0.0.8 + Set the :ivar:`content` property directly instead. + + .. versionadded:: 0.0.5 + """ + self.xa_elem.clearContents() + self.xa_elem.writeObjects_(content)
+ + + + +
[docs]class XANotification(XAObject): + """A class for managing and interacting with notifications. + + .. versionadded:: 0.0.9 + """ + def __init__(self, text: str, title: Union[str, None] = None, subtitle: Union[str, None] = None, sound_name: Union[str, None] = None): + """Initializes a notification object. + + :param text: The main text of the notification + :type text: str + :param title: The title of the notification, defaults to None + :type title: Union[str, None], optional + :param subtitle: The subtitle of the notification, defaults to None + :type subtitle: Union[str, None], optional + :param sound_name: The sound to play when the notification is displayed, defaults to None + :type sound_name: Union[str, None], optional + + .. versionadded:: 0.0.9 + """ + self.text = text + self.title = title + self.subtitle = subtitle + self.sound_name = sound_name + +
[docs] def display(self): + """Displays the notification. + + .. todo:: + + Currently uses :func:`subprocess.Popen`. Should use UserNotifications in the future. + + .. versionadded:: 0.0.9 + """ + script = AppleScript() + script.add(f"display notification \\\"{self.text}\\\"") + + if self.title is not None: + script.add(f"with title \\\"{self.title}\\\"") + + if self.subtitle is not None: + script.add(f"subtitle \\\"{self.subtitle}\\\"") + + if self.sound_name is not None: + script.add(f"sound name \\\"{self.sound_name}\\\"") + + cmd = "osascript -e \"" + " ".join(script.script) + "\"" + subprocess.Popen([cmd], shell=True)
+ + + + +
[docs]class XAProcess(XAObject): + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = properties["window_class"] + self.id = self.xa_elem.id() + self.unix_id = self.xa_elem.unixId() + + self.front_window: XAWindow #: The front window of the application process + + @property + def front_window(self) -> 'XAWindow': + return self._new_element(self.xa_elem.windows()[0], XAWindow) + +
[docs] def windows(self, filter: dict = None) -> 'XAWindowList': + return self._new_element(self.xa_elem.windows(), XAWindowList, filter)
+ +
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': + return self._new_element(self.xa_elem.menuBars(), XAUIMenuBarList, filter)
+ + + + +
[docs]class XACommandDetector(XAObject): + """A command-based query detector. + + .. versionadded:: 0.0.9 + """ + def __init__(self, command_function_map: Union[dict[str, Callable[[], Any]], None] = None): + """Creates a command detector object. + + :param command_function_map: A dictionary mapping command strings to function objects + :type command_function_map: dict[str, Callable[[], Any]] + + .. versionadded:: 0.0.9 + """ + self.command_function_map = command_function_map or {} #: The dictionary of commands and corresponding functions to run upon detection + +
[docs] def on_detect(self, command: str, function: Callable[[], Any]): + """Adds or replaces a command to listen for upon calling :func:`listen`, and associates the given function with that command. + + :param command: The command to listen for + :type command: str + :param function: The function to call when the command is heard + :type function: Callable[[], Any] + + :Example: + + >>> detector = PyXA.XACommandDetector() + >>> detector.on_detect("go to google", PyXA.XAURL("http://google.com").open) + >>> detector.listen() + + .. versionadded:: 0.0.9 + """ + self.command_function_map[command] = function
+ +
[docs] def listen(self) -> Any: + """Begins listening for the specified commands. + + :return: The execution return value of the corresponding command function + :rtype: Any + + :Example: + + >>> import PyXA + >>> PyXA.speak("What app do you want to open?") + >>> PyXA.XACommandDetector({ + >>> "safari": PyXA.application("Safari").activate, + >>> "messages": PyXA.application("Messages").activate, + >>> "shortcuts": PyXA.application("Shortcuts").activate, + >>> "mail": PyXA.application("Mail").activate, + >>> "calendar": PyXA.application("Calendar").activate, + >>> "notes": PyXA.application("Notes").activate, + >>> "music": PyXA.application("Music").activate, + >>> "tv": PyXA.application("TV").activate, + >>> "pages": PyXA.application("Pages").activate, + >>> "numbers": PyXA.application("Numbers").activate, + >>> "keynote": PyXA.application("Keynote").activate, + >>> }).listen() + + .. versionadded:: 0.0.9 + """ + command_function_map = self.command_function_map + return_value = None + class NSSpeechRecognizerDelegate(AppKit.NSObject): + def speechRecognizer_didRecognizeCommand_(self, recognizer, cmd): + return_value = command_function_map[cmd]() + AppHelper.stopEventLoop() + + recognizer = AppKit.NSSpeechRecognizer.alloc().init() + recognizer.setCommands_(list(command_function_map.keys())) + recognizer.setBlocksOtherRecognizers_(True) + recognizer.setDelegate_(NSSpeechRecognizerDelegate.alloc().init().retain()) + recognizer.startListening() + AppHelper.runConsoleEventLoop() + + return return_value
+ + + + +
[docs]class XASpeechRecognizer(XAObject): + """A rule-based query detector. + + .. versionadded:: 0.0.9 + """ + def __init__(self, finish_conditions: Union[None, dict[Callable[[str], bool], Callable[[str], bool]]] = None): + """Creates a speech recognizer object. + + By default, with no other rules specified, the Speech Recognizer will timeout after 10 seconds once :func:`listen` is called. + + :param finish_conditions: A dictionary of rules and associated methods to call when a rule evaluates to true, defaults to None + :type finish_conditions: Union[None, dict[Callable[[str], bool], Callable[[str], bool]]], optional + + .. versionadded:: 0.0.9 + """ + default_conditions = { + lambda x: self.time_elapsed > timedelta(seconds = 10): lambda x: self.spoken_query, + } + self.finish_conditions: Callable[[str], bool] = finish_conditions or default_conditions #: A dictionary of rules and associated methods to call when a rule evaluates to true + self.spoken_query: str = "" #: The recognized spoken input + self.start_time: datetime #: The time that the Speech Recognizer begins listening + self.time_elapsed: timedelta #: The amount of time passed since the start time + + def __prepare(self): + # Request microphone access if we don't already have it + Speech.SFSpeechRecognizer.requestAuthorization_(None) + + # Set up audio session + self.audio_session = AVFoundation.AVAudioSession.sharedInstance() + self.audio_session.setCategory_mode_options_error_(AVFoundation.AVAudioSessionCategoryRecord, AVFoundation.AVAudioSessionModeMeasurement, AVFoundation.AVAudioSessionCategoryOptionDuckOthers, None) + self.audio_session.setActive_withOptions_error_(True, AVFoundation.AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation, None) + + # Set up recognition request + self.recognizer = Speech.SFSpeechRecognizer.alloc().init() + self.recognition_request = Speech.SFSpeechAudioBufferRecognitionRequest.alloc().init() + self.recognition_request.setShouldReportPartialResults_(True) + + # Set up audio engine + self.audio_engine = AVFoundation.AVAudioEngine.alloc().init() + self.input_node = self.audio_engine.inputNode() + recording_format = self.input_node.outputFormatForBus_(0) + self.input_node.installTapOnBus_bufferSize_format_block_(0, 1024, recording_format, + lambda buffer, _when: self.recognition_request.appendAudioPCMBuffer_(buffer)) + self.audio_engine.prepare() + self.audio_engine.startAndReturnError_(None) + +
[docs] def on_detect(self, rule: Callable[[str], bool], method: Callable[[str], bool]): + """Sets the given rule to call the specified method if a spoken query passes the rule. + + :param rule: A function that takes the spoken query as a parameter and returns a boolean value depending on whether the query passes a desired rule + :type rule: Callable[[str], bool] + :param method: A function that takes the spoken query as a parameter and acts on it + :type method: Callable[[str], bool] + + .. versionadded:: 0.0.9 + """ + self.finish_conditions[rule] = method
+ +
[docs] def listen(self) -> Any: + """Begins listening for a query until a rule returns True. + + :return: The value returned by the method invoked upon matching some rule + :rtype: Any + + .. versionadded:: 0.0.9 + """ + self.start_time = datetime.now() + self.time_elapsed = None + self.__prepare() + + old_self = self + def detect_speech(transcription, error): + if error is not None: + print("Failed to detect speech. Error: ", error) + else: + old_self.spoken_query = transcription.bestTranscription().formattedString() + print(old_self.spoken_query) + + recognition_task = self.recognizer.recognitionTaskWithRequest_resultHandler_(self.recognition_request, detect_speech) + while self.spoken_query == "" or not any(x(self.spoken_query) for x in self.finish_conditions): + self.time_elapsed = datetime.now() - self.start_time + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.5)) + + self.audio_engine.stop() + for rule, method in self.finish_conditions.items(): + if rule(self.spoken_query): + return method(self.spoken_query)
+ + + + +
[docs]class XASpeech(XAObject): + def __init__(self, message: str = "", voice: Union[str, None] = None, volume: float = 0.5, rate: int = 200): + self.message: str = message #: The message to speak + self.voice: Union[str, None] = voice #: The voice that the message is spoken in + self.volume: float = volume #: The speaking volume + self.rate: int = rate #: The speaking rate + +
[docs] def voices(self) -> list[str]: + """Gets the list of voice names available on the system. + + :return: The list of voice names + :rtype: list[str] + + :Example: + + >>> import PyXA + >>> speaker = PyXA.XASpeech() + >>> print(speaker.voices()) + ['Agnes', 'Alex', 'Alice', 'Allison', + + .. versionadded:: 0.0.9 + """ + ls = AppKit.NSSpeechSynthesizer.availableVoices() + return [x.replace("com.apple.speech.synthesis.voice.", "").replace(".premium", "").title() for x in ls]
+ +
[docs] def speak(self, path: Union[str, XAPath, None] = None): + """Speaks the provided message using the desired voice, volume, and speaking rate. + + :param path: The path to a .AIFF file to output sound to, defaults to None + :type path: Union[str, XAPath, None], optional + + :Example 1: Speak a message aloud + + >>> import PyXA + >>> PyXA.XASpeech("This is a test").speak() + + :Example 2: Output spoken message to an AIFF file + + >>> import PyXA + >>> speaker = PyXA.XASpeech("Hello, world!") + >>> speaker.speak("/Users/steven/Downloads/Hello.AIFF") + + :Example 3: Control the voice, volume, and speaking rate + + >>> import PyXA + >>> speaker = PyXA.XASpeech( + >>> message = "Hello, world!", + >>> voice = "Alex", + >>> volume = 1, + >>> rate = 500 + >>> ) + >>> speaker.speak() + + .. versionadded:: 0.0.9 + """ + # Get the selected voice by name + voice = None + for v in AppKit.NSSpeechSynthesizer.availableVoices(): + if self.voice.lower() in v.lower(): + voice = v + + # Set up speech synthesis object + synthesizer = AppKit.NSSpeechSynthesizer.alloc().initWithVoice_(voice) + synthesizer.setVolume_(self.volume) + synthesizer.setRate_(self.rate) + + # Start speaking + if path is None: + synthesizer.startSpeakingString_(self.message) + else: + if isinstance(path, str): + path = XAPath(path) + synthesizer.startSpeakingString_toURL_(self.message, path.xa_elem) + + # Wait for speech to complete + while synthesizer.isSpeaking(): + time.sleep(0.01)
+ + + + +
[docs]class XASpotlight(XAObject): + """A Spotlight query for files on the disk. + + .. versionadded:: 0.0.9 + """ + def __init__(self, *query: list[Any]): + self.query: list[Any] = query #: The query terms to search + self.timeout: int = 10 #: The amount of time in seconds to timeout the search after + self.predicate: Union[str, XAPredicate] = None #: The predicate to filter search results by + self.results: list[XAPath] #: The results of the search + self.__results = None + + self.query_object = AppKit.NSMetadataQuery.alloc().init() + nc = AppKit.NSNotificationCenter.defaultCenter() + nc.addObserver_selector_name_object_(self, '_queryNotification:', None, self.query_object) + + @property + def results(self) -> list['XAPath']: + if len(self.query) == 0 and self.predicate is None: + return [] + self.run() + total_time = 0 + while self.__results is None and total_time < self.timeout: + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.01)) + total_time += 0.01 + if self.__results is None: + return [] + return self.__results + +
[docs] def run(self): + """Runs the search. + + :Example: + + >>> import PyXA + >>> from datetime import date, datetime, time + >>> date1 = datetime.combine(date(2022, 5, 17), time(0, 0, 0)) + >>> date2 = datetime.combine(date(2022, 5, 18), time(0, 0, 0)) + >>> search = PyXA.XASpotlight(date1, date2) + >>> print(search.results) + [<<class 'PyXA.XAPath'>file:///Users/exampleUser/Downloads/>, <<class 'PyXA.XAPath'>file:///Users/exampleUser/Downloads/Example.txt>, ...] + + .. versionadded:: 0.0.9 + """ + if self.predicate is not None: + # Search with custom predicate + if isinstance(self.predicate, XAPredicate): + self.predicate = self.predicate.get_clipboard_representation() + self.__search_with_predicate(self.predicate) + elif len(self.query) == 1 and isinstance(self.query[0], datetime): + # Search date + or - 24 hours + self.__search_by_date(self.query) + elif len(self.query) == 2 and isinstance(self.query[0], datetime) and isinstance(self.query[1], datetime): + # Search date range + self.__search_by_date_range(self.query[0], self.query[1]) + elif all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query): + # Search matching multiple strings + self.__search_by_strs(self.query) + elif isinstance(self.query[0], datetime) and all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query[1:]): + # Search by date and string + self.__search_by_date_strings(self.query[0], self.query[1:]) + elif isinstance(self.query[0], datetime) and isinstance(self.query[1], datetime) and all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query[2:]): + # Search by date range and string + self.__search_by_date_range_strings(self.query[0], self.query[1], self.query[2:]) + + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.01))
+ +
[docs] def show_in_finder(self): + """Shows the search in Finder. This might not reveal the same search results. + + .. versionadded:: 0.0.9 + """ + AppKit.NSWorkspace.sharedWorkspace().showSearchResultsForQueryString_(str(self.query))
+ + def __search_by_strs(self, terms: tuple[str]): + expanded_terms = [[x]*3 for x in terms] + expanded_terms = [x for sublist in expanded_terms for x in sublist] + format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) + self.__search_with_predicate(format[:-5], *expanded_terms) + + def __search_by_date(self, date: datetime): + self.__search_with_predicate(f"((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@))", *[date - timedelta(hours=12), date + timedelta(hours=12)]*5) + + def __search_by_date_range(self, date1: datetime, date2: datetime): + self.__search_with_predicate(f"((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@))", *[date1, date2]*5) + + def __search_by_date_strings(self, date: datetime, terms: tuple[str]): + expanded_terms = [[x]*3 for x in terms] + expanded_terms = [x for sublist in expanded_terms for x in sublist] + format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) + format += "(((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@)))" + self.__search_with_predicate(format, *expanded_terms, *[date - timedelta(hours=12), date + timedelta(hours=12)]*5) + + def __search_by_date_range_strings(self, date1: datetime, date2: datetime, terms: tuple[str]): + expanded_terms = [[x]*3 for x in terms] + expanded_terms = [x for sublist in expanded_terms for x in sublist] + format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) + format += "(((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@)))" + self.__search_with_predicate(format, *expanded_terms, *[date1, date2]*5) + + def __search_with_predicate(self, predicate_format: str, *args: list[Any]): + predicate = AppKit.NSPredicate.predicateWithFormat_(predicate_format, *args) + self.query_object.setPredicate_(predicate) + self.query_object.startQuery() + + def _queryNotification_(self, notification): + if notification.name() == AppKit.NSMetadataQueryDidFinishGatheringNotification: + self.query_object.stopQuery() + results = notification.object().results() + self.__results = [XAPath(x.valueForAttribute_(AppKit.NSMetadataItemPathKey)) for x in results]
+ + + + +###################### +### User Interface ### +###################### +
[docs]class XAUIElementList(XAList): + """A wrapper around a list of UI elements. + + All properties of UI elements can be accessed via methods on this list, returning a list of the method's return value for each element in the list. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_type = None): + if obj_type is None: + obj_type = XAUIElement + super().__init__(properties, obj_type, filter) + +
[docs] def properties(self) -> list[dict]: + return list(self.xa_elem.arrayByApplyingSelector_("properties"))
+ +
[docs] def accessibility_description(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("accessibilityDescription"))
+ +
[docs] def enabled(self) -> list[bool]: + return list(self.xa_elem.arrayByApplyingSelector_("enabled"))
+ +
[docs] def entire_contents(self) -> list[list[Any]]: + return list(self.xa_elem.arrayByApplyingSelector_("entireContents"))
+ +
[docs] def focused(self) -> list[bool]: + return list(self.xa_elem.arrayByApplyingSelector_("focused"))
+ +
[docs] def name(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def title(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("title"))
+ +
[docs] def position(self) -> list[tuple[tuple[int, int], tuple[int, int]]]: + return list(self.xa_elem.arrayByApplyingSelector_("position"))
+ +
[docs] def size(self) -> list[tuple[int, int]]: + return list(self.xa_elem.arrayByApplyingSelector_("size"))
+ +
[docs] def maximum_value(self) -> list[Any]: + return list(self.xa_elem.arrayByApplyingSelector_("maximumValue"))
+ +
[docs] def minimum_value(self) -> list[Any]: + return list(self.xa_elem.arrayByApplyingSelector_("minimumValue"))
+ +
[docs] def value(self) -> list[Any]: + return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ +
[docs] def role(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("role"))
+ +
[docs] def role_description(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("roleDescription"))
+ +
[docs] def subrole(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("subrole"))
+ +
[docs] def selected(self) -> list[bool]: + return list(self.xa_elem.arrayByApplyingSelector_("selected"))
+ +
[docs] def by_properties(self, properties: dict) -> 'XAUIElement': + return self.by_property("properties", properties)
+ +
[docs] def by_accessibility_description(self, accessibility_description: str) -> 'XAUIElement': + return self.by_property("accessibilityDescription", accessibility_description)
+ +
[docs] def by_entire_contents(self, entire_contents: list[Any]) -> 'XAUIElement': + return self.by_property("entireContents", entire_contents)
+ +
[docs] def by_focused(self, focused: bool) -> 'XAUIElement': + return self.by_property("focused", focused)
+ +
[docs] def by_name(self, name: str) -> 'XAUIElement': + return self.by_property("name", name)
+ +
[docs] def by_title(self, title: str) -> 'XAUIElement': + return self.by_property("title", title)
+ +
[docs] def by_position(self, position: tuple[tuple[int, int], tuple[int, int]]) -> 'XAUIElement': + return self.by_property("position", position)
+ +
[docs] def by_size(self, size: tuple[int, int]) -> 'XAUIElement': + return self.by_property("size", size)
+ +
[docs] def by_maximum_value(self, maximum_value: Any) -> 'XAUIElement': + return self.by_property("maximumValue", maximum_value)
+ +
[docs] def by_minimum_value(self, minimum_value: Any) -> 'XAUIElement': + return self.by_property("minimumValue", minimum_value)
+ +
[docs] def by_value(self, value: Any) -> 'XAUIElement': + return self.by_property("value", value)
+ +
[docs] def by_role(self, role: str) -> 'XAUIElement': + return self.by_property("role", role)
+ +
[docs] def by_role_description(self, role_description: str) -> 'XAUIElement': + return self.by_property("roleDescription", role_description)
+ +
[docs] def by_subrole(self, subrole: str) -> 'XAUIElement': + return self.by_property("subrole", subrole)
+ +
[docs] def by_selected(self, selected: bool) -> 'XAUIElement': + return self.by_property("selected", selected)
+ +
[docs]class XAUIElement(XAObject): + def __init__(self, properties): + super().__init__(properties) + + self.properties: dict #: All properties of the UI element + self.accessibility_description: str #: The accessibility description of the element + self.enabled: bool #: Whether the UI element is currently enabled + self.entire_contents: Any #: The entire contents of the element + self.focused: bool #: Whether the window is the currently element + self.name: str #: The name of the element + self.title: str #: The title of the element (often the same as its name) + self.position: tuple[int, int] #: The position of the top left corner of the element + self.size: tuple[int, int] #: The width and height of the element, in pixels + self.maximum_value: Any #: The maximum value that the element can have + self.minimum_value: Any #: The minimum value that the element can have + self.value: Any #: The current value of the element + self.role: str #: The element's role + self.role_description: str #: The description of the element's role + self.subrole: str #: The subrole of the UI element + self.selected: bool #: Whether the element is currently selected + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def accessibility_description(self) -> str: + return self.xa_elem.accessibilityDescription() + + @property + def enabled(self) -> bool: + return self.xa_elem.enabled() + + @property + def entire_contents(self) -> list[XAObject]: + ls = self.xa_elem.entireContents() + return [self._new_element(x, XAUIElement) for x in ls] + + @property + def focused(self) -> bool: + return self.xa_elem.focused() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def title(self) -> str: + return self.xa_elem.title() + + @property + def position(self) -> tuple[int, int]: + return self.xa_elem.position() + + @property + def size(self) -> tuple[int, int]: + return self.xa_elem.size() + + @property + def maximum_value(self) -> Any: + return self.xa_elem.maximumValue() + + @property + def minimum_value(self) -> Any: + return self.xa_elem.minimumValue() + + @property + def value(self) -> Any: + return self.xa_elem.value() + + @property + def role(self) -> str: + return self.xa_elem.role() + + @property + def role_description(self) -> str: + return self.xa_elem.roleDescription() + + @property + def subrole(self) -> str: + return self.xa_elem.subrole() + + @property + def selected(self) -> bool: + return self.xa_elem.selected() + +
[docs] def ui_elements(self, filter: dict = None) -> 'XAUIElementList': + return self._new_element(self.xa_elem.UIElements(), XAUIElementList, filter)
+ +
[docs] def windows(self, filter: dict = None) -> 'XAWindowList': + return self._new_element(self.xa_elem.windows(), XAWindowList, filter)
+ +
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': + return self._new_element(self.xa_elem.menuBars(), XAUIMenuBarList, filter)
+ +
[docs] def menu_bar_items(self, filter: dict = None) -> 'XAUIMenuBarItemList': + return self._new_element(self.xa_elem.menuBarItems(), XAUIMenuBarItemList, filter)
+ +
[docs] def menus(self, filter: dict = None) -> 'XAUIMenuList': + return self._new_element(self.xa_elem.menus(), XAUIMenuList, filter)
+ +
[docs] def menu_items(self, filter: dict = None) -> 'XAUIMenuItemList': + return self._new_element(self.xa_elem.menuItems(), XAUIMenuItemList, filter)
+ +
[docs] def splitters(self, filter: dict = None) -> 'XAUISplitterList': + return self._new_element(self.xa_elem.splitters(), XAUISplitterList, filter)
+ +
[docs] def toolbars(self, filter: dict = None) -> 'XAUIToolbarList': + return self._new_element(self.xa_elem.toolbars(), XAUIToolbarList, filter)
+ +
[docs] def tab_groups(self, filter: dict = None) -> 'XAUITabGroupList': + return self._new_element(self.xa_elem.tabGroups(), XAUITabGroupList, filter)
+ +
[docs] def scroll_areas(self, filter: dict = None) -> 'XAUIScrollAreaList': + return self._new_element(self.xa_elem.scrollAreas(), XAUIScrollAreaList, filter)
+ +
[docs] def groups(self, filter: dict = None) -> 'XAUIGroupList': + return self._new_element(self.xa_elem.groups(), XAUIGroupList, filter)
+ +
[docs] def buttons(self, filter: dict = None) -> 'XAButtonList': + return self._new_element(self.xa_elem.buttons(), XAButtonList, filter)
+ +
[docs] def radio_buttons(self, filter: dict = None) -> 'XAUIRadioButtonList': + return self._new_element(self.xa_elem.radioButtons(), XAUIRadioButtonList, filter)
+ +
[docs] def actions(self, filter: dict = None) -> 'XAUIActionList': + return self._new_element(self.xa_elem.actions(), XAUIActionList, filter)
+ +
[docs] def text_fields(self, filter: dict = None) -> 'XAUITextfieldList': + return self._new_element(self.xa_elem.textFields(), XAUITextfieldList, filter)
+ +
[docs] def static_texts(self, filter: dict = None) -> 'XAUIStaticTextList': + return self._new_element(self.xa_elem.staticTexts(), XAUIStaticTextList, filter)
+ + + + +
[docs]class XAWindowList(XAUIElementList): + """A wrapper around a list of windows. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAWindow) + +
[docs] def collapse(self): + """Collapses all windows in the list. + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Keychain Access") + >>> app.windows().collapse() + + .. versionadded:: 0.0.5 + """ + for window in self: + window.collapse()
+ +
[docs] def uncollapse(self): + """Uncollapses all windows in the list. + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Keychain Access") + >>> app.windows().uncollapse() + + .. versionadded:: 0.0.6 + """ + for window in self: + window.uncollapse()
+ +
[docs] def close(self): + """Closes all windows in the list.add() + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Keychain Access") + >>> app.windows().close() + + .. versionadded:: 0.0.6 + """ + for window in self: + window.close()
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title()) + ">"
+ +
[docs]class XAWindow(XAUIElement): + """A general window class for windows of both officially scriptable and non-scriptable applications. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def close(self) -> 'XAWindow': + """Collapses (minimizes) the window. + + :return: A reference to the now-collapsed window object. + :rtype: XAWindow + + :Example: + + >>> import PyXA + >>> PyXA.Application("App Store").front_window.close() + + .. versionadded:: 0.0.1 + """ + close_button = self.buttons({"subrole": "AXCloseButton"})[0] + close_button.click() + return self
+ +
[docs] def collapse(self) -> 'XAWindow': + """Collapses (minimizes) the window. + + :return: A reference to the now-collapsed window object. + :rtype: XAWindow + + :Example: + + >>> import PyXA + >>> PyXA.Application("App Store").front_window.collapse() + + .. versionadded:: 0.0.1 + """ + if hasattr(self.xa_elem.properties(), "miniaturized"): + self.xa_elem.setValue_forKey_(True, "miniaturized") + else: + close_button = self.buttons({"subrole": "AXMinimizeButton"})[0] + close_button.click() + return self
+ +
[docs] def uncollapse(self) -> 'XAWindow': + """Uncollapses (unminimizes/expands) the window. + + :return: A reference to the uncollapsed window object. + :rtype: XAWindow + + :Example: + + >>> import PyXA + >>> PyXA.Application("App Store").front_window.uncollapse() + + .. versionadded:: 0.0.1 + """ + ls = self.xa_sevt.applicationProcesses() + dock_process = XAPredicate.evaluate_with_format(ls, "name == 'Dock'")[0] + + ls = dock_process.lists()[0].UIElements() + name = self.name + + app_icon = XAPredicate.evaluate_with_format(ls, f"name == '{name}'")[0] + app_icon.actions()[0].perform() + return self
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title) + ">"
+ + + + +
[docs]class XAUIMenuBarList(XAUIElementList): + """A wrapper around a list of menu bars. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenuBar)
+ +
[docs]class XAUIMenuBar(XAUIElement): + """A menubar UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIMenuBarItemList(XAUIElementList): + """A wrapper around a list of menu bar items. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenuBarItem)
+ +
[docs]class XAUIMenuBarItem(XAUIElement): + """A menubar item UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIMenuList(XAUIElementList): + """A wrapper around a list of menus. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenu)
+ +
[docs]class XAUIMenu(XAUIElement): + """A menu UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIMenuItemList(XAUIElementList): + """A wrapper around a list of menu items. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenuItem)
+ +
[docs]class XAUIMenuItem(XAUIElement): + """A menu item UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def click(self) -> 'XAUIMenuItem': + """Clicks the menu item. Synonymous with :func:`press`. + + :return: The menu item object. + :rtype: XAUIMenuItem + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ +
[docs] def press(self) -> 'XAUIMenuItem': + """Clicks the menu item. Synonymous with :func:`click`. + + :return: The menu item object. + :rtype: XAUIMenuItem + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ + + + +
[docs]class XAUISplitterList(XAUIElementList): + """A wrapper around a list of splitters. + + .. versionadded:: 0.0.8 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUISplitter)
+ +
[docs]class XAUISplitter(XAUIElement): + """A splitter UI element. + + .. versionadded:: 0.0.8 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIToolbarList(XAUIElementList): + """A wrapper around a list of toolbars. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIToolbar)
+ +
[docs]class XAUIToolbar(XAUIElement): + """A toolbar UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIGroupList(XAUIElementList): + """A wrapper around a list of UI element groups. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIGroup)
+ +
[docs]class XAUIGroup(XAUIElement): + """A group of UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUITabGroupList(XAUIElementList): + """A wrapper around a list of UI element tab groups. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUITabGroup)
+ +
[docs]class XAUITabGroup(XAUIElement): + """A tab group UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIScrollAreaList(XAUIElementList): + """A wrapper around a list of scroll areas. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIScrollArea)
+ +
[docs]class XAUIScrollArea(XAUIElement): + """A scroll area UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAButtonList(XAUIElementList): + """A wrapper around lists of buttons that employs fast enumeration techniques. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAButton)
+ +
[docs]class XAButton(XAUIElement): + """A button UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def click(self) -> 'XAButton': + """Clicks the button. Synonymous with :func:`press`. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ +
[docs] def press(self) -> 'XAButton': + """Clicks the button. Synonymous with :func:`click`. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ +
[docs] def option_click(self) -> 'XAButton': + """Option-Clicks the button. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXZoomWindow"})[0].perform() + return self
+ +
[docs] def show_menu(self) -> 'XAButton': + """Right clicks the button, invoking a menu. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXShowMenu"})[0].perform() + return self
+ + + + +
[docs]class XAUIRadioButtonList(XAUIElementList): + """A wrapper around lists of radio buttons that employs fast enumeration techniques. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIRadioButton)
+ +
[docs]class XAUIRadioButton(XAUIElement): + """A radio button UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIActionList(XAUIElementList): + """A wrapper around a list of UI element actions. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIAction)
+ +
[docs]class XAUIAction(XAUIElement): + """An action associated with a UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def perform(self): + """Executes the action. + + .. versionadded:: 0.0.2 + """ + self.xa_elem.perform()
+ + + + +
[docs]class XAUITextfieldList(XAUIElementList): + """A wrapper around a list of textfields. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUITextfield)
+ +
[docs]class XAUITextfield(XAUIElement): + """A textfield UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIStaticTextList(XAUIElementList): + """A wrapper around a list of static text elements. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIStaticText)
+ +
[docs]class XAUIStaticText(XAUIElement): + """A static text UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + +############ +### Text ### +############ +
[docs]class XATextDocumentList(XAList, XAClipboardCodable): + """A wrapper around lists of text documents that employs fast enumeration techniques. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XATextDocument + super().__init__(properties, obj_class, filter) + +
[docs] def properties(self) -> list[dict]: + """Gets the properties of each document in the list. + + :return: A list of document properties dictionaries + :rtype: list[dict] + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("properties") + return [dict(x) for x in ls]
+ +
[docs] def text(self) -> 'XATextList': + """Gets the text of each document in the list. + + :return: A list of document texts + :rtype: XATextList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("text") + return self._new_element(ls, XATextList)
+ +
[docs] def by_properties(self, properties: dict) -> Union['XATextDocument', None]: + """Retrieves the document whose properties match the given properties dictionary, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XATextDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("properties", properties)
+ +
[docs] def by_text(self, text: str) -> Union['XATextDocument', None]: + """Retrieves the first documents whose text matches the given text. + + :return: The desired document, if it is found + :rtype: Union[XATextDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("text", text)
+ +
[docs] def paragraphs(self) -> 'XAParagraphList': + """Gets the paragraphs of each document in the list. + + :return: A combined list of all paragraphs in each document of the list + :rtype: XAParagraphList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("paragraphs") + return self._new_element([plist for plist in ls], XAParagraphList)
+ +
[docs] def words(self) -> 'XAWordList': + """Gets the words of each document in the list. + + :return: A combined list of all words in each document of the list + :rtype: XAWordList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("words") + return [self._new_element([plist for plist in ls], XAWordList)]
+ +
[docs] def characters(self) -> 'XACharacterList': + """Gets the characters of each document in the list. + + :return: A combined list of all characters in each document of the list + :rtype: XACharacterList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("characters") + return [self._new_element([plist for plist in ls], XACharacterList)]
+ +
[docs] def attribute_runs(self) -> 'XAAttributeRunList': + """Gets the attribute runs of each document in the list. + + :return: A combined list of all attribute runs in each document of the list + :rtype: XAAttributeRunList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("attributeRuns") + return [self._new_element([plist for plist in ls], XAAttributeRunList)]
+ +
[docs] def attachments(self) -> 'XAAttachmentList': + """Gets the attachments of each document in the list. + + :return: A combined list of all attachments in each document of the list + :rtype: XAAttachmentList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("attachments") + return [self._new_element([plist for plist in ls], XAAttachmentList)]
+ +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of each document in the list. + + When the clipboard content is set to a list of documents, each documents's file URL and name are added to the clipboard. + + :return: A list of each document's file URL and name + :rtype: list[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + return [str(x) for x in self.text()]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.text()) + ">"
+ +
[docs]class XATextDocument(XAObject): + """A class for managing and interacting with text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties) + self.text: XAText #: The text of the document. + + @property + def text(self) -> 'XAText': + return self._new_element(self.xa_elem.text(), XAText) + + @text.setter + def text(self, text: str): + self.set_property("text", text) + +
[docs] def set_text(self, new_text: str) -> 'XATextDocument': + """Sets the text of the document. + + :param new_text: The new text of the document + :type new_text: str + :return: A reference to the document object. + :rtype: XATextDocument + + .. seealso:: :func:`prepend`, :func:`append` + + .. deprecated:: 0.1.0 + Directly set the text attribute instead. + + .. versionadded:: 0.0.1 + """ + self.set_property("text", new_text) + return self
+ +
[docs] def prepend(self, text: str) -> 'XATextDocument': + """Inserts the provided text at the beginning of the document. + + :param text: The text to insert. + :type text: str + :return: A reference to the document object. + :rtype: XATextDocument + + .. seealso:: :func:`append`, :func:`set_text` + + .. versionadded:: 0.0.1 + """ + old_text = self.text + self.set_property("text", text + old_text) + return self
+ +
[docs] def append(self, text: str) -> 'XATextDocument': + """Appends the provided text to the end of the document. + + :param text: The text to append. + :type text: str + :return: A reference to the document object. + :rtype: XATextDocument + + .. seealso:: :func:`prepend`, :func:`set_text` + + .. versionadded:: 0.0.1 + """ + old_text = self.text + self.set_property("text", old_text + text) + return self
+ +
[docs] def reverse(self) -> 'XATextDocument': + """Reverses the text of the document. + + :return: A reference to the document object. + :rtype: XATextDocument + + .. versionadded:: 0.0.4 + """ + self.set_property("text", reversed(self.text)) + return self
+ +
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': + return self.text.paragraphs(filter)
+ +
[docs] def sentences(self, filter: dict = None) -> 'XASentenceList': + return self.text.sentences(filter)
+ +
[docs] def words(self, filter: dict = None) -> 'XAWordList': + return self.text.words(filter)
+ +
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': + return self.text.characters(filter)
+ +
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': + return self.text.attribute_runs(filter)
+ +
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': + return self.text.attachments(filter)
+ + + + +
[docs]class XATextList(XAList): + """A wrapper around lists of text objects that employs fast enumeration techniques. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XAText + super().__init__(properties, obj_class, filter) + +
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': + """Gets the paragraphs of every text item in the list. + + :return: The list of paragraphs + :rtype: XAParagraphList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("paragraphs") + else: + ls = [x.xa_elem.split("\n") for x in self] + ls = [paragraph for paragraphlist in ls for paragraph in paragraphlist if paragraph.strip() != ''] + return self._new_element(ls, XAParagraphList, filter)
+ +
[docs] def sentences(self) -> 'XASentenceList': + """Gets the sentences of every text item in the list. + + :return: The list of sentences + :rtype: XASentenceList + + .. versionadded:: 0.1.0 + """ + ls = [x.sentences() for x in self] + ls = [sentence for sentencelist in ls for sentence in sentencelist] + return self._new_element(ls, XASentenceList)
+ +
[docs] def words(self, filter: dict = None) -> 'XAWordList': + """Gets the words of every text item in the list. + + :return: The list of words + :rtype: XAWordList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("words") + else: + ls = [x.xa_elem.split() for x in self] + ls = [word for wordlist in ls for word in wordlist] + return self._new_element(ls, XAWordList, filter)
+ +
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': + """Gets the characters of every text item in the list. + + :return: The list of characters + :rtype: XACharacterList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("characters") + else: + ls = [list(x.xa_elem) for x in self] + ls = [character for characterlist in ls for character in characterlist] + return self._new_element(ls, XACharacterList, filter)
+ +
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': + """Gets the attribute runs of every text item in the list. + + :return: The list of attribute runs + :rtype: XAAttributeRunList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("attributeRuns") + ls = [attribute_run for attribute_run_list in ls for attribute_run in attribute_run_list] + return self._new_element(ls, XAAttributeRunList, filter)
+ +
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': + """Gets the attachments of every text item in the list. + + :return: The list of attachments + :rtype: XAAttachmentList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("attachments") + ls = [attachment for attachment_list in ls for attachment in attachment_list] + return self._new_element(ls, XAAttachmentList, filter)
+ + def __repr__(self): + try: + if isinstance(self.xa_elem[0], ScriptingBridge.SBObject): + # List items will not resolved to text upon dereferencing the list; need to resolve items individually + count = self.xa_elem.count() + if count <= 500: + # Too many unresolved pointers, save time by just reporting the length + return "<" + str(type(self)) + str([x.get() for x in self.xa_elem]) + ">" + return "<" + str(type(self)) + "length: " + str(self.xa_elem.count()) + ">" + + # List items will resolve to text upon dereferencing the list + return "<" + str(type(self)) + str(self.xa_elem.get()) + ">" + except: + return "<" + str(type(self)) + str(list(self.xa_elem)) + ">"
+ +
[docs]class XAText(XAObject): + """A class for managing and interacting with the text of documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + if isinstance(properties, dict): + super().__init__(properties) + elif isinstance(properties, str): + super().__init__({"element": properties}) + + self.text: str #: The plaintext contents of the rich text + self.color: XAColor #: The color of the first character + self.font: str #: The name of the font of the first character + self.size: int #: The size in points of the first character + + @property + def text(self) -> str: + if isinstance(self.xa_elem, str): + return self.xa_elem + else: + return self.xa_elem.text() + + @text.setter + def text(self, text: str): + if isinstance(self.xa_elem, str): + self.xa_elem = text + else: + self.set_property("text", text) + + @property + def color(self) -> 'XAColor': + if isinstance(self.xa_elem, str): + return None + else: + return XAColor(self.xa_elem.color()) + + @color.setter + def color(self, color: 'XAColor'): + if isinstance(self.xa_elem, str): + self.color = color.xa_elem + else: + self.set_property("color", color.xa_elem) + + @property + def font(self) -> str: + if isinstance(self.xa_elem, str): + return None + else: + return self.xa_elem.font() + + @font.setter + def font(self, font: str): + if isinstance(self.xa_elem, str): + self.font = font + else: + self.set_property("font", font) + + @property + def size(self) -> int: + if isinstance(self.xa_elem, str): + return 0 + else: + return self.xa_elem.size() + + @size.setter + def size(self, size: int): + if isinstance(self.xa_elem, str): + self.size = size + else: + self.set_property("size", size) + + # def spelling_suggestions(self): + # suggestions = [] + # text = str(self.xa_elem) + # spellchecker = AppKit.NSSpellChecker.sharedSpellChecker() + + # orthography = None + # word_count = 0 + + # pprint(dir(LatentSemanticMapping.LSMMapCreate(None, 0))) + + # # c = spellchecker.checkString_range_types_options_inSpellDocumentWithTag_orthography_wordCount_(text, (0, len(text)), AppKit.NSTextCheckingTypeSpelling | AppKit.NSTextCheckingTypeGrammar | AppKit.NSTextCheckingTypeCorrection, {}, 0, orthography, None) + # # print(c[1].languageMap()) + + # for word in text.split(): + # completions = spellchecker.completionsForPartialWordRange_inString_language_inSpellDocumentWithTag_((0, len(word)), word, "", 0) + # suggestions.append(completions) + + # # for word in text.split(): + # # guesses = spellchecker.guessesForWordRange_inString_language_inSpellDocumentWithTag_((0, len(word)), word, "", 0) + # # suggestions.append(guesses) + # return suggestions + +
[docs] def tag_parts_of_speech(self, unit: Literal["word", "sentence", "paragraph", "document"] = "word") -> list[tuple[str, str]]: + """Tags each word of the text with its associated part of speech. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "word" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each word of the text and its part of speech + :rtype: list[tuple[str, str]] + + :Example 1: Extract nouns from a text + + >>> import PyXA + >>> text = PyXA.XAText("Here’s to the crazy ones. The misfits. The rebels.") + >>> nouns = [pos[0] for pos in text.tag_parts_of_speech() if pos[1] == "Noun"] + >>> print(nouns) + ['ones', 'misfits', 'rebels'] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeLexicalClass]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_pos = [] + def apply_tags(tag, token_range, error): + word_phrase = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + tagged_pos.append((word_phrase, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeLexicalClass, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace, apply_tags) + return tagged_pos
+ +
[docs] def tag_languages(self, unit: Literal["word", "sentence", "paragraph", "document"] = "paragraph") -> list[tuple[str, str]]: + """Tags each paragraph of the text with its language. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "paragraph" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each paragraph of the text and its language + :rtype: list[tuple[str, str]] + + :Example: + + >>> import PyXA + >>> text = PyXA.XAText("This is English.\nQuesto è Italiano.\nDas ist deutsch.\nこれは日本語です。") + >>> print(text.tag_languages()) + [('This is English.\n', 'en'), ('Questo è Italiano.\n', 'it'), ('Das ist deutsch.\n', 'de'), ('これは日本語です。', 'ja')] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeLanguage]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_languages = [] + def apply_tags(tag, token_range, error): + paragraph = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if paragraph.strip() != "": + tagged_languages.append((paragraph, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeLanguage, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace, apply_tags) + return tagged_languages
+ +
[docs] def tag_entities(self, unit: Literal["word", "sentence", "paragraph", "document"] = "word") -> list[tuple[str, str]]: + """Tags each word of the text with either the category of entity it represents (i.e. person, place, or organization) or its part of speech. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "word" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each word of the text and its entity category or part of speech + :rtype: list[tuple[str, str]] + + :Example: + + >>> import PyXA + >>> text = PyXA.XAText("Tim Cook is the CEO of Apple.") + >>> print(text.tag_entities()) + [('Tim', 'PersonalName'), ('Cook', 'PersonalName'), ('is', 'Verb'), ('the', 'Determiner'), ('CEO', 'Noun'), ('of', 'Preposition'), ('Apple', 'OrganizationName')] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeNameTypeOrLexicalClass]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_languages = [] + def apply_tags(tag, token_range, error): + word_phrase = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if word_phrase.strip() != "": + tagged_languages.append((word_phrase, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeNameTypeOrLexicalClass, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace, apply_tags) + return tagged_languages
+ +
[docs] def tag_lemmas(self, unit: Literal["word", "sentence", "paragraph", "document"] = "word") -> list[tuple[str, str]]: + """Tags each word of the text with its stem word. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "word" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each word of the text and its stem words + :rtype: list[tuple[str, str]] + + :Example 1: Lemmatize each word in a text + + >>> import PyXA + >>> text = PyXA.XAText("Here’s to the crazy ones. The misfits. The rebels.") + >>> print(text.tag_lemmas()) + [('Here’s', 'here'), ('to', 'to'), ('the', 'the'), ('crazy', 'crazy'), ('ones', 'one'), ('The', 'the'), ('misfits', 'misfit'), ('The', 'the'), ('rebels', 'rebel')] + + :Example 2: Combine parts of speech tagging and lemmatization + + >>> import PyXA + >>> text = PyXA.XAText("The quick brown fox tries to jump over the sleeping lazy dog.") + >>> verbs = [pos[0] for pos in text.tag_parts_of_speech() if pos[1] == "Verb"] + >>> for index, verb in enumerate(verbs): + >>> print(index, PyXA.XAText(verb).tag_lemmas()) + 0 [('tries', 'try')] + 1 [('jump', 'jump')] + 2 [('sleeping', 'sleep')] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeLemma]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_lemmas = [] + def apply_tags(tag, token_range, error): + word_phrase = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if word_phrase.strip() != "": + tagged_lemmas.append((word_phrase, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeLemma, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace | NaturalLanguage.NLTaggerJoinContractions, apply_tags) + return tagged_lemmas
+ +
[docs] def tag_sentiments(self, sentiment_scale: list[str] = None, unit: Literal["word", "sentence", "paragraph", "document"] = "paragraph") -> list[tuple[str, str]]: + """Tags each paragraph of the text with a sentiment rating. + + :param sentiment_scale: A list of terms establishing a range of sentiments from most negative to most postive + :type sentiment_scale: list[str] + :param unit: The grammatical unit to divide the text into for tagging, defaults to "paragraph" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each paragraph of the text and its sentiment rating + :rtype: list[tuple[str, str]] + + :Example 1: Assess the sentiment of a string + + >>> import PyXA + >>> text = PyXA.XAText("This sucks.\nBut this is great!") + >>> print(text.tag_sentiments()) + [('This sucks.\n', 'Negative'), ('But this is great!', 'Positive')] + + :Example 2: Use a custom sentiment scale + + >>> import PyXA + >>> text = PyXA.XAText("This sucks.\nBut this is good!\nAnd this is great!") + >>> print(text.tag_sentiments(sentiment_scale=["Very Negative", "Negative", "Somewhat Negative", "Neutral", "Somewhat Positive", "Positive", "Very Positive"])) + [('This sucks.\n', 'Very Negative'), ('But this is good!\n', 'Neutral'), ('And this is great!', 'Very Positive')] + + :Example 3: Use other tag units + + >>> import PyXA + >>> text = PyXA.XAText("This sucks.\nBut this is good!\nAnd this is great!") + >>> print(1, text.tag_sentiments()) + >>> print(2, text.tag_sentiments(unit="word")) + >>> print(3, text.tag_sentiments(unit="document")) + 1 [('This sucks.\n', 'Negative'), ('But this is good!\n', 'Neutral'), ('And this is great!', 'Positive')] + 2 [('This', 'Negative'), ('sucks', 'Negative'), ('.', 'Negative'), ('But', 'Neutral'), ('this', 'Neutral'), ('is', 'Neutral'), ('good', 'Neutral'), ('!', 'Neutral'), ('And', 'Positive'), ('this', 'Positive'), ('is', 'Positive'), ('great', 'Positive'), ('!', 'Positive')] + 3 [('This sucks.\nBut this is good!\nAnd this is great!', 'Neutral')] + + .. versionadded:: 0.1.0 + """ + if sentiment_scale is None or len(sentiment_scale) == 0: + sentiment_scale = ["Negative", "Neutral", "Positive"] + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeSentimentScore]) + tagger.setString_(str(self.xa_elem)) + + tagged_sentiments = [] + def apply_tags(tag, token_range, error): + paragraph = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if paragraph.strip() != "": + # Map raw tag value to range length + raw_value = float(tag or 0) + scaled = (raw_value + 1.0) / 2.0 * (len(sentiment_scale) - 1) + + label = sentiment_scale[int(scaled)] + tagged_sentiments.append((paragraph, label)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(self.xa_elem)), unit, NaturalLanguage.NLTagSchemeSentimentScore, 0, apply_tags) + return tagged_sentiments
+ +
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': + """Gets a list of paragraphs in the text. + + :param filter: The properties and associated values to filter paragraphs by, defaults to None + :type filter: dict, optional + :return: The list of paragraphs + :rtype: XAParagraphList + + :Example 1: Get paragraphs of a text string + + >>> import PyXA + >>> string = \"\"\"This is the first paragraph. + >>> + >>> This is the second paragraph.\"\"\" + >>> text = PyXA.XAText(string) + >>> print(text.paragraphs()) + <<class 'PyXA.XAWordList'>['This is the first paragraph.', 'This is the second paragraph. Neat! Very cool.']> + + :Example 2: Get paragraphs of a Note + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.paragraphs()) + <<class 'PyXA.XAWordList'>['This is the first paragraph.', 'This is the second paragraph. Neat! Very cool.']> + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + ls = [x for x in self.xa_elem.split("\n") if x.strip() != ''] + return self._new_element(ls, XAWordList, filter) + else: + return self._new_element(self.xa_elem.paragraphs(), XAParagraphList, filter)
+ +
[docs] def sentences(self) -> 'XASentenceList': + """Gets a list of sentences in the text. + + :return: The list of sentencnes + :rtype: XASentenceList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.sentences()) + <<class 'PyXA.XASentenceList'>['This is the first paragraph.\\n', '\\n', 'This is the second paragraph. ', 'Neat! ', 'Very cool.']> + + .. versionadded:: 0.1.0 + """ + raw_string = self.xa_elem + if hasattr(self.xa_elem, "get"): + raw_string = self.xa_elem.get() + + sentences = [] + tokenizer = AppKit.NLTokenizer.alloc().initWithUnit_(AppKit.kCFStringTokenizerUnitSentence) + tokenizer.setString_(raw_string) + for char_range in tokenizer.tokensForRange_((0, len(raw_string))): + start = char_range.rangeValue().location + end = start + char_range.rangeValue().length + sentences.append(raw_string[start:end]) + + ls = AppKit.NSArray.alloc().initWithArray_(sentences) + return self._new_element(sentences, XASentenceList)
+ +
[docs] def words(self, filter: dict = None) -> 'XAWordList': + """Gets a list of words in the text. + + :return: The list of words + :rtype: XAWordList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.words()) + <<class 'PyXA.XAWordList'>['This', 'is', 'the', 'first', 'paragraph.', 'This', 'is', 'the', 'second', 'paragraph.', 'Neat!', 'Very', 'cool.']> + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + ls = self.xa_elem.split() + return self._new_element(ls, XAWordList, filter) + else: + return self._new_element(self.xa_elem.words(), XAWordList, filter)
+ +
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': + """Gets a list of characters in the text. + + :return: The list of characters + :rtype: XACharacterList + + :Example 1: Get all characters in a text + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.characters()) + <<class 'PyXA.XACharacterList'>['T', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 'f', 'i', 'r', 's', 't', ' ', 'p', 'a', 'r', 'a', 'g', 'r', 'a', 'p', 'h', '.', '\n', '\n', 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 's', 'e', 'c', 'o', 'n', 'd', ' ', 'p', 'a', 'r', 'a', 'g', 'r', 'a', 'p', 'h', '.', ' ', 'N', 'e', 'a', 't', '!', ' ', 'V', 'e', 'r', 'y', ' ', 'c', 'o', 'o', 'l', '.']> + + :Example 2: Get the characters of the first word in a text + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.words()[0].characters()) + <<class 'PyXA.XACharacterList'>['T', 'h', 'i', 's']> + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + ls = list(self.xa_elem) + return self._new_element(ls, XACharacterList, filter) + else: + return self._new_element(self.xa_elem.characters().get(), XACharacterList, filter)
+ +
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': + """Gets a list of attribute runs in the text. For formatted text, this returns all sequences of characters sharing the same attributes. + + :param filter: The properties and associated values to filter attribute runs by, defaults to None + :type filter: dict, optional + :return: The list of attribute runs + :rtype: XAAttributeRunList + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + return [] + else: + return self._new_element(self.xa_elem.attributeRuns(), XAAttributeRunList, filter)
+ +
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': + """Gets a list of attachments of the text. + + :param filter: The properties and associated values to filter attachments by, defaults to None + :type filter: dict, optional + :return: The list of attachments + :rtype: XAAttachmentList + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + return [] + else: + return self._new_element(self.xa_elem.attachments(), XAAttachmentList, filter)
+ + def __len__(self): + return len(self.xa_elem.get()) + + def __str__(self): + if isinstance(self.xa_elem, str): + return self.xa_elem + return str(self.xa_elem.get()) + + def __repr__(self): + if isinstance(self.xa_elem, str): + return self.xa_elem + return str(self.xa_elem.get())
+ + + + +
[docs]class XAParagraphList(XATextList): + """A wrapper around lists of paragraphs that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAParagraph)
+ +
[docs]class XAParagraph(XAText): + """A class for managing and interacting with paragraphs in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + +
[docs]class XASentenceList(XATextList): + """A wrapper around lists of sentences that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XASentence)
+ +
[docs]class XASentence(XAText): + """A class for managing and interacting with sentences in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAWordList(XATextList): + """A wrapper around lists of words that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAWord)
+ +
[docs]class XAWord(XAText): + """A class for managing and interacting with words in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XACharacterList(XATextList): + """A wrapper around lists of characters that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XACharacter)
+ +
[docs]class XACharacter(XAText): + """A class for managing and interacting with characters in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAAttributeRunList(XATextList): + """A wrapper around lists of attribute runs that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAAttributeRun)
+ +
[docs]class XAAttributeRun(XAText): + """A class for managing and interacting with attribute runs in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAAttachmentList(XATextList): + """A wrapper around lists of text attachments that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAAttachment)
+ +
[docs]class XAAttachment(XAObject): + """A class for managing and interacting with attachments in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XALSM(XAObject): + def __init__(self, dataset: Union[dict[str, list[str]], None] = None, from_file: bool = False): + """Initializes a Latent Semantic Mapping environment. + + :param dataset: The initial dataset, specified as a dictionary where keys are categories and values are list of corresponding texts, defaults to None. Cannot be None if from_file is False. + :type dataset: Union[dict[str, list[str]], None], optional + :param from_file: Whether the LSM is being loaded from a file, defaults to False. Cannot be False is dataset is None. + :type from_file: bool, optional + :raises ValueError: Either you must provide a dataset, or you must load an existing map from an external file + + :Example 1: Classify emails based on subject line + + >>> import PyXA + >>> lsm = PyXA.XALSM({ + >>> # 1 + >>> "spam": ["Deals", "Holiday playbook", "Spend and save. You know the drill.", "Don't miss these holiday deals!", "GOOD NEWS", "you will never have an opportunity of this kind again", "Your Long Overdue Compensation Funds; Totally", "US $25,000,000.00", "goog day", "GOOD DAY, I am Mike Paul I have a", "enron methanol; meter # : 988291 this is a follow up to the note i gave you on monday , 4...", "hpl nom for january 9, see attached file : hplnol 09. xls", "neon retreat ho ho ho, we're around to that most wonderful time of the year", "photoshop, windows, office cheap, main trending abasements, darer prudently fortuitous", "re: indian springs this deal is to book the teco pvr revenue. it is my understanding that...", "noms / actual flow for 2 / we agree", "nominations for oct 21 - 23"], + >>> + >>> # 2 + >>> "kayak": ["Price Alert: Airfare holding steady for your trip", "Prices going down for your Boston to Dublin flight", "Price Increase: Boston to Dublin airfare up $184", "Flight Alert: #37 decrease on your flight to Dublin.", "Flight Alert: It's time to book your flight to Dublin", "Price Alert: Airfare holding steady for your Bangor, ME to...", "Ready to explore the world again?"], + >>> + >>> # 3 + >>> "lenovo": ["Doorbuster deals up to 70% off", "Visionary, On-Demand Content. Lenovo Tech World '22 is starting", "Up to 70% off deals 9 AM", "TGIF! Here's up to 70% off to jumpstart your weekend", "Top picks to refresh your workspace", "This only happens twice a year", "Think about saving on a Think PC", "Deep deals on Summer Clearance", "Save up to 67% + earn rewards", "Unlock up to 61% off Think PCs", "Giveaway alert!", "Annual Sale Sneak Peak Unlocked!"], + >>> + >>> # 4 + >>> "linkedin": ["Here is the latest post trending amongst your coworkers", "Stephen, add Sean Brown to your network", "Share thoughts on LinkedIn", "Top companies are hiring", "Linkedin is better on the app", "Here is the latest post trending amongst your coworkers", "Stephen, add Ronald McDonald to your network", "James Smith shared a post for the first time in a while", "Here is the latest post trending amongst your coworkers", "You appeared in 13 searches this week", "you're on a roll with your career!", "You appeared in 16 searches this week", "18 people notices you", "You appeared in 10 searches this week", "Stephen, add Joe Shmoe to your network", "Your network is talking: The Center for Oceanic Research...", "thanks for being a valued member"] + >>> }) + >>> print(lsm.categorize_query("New! Weekend-only deals")) + >>> print(lsm.categorize_query("Stephen, redeem these three (3) unlocked courses")) + >>> print(lsm.categorize_query("Round Trip From San Francisco to Atlanta")) + [(3, 0.9418474435806274)] + [(4, 0.9366401433944702)] + [(2, 0.9944692850112915)] + + :Example 2: Use the Mail module to automate dataset construction + + >>> import PyXA + >>> app = PyXA.Application("Mail") + >>> junk_subject_lines = app.accounts()[0].mailboxes().by_name("Junk").messages().subject() + >>> other_subject_lines = app.accounts()[0].mailboxes().by_name("INBOX").messages().subject() + >>> + >>> dataset = { + >>> "junk": junk_subject_lines, + >>> "other": other_subject_lines + >>> } + >>> lsm = PyXA.XALSM(dataset) + >>> + >>> query = "Amazon Web Services Billing Statement Available" + >>> category = list(dataset.keys())[lsm.categorize_query(query)[0][0] - 1] + >>> print(query, "- category:", category) + >>> + >>> query = "Complete registration form asap receive your rewards" + >>> category = list(dataset.keys())[lsm.categorize_query(query)[0][0] - 1] + >>> print(query, "- category:", category) + Amazon Web Services Billing Statement Available - category: other + Complete registration form asap receive your rewards - category: junk + + .. versionadded:: 0.1.0 + """ + self.__categories = {} + if dataset is None and not from_file: + raise ValueError("You must either load a map from an external file or provide an initial dataset.") + elif dataset is None: + # Map will be loaded from external file -- empty dataset is temporary + self.__dataset = {} + else: + # Create new map + self.__dataset = dataset + + self.map = LatentSemanticMapping.LSMMapCreate(None, 0) + LatentSemanticMapping.LSMMapStartTraining(self.map) + LatentSemanticMapping.LSMMapSetProperties(self.map, { + LatentSemanticMapping.kLSMSweepCutoffKey: 0, + # LatentSemanticMapping.kLSMPrecisionKey: LatentSemanticMapping.kLSMPrecisionDouble, + LatentSemanticMapping.kLSMAlgorithmKey: LatentSemanticMapping.kLSMAlgorithmSparse, + }) + + for category in dataset: + self.__add_category(category) + + LatentSemanticMapping.LSMMapCompile(self.map) + + def __add_category(self, category: str) -> int: + loc = Foundation.CFLocaleGetSystem() + category_ref = LatentSemanticMapping.LSMMapAddCategory(self.map) + self.__categories[category] = category_ref + self.__categories[category_ref] = category_ref + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, " ".join(self.__dataset[category]), loc, LatentSemanticMapping.kLSMTextPreserveAcronyms) + LatentSemanticMapping.LSMMapAddText(self.map, text_ref, category_ref) + return category_ref + +
[docs] def save(self, file_path: Union[XAPath, str]) -> bool: + """Saves the map to an external file. + + :param file_path: The path to save the map at + :type file_path: Union[XAPath, str] + :return: True if the map was saved successfully + :rtype: bool + + :Example: Create a Reddit post classifier for gaming vs. productivity posts + + >>> import PyXA + >>> lsm = PyXA.XALSM({ + >>> # 1 + >>> "gaming": ["If you could vote on the 2017 Mob Vote again, which mob would you choose this time and why?", "Take your time, you got this", "My parents (late 70s) got me a ps5 controller for Christmas. I do not own a playstation 5...", "I got off the horse by accident right before a cutscene in red dead", "boy gamer", "Minesweeper 99 x 99, 1500 mines. Took me about 2.5 hours to finish, nerve-wracking. No one might care, but just wanted to share this.", "The perfect cosplay doesn’t ex...", "'Play until we lose'", "Can we please boycott Star Wars battlefront 2", "EA removed the refund button on their webpage, and now you have to call them and wait to get a refund.", "Train Simulator is so immersive!", "Been gaming with this dude for 15 years. Since Rainbow Six Vegas on 360. I have some good gaming memories with him. He tried but couldn’t get one. Little did he know I was able to get him one. Looking forward to playing another generation with him.", "EA will no longer have exclusive rights of the Star Wars games", "A ziplining contraption I created with 1000+ command blocks", "The steepest walkable staircase possible in 1.16", "I made a texture pack that gives mobs different facial expressions. Should I keep going?"], + >>> + >>> # 2 + >>> "productivity": ["Looking for an alarm app that plays a really small alarm, doesn’t need to be switched off and doesn’t repeat.", "I want to build a second brain but I'm lost and don't know where to start.", "noise cancelling earplugs", "I have so much to do but I don't know where to start", "How to handle stressful work calls", "time tracking app/platform", "We just need to find ways to cope and keep moving forward.", "Ikigai - A Reason for Being", "Minimalist Productivity Tip: create two users on your computer ➞ One for normal use and leisure ➞ One for business/work only. I have nothing except the essentials logged in on my work user. Not even Messages or YouTube. It completely revolutionized my productivity 💸", "Trick yourself into productivity the same way you trick yourself into procrastination", "I spent 40 hours sifting through research papers to fix my mental clarity, focus, and productivity - I ended up going down a rabbit hole and figuring out it was all tied to sleep, even though I felt I sleep well - here's what I found.", "The Cycle of Procrastination. Always a good reminder", "'Most people underestimate what they can do in a year, and overestimate what they can do in a day' - When you work on getting 1% better each day you won't even recognize yourself in a year."], + >>> }) + >>> lsm.save("/Users/steven/Downloads/gaming-productivity.map") + + .. versionadded:: 0.1.0 + """ + if isinstance(file_path, str): + file_path = XAPath(file_path) + + status = LatentSemanticMapping.LSMMapWriteToURL(self.map, file_path.xa_elem, 0) + if status == 0: + return True + return False
+ +
[docs] def load(file_path: Union[XAPath, str]) -> 'XALSM': + """Loads a map from an external file. + + :param file_path: The file path for load the map from + :type file_path: Union[XAPath, str] + :return: The populated LSM object + :rtype: XALSM + + :Example: Using the gaming vs. productivity Reddit post map + + >>> import PyXA + >>> lsm = PyXA.XALSM.load("/Users/steven/Downloads/gaming-productivity.map") + >>> print(lsm.categorize_query("Hidden survival base on our server")) + >>> print(lsm.categorize_query("Your memory is FAR more powerful than you think… school just never taught us to use it properly.")) + [(1, 0.7313863635063171)] + [(2, 0.9422407150268555)] + + .. versionadded:: 0.1.0 + """ + if isinstance(file_path, str): + file_path = XAPath(file_path) + + new_lsm = XALSM(from_file=True) + new_lsm.map = LatentSemanticMapping.LSMMapCreateFromURL(None, file_path.xa_elem, LatentSemanticMapping.kLSMMapLoadMutable) + new_lsm.__dataset = {i: [] for i in range(LatentSemanticMapping.LSMMapGetCategoryCount(new_lsm.map))} + new_lsm.__categories = {i: i for i in range(LatentSemanticMapping.LSMMapGetCategoryCount(new_lsm.map))} + LatentSemanticMapping.LSMMapCompile(new_lsm.map) + return new_lsm
+ +
[docs] def add_category(self, name: str, initial_data: Union[list[str], None] = None) -> int: + """Adds a new category to the map, optionally filling the category with initial text data. + + :param name: The name of the category + :type name: str + :param initial_data: _description_ + :type initial_data: list[str] + :return: The ID of the new category + :rtype: int + + :Example: Add a category for cleaning-related Reddit posts to the previous example + + >>> import PyXA + >>> lsm = PyXA.XALSM.load("/Users/steven/Downloads/gaming-productivity.map") + >>> lsm.add_category("cleaning", ["Curtains stained from eyelet reaction at dry cleaner", "How do I get these stains out of my pink denim overalls? from a black denim jacket that was drying next to them", "Cleaned my depression room after months 🥵", "Tip: 30 minute soak in Vinegar", "Regular floor squeegee pulled a surprising amount of pet hair out of my carpet!", "Before and after…", "It actually WORKS", "CLR is actually magic. (With some elbow grease)", "It was 100% worth it to scrape out my old moldy caulk and replace it. $5 dollars and a bit of time to make my shower look so much cleaner!", "Thanks to the person who recommended the Clorox Foamer. Before and after pics", "TIL you can dissolve inkstains with milk.", "Fixing cat scratch marks to couch using felting needle: Before and After", "Turns out BKF isn't a meme! Really satisfied with this stuff"]) + >>> print(lsm.categorize_query("Hidden survival base on our server")) + >>> print(lsm.categorize_query("Your memory is FAR more powerful than you think… school just never taught us to use it properly.")) + >>> print(lsm.categorize_query("A carpet rake will change your life.")) + [(1, 0.7474805116653442)] + [(2, 0.7167008519172668)] + [(3, 0.797333300113678)] + + .. versionadded:: 0.1.0 + """ + LatentSemanticMapping.LSMMapStartTraining(self.map) + + if initial_data is None: + initial_data = [] + + if name in self.__dataset: + raise ValueError("The category name must be unique.") + + self.__dataset[name] = initial_data + category_ref = self.__add_category(name) + LatentSemanticMapping.LSMMapCompile(self.map) + return category_ref
+ +
[docs] def add_data(self, data: dict[Union[int, str], list[str]]) -> list[int]: + """Adds the provided data, organized by category, to the active map. + + :param data: A dictionary specifying new or existing categories along with data to input into them + :type data: dict[Union[int, str], list[str]] + :return: A list of newly created category IDs + :rtype: int + + :Example: Classify text by language + + >>> import PyXA + >>> lsm = PyXA.XALSM({}) + >>> lsm.add_data({ + >>> # 1 + >>> "english": ["brilliance outer jacket artist flat mosquito recover restrict official gas ratio publish domestic realize pure offset obstacle thigh favorite demonstration revive nest reader slide pudding symptom ballot auction characteristic complete Mars ridge student explosion dive emphasis the buy perfect motif penny a errand to fur far spirit random integration of with"], + >>> + >>> # 2 + >>> "italian": ["da piazza proposta di legge legare nazionale a volte la salute bar farti farmi il pane aggiunta valore artista chiamata settentrionale scuro buio classe signori investitore in grado di fidanzato tagliare arriva successo altrimenti speciale esattamente definizione sorriso chiamo madre pulire esperto rurale vedo malattia era amici libertà l'account immaginare lingua soldi più perché"], + >>> }) + >>> print(lsm.categorize_query("Here's to the crazy ones")) + >>> print(lsm.categorize_query("Potete parlarmi in italiano")) + [(1, 1.0)] + [(2, 1.0)] + + .. versionadded:: 0.1.0 + """ + category_refs = [] + LatentSemanticMapping.LSMMapStartTraining(self.map) + for category in data: + if category not in self.__dataset: + self.__dataset[category] = data[category] + category_refs.append(self.__add_category(category)) + else: + loc = Foundation.CFLocaleGetSystem() + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, " ".join(data[category]), loc, LatentSemanticMapping.kLSMTextPreserveAcronyms) + LatentSemanticMapping.LSMMapAddText(self.map, text_ref, self.__categories[category]) + LatentSemanticMapping.LSMMapCompile(self.map) + return category_refs
+ +
[docs] def add_text(self, text: str, category: Union[int, str], weight: float = 1): + """Adds the given text to the specified category, applying an optional weight. + + :param text: The text to add to the dataset + :type text: str + :param category: The category to add the text to + :type category: Union[int, str] + :param weight: The weight to assign to the text entry, defaults to 1 + :type weight: float, optional + :raises ValueError: The specified category must be a valid category name or ID + + :Example: + + >>> import PyXA + >>> lsm = PyXA.XALSM({"colors": [], "numbers": ["One", "Two", "Three"]}) + >>> lsm.add_text("red orange yellow green blue purple", "colors") + >>> lsm.add_text("white black grey gray brown pink", 1) + >>> print(lsm.categorize_query("pink")) + + .. versionadded:: 0.1.0 + """ + LatentSemanticMapping.LSMMapStartTraining(self.map) + if category not in self.__dataset and category not in self.__categories: + raise ValueError(f"Invalid category: {category}") + + loc = Foundation.CFLocaleGetSystem() + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, text, loc, LatentSemanticMapping.kLSMTextPreserveAcronyms) + LatentSemanticMapping.LSMMapAddTextWithWeight(self.map, text_ref, self.__categories[category], weight) + LatentSemanticMapping.LSMMapCompile(self.map)
+ +
[docs] def categorize_query(self, query: str, num_results: int = 1) -> list[tuple[int, float]]: + """Categorizes the query based on the current weights in the map. + + :param query: The query to categorize + :type query: str + :param num_results: The number of categorizations to show, defaults to 1 + :type num_results: int, optional + :return: A list of tuples identifying categories and their associated score. A higher score indicates better fit. If not matching categorization is found, the list will be empty. + :rtype: list[tuple[int, float]] + + :Example: + + >>> import PyXA + >>> dataset = { + >>> # 1 + >>> "color": ["red", "orange", "yellow", "green", "emerald", "blue", "purple", "white", "black", "brown", "pink", "grey", "gray"], + >>> + >>> # 2 + >>> "number": ["One Two Three Four Five Six Seven Eight Nine Ten"] + >>> } + >>> lsm = PyXA.XALSM(dataset) + >>> queries = ["emerald green three", "one hundred five", "One o' clock", "sky blue", "ninety nine", "purple pink"] + >>> + >>> for query in queries: + >>> category = "Unknown" + >>> categorization_tuple = lsm.categorize_query(query) + >>> if len(categorization_tuple) > 0: + >>> category = list(dataset.keys())[categorization_tuple[0][0] - 1] + >>> print(query, "is a", category) + emerald green three is a color + one hundred five is a number + One o' clock is a number + sky blue is a color + ninety nine is a number + purple pink is a color + + .. versionadded:: 0.1.0 + """ + loc = Foundation.CFLocaleGetSystem() + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, query, loc, 0) + rows = LatentSemanticMapping.LSMResultCreate(None, self.map, text_ref, 10, LatentSemanticMapping.kLSMTextPreserveAcronyms) + + categorization = [] + num_results = min(num_results, LatentSemanticMapping.LSMResultGetCount(rows)) + for i in range(0, num_results): + category_num = LatentSemanticMapping.LSMResultGetCategory(rows, i) + score = LatentSemanticMapping.LSMResultGetScore(rows, i) + categorization.append((category_num, score)) + return categorization
+ + + + +
[docs]class XAColorList(XATextList): + """A wrapper around lists of colors that employs fast enumeration techniques. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAColor, filter)
+ +
[docs]class XAColor(XAObject, XAClipboardCodable): + def __init__(self, *args): + if len(args) == 0: + # No color specified -- default to white + self.xa_elem = XAColor.white_color().xa_elem + elif len(args) == 1 and isinstance(args[0], AppKit.NSColor): + # Initialize copy of non-mutable NSColor object + self.copy_color(args[0]) + elif len(args) == 1 and isinstance(args[0], XAColor): + # Initialize copy of another XAColor object + self.copy_color(args[0].xa_elem) + else: + # Initialize from provided RGBA values + red = args[0] if len(args) >= 0 else 255 + green = args[1] if len(args) >= 1 else 255 + blue = args[2] if len(args) >= 3 else 255 + alpha = args[3] if len(args) == 4 else 1.0 + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red, green, blue, alpha) + +
[docs] def red() -> 'XAColor': + """Initializes and returns a pure red :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(65535, 0, 0)
+ +
[docs] def orange() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (1.0, 0.5, 0.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.orangeColor())
+ +
[docs] def yellow() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (1.0, 1.0, 0.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.yellowColor())
+ +
[docs] def green() -> 'XAColor': + """Initializes and returns a pure green :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 65535, 0)
+ +
[docs] def cyan() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.0, 1.0, 1.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.cyanColor())
+ +
[docs] def blue() -> 'XAColor': + """Initializes and returns a pure blue :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 0, 65535)
+ +
[docs] def magenta() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (1.0, 0.0, 1.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.magentaColor())
+ +
[docs] def purple() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.5, 0.0, 0.5). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.purpleColor())
+ +
[docs] def brown() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.6, 0.4, 0.2). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.brownColor())
+ +
[docs] def white() -> 'XAColor': + """Initializes and returns a pure white :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(65535, 65535, 65535)
+ +
[docs] def gray() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.5, 0.5, 0.5). + + .. versionadded:: 0.1.0 + """ + return XAColor(0.5, 0.5, 0.5)
+ +
[docs] def black() -> 'XAColor': + """Initializes and returns a pure black :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 0, 0)
+ +
[docs] def clear() -> 'XAColor': + """Initializes and returns a an :class:`XAColor` object whose alpha value is 0.0. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 0, 0, 0)
+ + @property + def red_value(self) -> float: + """The red value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.redComponent() + + @red_value.setter + def red_value(self, red_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red_value, self.green_value, self.blue_value, self.alpha_value) + + @property + def green_value(self) -> float: + """The green value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.greenComponent() + + @green_value.setter + def green_value(self, green_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(self.red_value, green_value, self.blue_value, self.alpha_value) + + @property + def blue_value(self) -> float: + """The blue value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.blueComponent() + + @blue_value.setter + def blue_value(self, blue_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(self.red_value, self.green_value, blue_value, self.alpha_value) + + @property + def alpha_value(self) -> float: + """The alpha value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.alphaComponent() + + @alpha_value.setter + def alpha_value(self, alpha_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(self.red_value, self.green_value, self.blue_value, alpha_value) + + @property + def hue_value(self): + """The hue value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.hueComponent() + + @hue_value.setter + def hue_value(self, hue_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(hue_value, self.saturation_value, self.brightness_value, self.alpha_value) + + @property + def saturation_value(self): + """The staturation value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.saturationComponent() + + @saturation_value.setter + def saturation_value(self, saturation_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(self.hue_value, saturation_value, self.brightness_value, self.alpha_value) + + @property + def brightness_value(self): + """The brightness value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.brightnessComponent() + + @brightness_value.setter + def brightness_value(self, brightness_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(self.hue_value, self.saturation_value, brightness_value, self.alpha_value) + +
[docs] def copy_color(self, color: AppKit.NSColor) -> 'XAColor': + """Initializes a XAColor copy of an NSColor object. + + :param color: The NSColor to copy + :type color: AppKit.NSColor + :return: The newly created XAColor object + :rtype: XAColor + + .. versionadded:: 0.1.0 + """ + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_( + color.redComponent(), + color.greenComponent(), + color.blueComponent(), + color.alphaComponent() + ) + return self
+ +
[docs] def set_rgba(self, red: float, green: float, blue: float, alpha: float) -> 'XAColor': + """Sets the RGBA values of the color. + + :param red: The red value of the color, from 0.0 to 1.0 + :type red: float + :param green: The green value of the color, from 0.0 to 1.0 + :type green: float + :param blue: The blue value of the color, from 0.0 to 1.0 + :type blue: float + :param alpha: The opacity of the color, from 0.0 to 1.0 + :type alpha: float + :return: The XAColor object + :rtype: XAColor + + .. versionadded:: 0.1.0 + """ + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red, green, blue, alpha) + return self
+ +
[docs] def set_hsla(self, hue: float, saturation: float, brightness: float, alpha: float) -> 'XAColor': + """Sets the HSLA values of the color. + + :param hue: The hue value of the color, from 0.0 to 1.0 + :type hue: float + :param saturation: The saturation value of the color, from 0.0 to 1.0 + :type saturation: float + :param brightness: The brightness value of the color, from 0.0 to 1.0 + :type brightness: float + :param alpha: The opacity of the color, from 0.0 to 1.0 + :type alpha: float + :return: The XAColor object + :rtype: XAColor + + .. versionadded:: 0.1.0 + """ + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(hue, saturation, brightness, alpha) + return self
+ +
[docs] def mix_with(self, color: 'XAColor', fraction: int = 0.5) -> 'XAColor': + """Blends this color with the specified fraction of another. + + :param color: The color to blend this color with + :type color: XAColor + :param fraction: The fraction of the other color to mix into this color, from 0.0 to 1.0, defaults to 0.5 + :type fraction: int, optional + :return: The resulting color after mixing + :rtype: XAColor + + .. versionadded:: 0.1.0 + """ + new_color = self.xa_elem.blendedColorWithFraction_ofColor_(fraction, color.xa_elem) + return XAColor(new_color.redComponent(), new_color.greenComponent(), new_color.blueComponent(), new_color.alphaComponent())
+ +
[docs] def brighten(self, fraction: float = 0.5) -> 'XAColor': + """Brightens the color by mixing the specified fraction of the system white color into it. + + :param fraction: The amount (fraction) of white to mix into the color, defaults to 0.5 + :type fraction: float, optional + :return: The resulting color after brightening + :rtype: XAColor + + .. versionadded:: 0.1.0 + """ + self.xa_elem = self.xa_elem.highlightWithLevel_(fraction) + return self
+ +
[docs] def darken(self, fraction: float = 0.5) -> 'XAColor': + """Darkens the color by mixing the specified fraction of the system black color into it. + + :param fraction: The amount (fraction) of black to mix into the color, defaults to 0.5 + :type fraction: float, optional + :return: The resulting color after darkening + :rtype: XAColor + + .. versionadded:: 0.1.0 + """ + self.xa_elem = self.xa_elem.shadowWithLevel_(fraction) + return self
+ +
[docs] def make_swatch(self, width: int = 100, height: int = 100) -> 'XAImage': + """Creates an image swatch of the color with the specified dimensions. + + :param width: The width of the swatch image, in pixels, defaults to 100 + :type width: int, optional + :param height: The height of the swatch image, in pixels, defaults to 100 + :type height: int, optional + :return: The image swatch as an XAImage object + :rtype: XAImage + + :Example: View swatches in Preview + + >>> import PyXA + >>> from time import sleep + >>> + >>> blue = PyXA.XAColor.blue() + >>> red = PyXA.XAColor.red() + >>> + >>> swatches = [ + >>> blue.make_swatch(), + >>> blue.darken(0.5).make_swatch(), + >>> blue.mix_with(red).make_swatch() + >>> ] + >>> + >>> for swatch in swatches: + >>> swatch.show_in_preview() + >>> sleep(0.2) + + .. versionadded:: 0.1.0 + """ + img = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(width, height)) + img.lockFocus() + self.xa_elem.drawSwatchInRect_(AppKit.NSMakeRect(0, 0, width, height)) + img.unlockFocus() + return XAImage(img)
+ +
[docs] def get_clipboard_representation(self) -> AppKit.NSColor: + """Gets a clipboard-codable representation of the color. + + When the clipboard content is set to a color, the raw color data is added to the clipboard. + + :return: The raw color data + :rtype: AppKit.NSColor + + .. versionadded:: 0.1.0 + """ + return self.xa_elem
+ + def __repr__(self): + return f"<{str(type(self))}r={str(self.red())}, g={self.green()}, b={self.blue()}, a={self.alpha()}>"
+ + + + +# TODO: Init NSLocation object +
[docs]class XALocation(XAObject): + """A location with a latitude and longitude, along with other data. + + .. versionadded:: 0.0.2 + """ + current_location: 'XALocation' #: The current location of the device + + def __init__(self, raw_value: CoreLocation.CLLocation = None, title: str = None, latitude: float = 0, longitude: float = 0, altitude: float = None, radius: int = 0, address: str = None): + self.raw_value = raw_value #: The raw CLLocation object + self.title = title #: The name of the location + self.latitude = latitude #: The latitude of the location + self.longitude = longitude #: The longitude of the location + self.altitude = altitude #: The altitude of the location + self.radius = radius #: The horizontal accuracy of the location measurement + self.address = address #: The addres of the location + + @property + def raw_value(self) -> CoreLocation.CLLocation: + return self.__raw_value + + @raw_value.setter + def raw_value(self, raw_value: CoreLocation.CLLocation): + self.__raw_value = raw_value + if raw_value is not None: + self.latitude = raw_value.coordinate()[0] + self.longitude = raw_value.coordinate()[1] + self.altitude = raw_value.altitude() + self.radius = raw_value.horizontalAccuracy() + + @property + def current_location(self) -> 'XALocation': + self.raw_value = None + self._spawn_thread(self.__get_current_location) + while self.raw_value is None: + time.sleep(0.01) + return self + +
[docs] def show_in_maps(self): + """Shows the location in Maps.app. + + .. versionadded:: 0.0.6 + """ + XAURL(f"maps://?q={self.title},ll={self.latitude},{self.longitude}").open()
+ + def __get_current_location(self): + location_manager = CoreLocation.CLLocationManager.alloc().init() + old_self = self + class CLLocationManagerDelegate(AppKit.NSObject): + def locationManager_didUpdateLocations_(self, manager, locations): + if locations is not None: + old_self.raw_value = locations[0] + AppHelper.stopEventLoop() + + def locationManager_didFailWithError_(self, manager, error): + print(manager, error) + + location_manager.requestWhenInUseAuthorization() + location_manager.setDelegate_(CLLocationManagerDelegate.alloc().init().retain()) + location_manager.requestLocation() + + AppHelper.runConsoleEventLoop() + + def __repr__(self): + return "<" + str(type(self)) + str((self.latitude, self.longitude)) + ">"
+ + + + +
[docs]class XAAlertStyle(Enum): + """Options for which alert style an alert should display with. + """ + INFORMATIONAL = AppKit.NSAlertStyleInformational + WARNING = AppKit.NSAlertStyleWarning + CRITICAL = AppKit.NSAlertStyleCritical
+ +
[docs]class XAAlert(XAObject): + """A class for creating and interacting with an alert dialog window. + + .. versionadded:: 0.0.5 + """ + def __init__(self, title: str = "Alert!", message: str = "", style: XAAlertStyle = XAAlertStyle.INFORMATIONAL, buttons = ["Ok", "Cancel"]): + super().__init__() + self.title: str = title + self.message: str = message + self.style: XAAlertStyle = style + self.buttons: list[str] = buttons + +
[docs] def display(self) -> int: + """Displays the alert. + + :return: A number representing the button that the user selected, if any + :rtype: int + + .. versionadded:: 0.0.5 + """ + alert = AppKit.NSAlert.alloc().init() + alert.setMessageText_(self.title) + alert.setInformativeText_(self.message) + alert.setAlertStyle_(self.style.value) + + for button in self.buttons: + alert.addButtonWithTitle_(button) + return alert.runModal()
+ + + + +
[docs]class XAColorPickerStyle(Enum): + """Options for which tab a color picker should display when first opened. + """ + GRAYSCALE = AppKit.NSColorPanelModeGray + RGB_SLIDERS = AppKit.NSColorPanelModeRGB + CMYK_SLIDERS = AppKit.NSColorPanelModeCMYK + HSB_SLIDERS = AppKit.NSColorPanelModeHSB + COLOR_LIST = AppKit.NSColorPanelModeColorList + COLOR_WHEEL = AppKit.NSColorPanelModeWheel + CRAYONS = AppKit.NSColorPanelModeCrayon + IMAGE_PALETTE = AppKit.NSColorPanelModeCustomPalette
+ +
[docs]class XAColorPicker(XAObject): + """A class for creating and interacting with a color picker window. + + .. versionadded:: 0.0.5 + """ + def __init__(self, style: XAColorPickerStyle = XAColorPickerStyle.GRAYSCALE): + super().__init__() + self.style = style + +
[docs] def display(self) -> XAColor: + """Displays the color picker. + + :return: The color that the user selected + :rtype: XAColor + + .. versionadded:: 0.0.5 + """ + panel = AppKit.NSColorPanel.sharedColorPanel() + panel.setMode_(self.style.value) + panel.setShowsAlpha_(True) + + def run_modal(panel): + initial_color = panel.color() + time.sleep(0.5) + while panel.isVisible() and panel.color() == initial_color: + time.sleep(0.01) + AppKit.NSApp.stopModal() + + modal_thread = threading.Thread(target=run_modal, args=(panel, ), name="Run Modal", daemon=True) + modal_thread.start() + + AppKit.NSApp.runModalForWindow_(panel) + return XAColor(panel.color())
+ + + + +
[docs]class XADialog(XAObject): + """A custom dialog window. + + .. versionadded:: 0.0.8 + """ + def __init__(self, text: str = "", title: Union[str, None] = None, buttons: Union[None, list[Union[str, int]]] = None, hidden_answer: bool = False, default_button: Union[str, int, None] = None, cancel_button: Union[str, int, None] = None, icon: Union[Literal["stop", "note", "caution"], None] = None, default_answer: Union[str, int, None] = None): + super().__init__() + self.text: str = text + self.title: str = title + self.buttons: Union[None, list[Union[str, int]]] = buttons or [] + self.hidden_answer: bool = hidden_answer + self.icon: Union[str, None] = icon + self.default_button: Union[str, int, None] = default_button + self.cancel_button: Union[str, int, None] = cancel_button + self.default_answer: Union[str, int, None] = default_answer + +
[docs] def display(self) -> Union[str, int, None, list[str]]: + """Displays the dialog, waits for the user to select an option or cancel, then returns the selected button or None if cancelled. + + :return: The selected button or None if no value was selected + :rtype: Union[str, int, None, list[str]] + + .. versionadded:: 0.0.8 + """ + buttons = [x.replace("'", "") for x in self.buttons] + buttons = str(buttons).replace("'", '"') + + default_button = str(self.default_button).replace("'", "") + default_button_str = "default button \"" + default_button + "\"" if self.default_button is not None else "" + + cancel_button = str(self.cancel_button).replace("'", "") + cancel_button_str = "cancel button \"" + cancel_button + "\"" if self.cancel_button is not None else "" + + icon_str = "with icon " + self.icon + "" if self.icon is not None else "" + + default_answer = str(self.default_answer).replace("'", '"') + default_answer_str = "default answer \"" + default_answer + "\"" if self.default_answer is not None else "" + + script = AppleScript(f""" + tell application "Terminal" + display dialog \"{self.text}\" with title \"{self.title}\" buttons {buttons} {default_button_str} {cancel_button_str} {icon_str} {default_answer_str} hidden answer {self.hidden_answer} + end tell + """) + + result = script.run()["event"] + if result is not None: + if result.numberOfItems() > 1: + return [result.descriptorAtIndex_(1).stringValue(), result.descriptorAtIndex_(2).stringValue()] + else: + return result.descriptorAtIndex_(1).stringValue()
+ + + + +
[docs]class XAMenu(XAObject): + """A custom list item selection menu. + + .. versionadded:: 0.0.8 + """ + def __init__(self, menu_items: list[Any], title: str = "Select Item", prompt: str = "Select an item", default_items: Union[list[str], None] = None, ok_button_name: str = "Okay", cancel_button_name: str = "Cancel", multiple_selections_allowed: bool = False, empty_selection_allowed: bool = False): + super().__init__() + self.menu_items: list[Union[str, int]] = menu_items #: The items the user can choose from + self.title: str = title #: The title of the dialog window + self.prompt: str = prompt #: The prompt to display in the dialog box + self.default_items: list[str] = default_items or [] #: The items to initially select + self.ok_button_name: str = ok_button_name #: The name of the OK button + self.cancel_button_name: str = cancel_button_name #: The name of the Cancel button + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether multiple items can be selected + self.empty_selection_allowed: bool = empty_selection_allowed #: Whether the user can click OK without selecting anything + +
[docs] def display(self) -> Union[str, int, bool, list[str], list[int]]: + """Displays the menu, waits for the user to select an option or cancel, then returns the selected value or False if cancelled. + + :return: The selected value or False if no value was selected + :rtype: Union[str, int, bool, list[str], list[int]] + + .. versionadded:: 0.0.8 + """ + menu_items = [x.replace("'", "") for x in self.menu_items] + menu_items = str(menu_items).replace("'", '"') + default_items = str(self.default_items).replace("'", '"') + script = AppleScript(f""" + tell application "Terminal" + choose from list {menu_items} with title \"{self.title}\" with prompt \"{self.prompt}\" default items {default_items} OK button name \"{self.ok_button_name}\" cancel button name \"{self.cancel_button_name}\" multiple selections allowed {self.multiple_selections_allowed} empty selection allowed {self.empty_selection_allowed} + end tell + """) + result = script.run()["event"] + if result is not None: + if self.multiple_selections_allowed: + values = [] + for x in range(1, result.numberOfItems() + 1): + desc = result.descriptorAtIndex_(x) + values.append(desc.stringValue()) + return values + else: + if result.stringValue() == "false": + return False + return result.stringValue()
+ + + + +
[docs]class XAFilePicker(XAObject): + """A file selection window. + + .. versionadded:: 0.0.8 + """ + def __init__(self, prompt: str = "Choose File", types: list[str] = None, default_location: Union[str, None] = None, show_invisibles: bool = False, multiple_selections_allowed: bool = False, show_package_contents: bool = False): + super().__init__() + self.prompt: str = prompt #: The prompt to display in the dialog box + self.types: list[str] = types #: The file types/type identifiers to allow for selection + self.default_location: Union[str, None] = default_location #: The default file location + self.show_invisibles: bool = show_invisibles #: Whether invisible files and folders are shown + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether the user can select multiple files + self.show_package_contents: bool = show_package_contents #: Whether to show the contents of packages + +
[docs] def display(self) -> Union[XAPath, None]: + """Displays the file chooser, waits for the user to select a file or cancel, then returns the selected file URL or None if cancelled. + + :return: The selected file URL or None if no file was selected + :rtype: Union[XAPath, None] + + .. versionadded:: 0.0.8 + """ + types = [x.replace("'", "") for x in self.types] + types = str(types).replace("'", '"') + types_str = "of type " + types if self.types is not None else "" + + default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" + + script = AppleScript(f""" + tell application "Terminal" + choose file with prompt \"{self.prompt}\" {types_str}{default_location_str} invisibles {self.show_invisibles} multiple selections allowed {self.multiple_selections_allowed} showing package contents {self.show_package_contents} + end tell + """) + result = script.run()["event"] + + if result is not None: + if self.multiple_selections_allowed: + values = [] + for x in range(1, result.numberOfItems() + 1): + desc = result.descriptorAtIndex_(x) + values.append(XAPath(desc.fileURLValue())) + return values + else: + return XAPath(result.fileURLValue())
+ + + + +
[docs]class XAFolderPicker(XAObject): + """A folder selection window. + + .. versionadded:: 0.0.8 + """ + def __init__(self, prompt: str = "Choose Folder", default_location: Union[str, None] = None, show_invisibles: bool = False, multiple_selections_allowed: bool = False, show_package_contents: bool = False): + super().__init__() + self.prompt: str = prompt #: The prompt to display in the dialog box + self.default_location: Union[str, None] = default_location #: The default folder location + self.show_invisibles: bool = show_invisibles #: Whether invisible files and folders are shown + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether the user can select multiple folders + self.show_package_contents: bool = show_package_contents #: Whether to show the contents of packages + +
[docs] def display(self) -> Union[XAPath, None]: + """Displays the folder chooser, waits for the user to select a folder or cancel, then returns the selected folder URL or None if cancelled. + + :return: The selected folder URL or None if no folder was selected + :rtype: Union[XAPath, None] + + .. versionadded:: 0.0.8 + """ + + default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" + + script = AppleScript(f""" + tell application "Terminal" + choose folder with prompt \"{self.prompt}\" {default_location_str} invisibles {self.show_invisibles} multiple selections allowed {self.multiple_selections_allowed} showing package contents {self.show_package_contents} + end tell + """) + result = script.run()["event"] + if result is not None: + if self.multiple_selections_allowed: + values = [] + for x in range(1, result.numberOfItems() + 1): + desc = result.descriptorAtIndex_(x) + values.append(XAPath(desc.fileURLValue())) + return values + else: + return XAPath(result.fileURLValue())
+ + + + +
[docs]class XAApplicationPicker(XAObject): + """An application selection window. + + .. versionadded:: 0.1.0 + """ + def __init__(self, title: Union[str, None] = None, prompt: Union[str, None] = None, multiple_selections_allowed: bool = False): + super().__init__() + self.title: str = title #: The dialog window title + self.prompt: str = prompt #: The prompt to be displayed in the dialog box + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether to allow multiple items to be selected + +
[docs] def display(self) -> str: + """Displays the application chooser, waits for the user to select an application or cancel, then returns the selected application's name or None if cancelled. + + :return: The name of the selected application + :rtype: str + + .. versionadded:: 0.0.8 + """ + + script = AppleScript("tell application \"Terminal\"") + dialog_str = "choose application " + if self.title is not None: + dialog_str += f"with title \"{self.title}\" " + if self.prompt is not None: + dialog_str += f"with prompt \"{self.prompt}\"" + dialog_str += f"multiple selections allowed {self.multiple_selections_allowed} " + script.add(dialog_str) + script.add("end tell") + + return script.run()["string"]
+ + + + +
[docs]class XAFileNameDialog(XAObject): + """A file name input window. + + .. versionadded:: 0.0.8 + """ + def __init__(self, prompt: str = "Specify file name and location", default_name: str = "New File", default_location: Union[str, None] = None): + super().__init__() + self.prompt: str = prompt #: The prompt to display in the dialog box + self.default_name: str = default_name #: The default name for the new file + self.default_location: Union[str, None] = default_location #: The default file location + +
[docs] def display(self) -> Union[XAPath, None]: + """Displays the file name input window, waits for the user to input a name and location or cancel, then returns the specified file URL or None if cancelled. + + :return: The specified file URL or None if no file name was inputted + :rtype: Union[XAPath, None] + + .. versionadded:: 0.0.8 + """ + + default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" + + script = AppleScript(f""" + tell application "Terminal" + choose file name with prompt \"{self.prompt}\" default name \"{self.default_name}\" {default_location_str} + end tell + """) + result = script.run()["event"] + if result is not None: + return XAPath(result.fileURLValue())
+ + + + +
[docs]class XAMenuBar(XAObject): + def __init__(self): + """Creates a new menu bar object for interacting with the system menu bar. + + .. versionadded:: 0.0.9 + """ + self._menus = {} + self._menu_items = {} + self._methods = {} + + detector = self + class MyApplicationAppDelegate(AppKit.NSObject): + start_time = datetime.now() + + def applicationDidFinishLaunching_(self, sender): + for item_name, status_item in detector._menus.items(): + menu = status_item.menu() + + menuitem = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '') + menu.addItem_(menuitem) + + def action_(self, menu_item): + selection = menu_item.title() + for item_name in detector._methods: + if selection == item_name: + detector._methods[item_name]() + + app = AppKit.NSApplication.sharedApplication() + app.setDelegate_(MyApplicationAppDelegate.alloc().init().retain()) + +
[docs] def add_menu(self, title: str, image: Union['XAImage', None] = None, tool_tip: Union[str, None] = None, img_width: int = 30, img_height: int = 30): + """Adds a new menu to be displayed in the system menu bar. + + :param title: The name of the menu + :type title: str + :param image: The image to display for the menu, defaults to None + :type image: Union[XAImage, None], optional + :param tool_tip: The tooltip to display on hovering over the menu, defaults to None + :type tool_tip: Union[str, None], optional + :param img_width: The width of the image, in pixels, defaults to 30 + :type img_width: int, optional + :param img_height: The height of the image, in pixels, defaults to 30 + :type img_height: int, optional + + :Example: + + >>> import PyXA + >>> menu_bar = PyXA.XAMenuBar() + >>> img = PyXA.XAImage("/Users/steven/Downloads/Blackness.jpg") + >>> menu_bar.add_menu("Menu 1", image=img, img_width=100, img_height=100) + >>> menu_bar.display() + + .. versionadded:: 0.0.9 + """ + status_bar = AppKit.NSStatusBar.systemStatusBar() + status_item = status_bar.statusItemWithLength_(AppKit.NSVariableStatusItemLength).retain() + status_item.setTitle_(title) + + if isinstance(image, XAImage): + img = image.xa_elem.copy() + img.setScalesWhenResized_(True) + img.setSize_((img_width, img_height)) + status_item.button().setImage_(img) + + status_item.setHighlightMode_(objc.YES) + + if isinstance(tool_tip, str): + status_item.setToolTip_(tool_tip) + + menu = AppKit.NSMenu.alloc().init() + status_item.setMenu_(menu) + + status_item.setEnabled_(objc.YES) + self._menus[title] = status_item
+ +
[docs] def add_item(self, menu: str, item_name: str, method: Union[Callable[[], None], None] = None, image: Union['XAImage', None] = None, img_width: int = 20, img_height: int = 20): + """Adds an item to a menu, creating the menu if necessary. + + :param menu: The name of the menu to add an item to, or the name of the menu to create + :type menu: str + :param item_name: The name of the item + :type item_name: str + :param method: The method to associate with the item (the method called when the item is clicked) + :type method: Callable[[], None] + :param image: The image for the item, defaults to None + :type image: Union[XAImage, None], optional + :param img_width: The width of image, in pixels, defaults to 30 + :type img_width: int, optional + :param img_height: The height of the image, in pixels, defaults to 30 + :type img_height: int, optional + + :Example: + + >>> import PyXA + >>> menu_bar = PyXA.XAMenuBar() + >>> + >>> menu_bar.add_menu("Menu 1") + >>> menu_bar.add_item(menu="Menu 1", item_name="Item 1", method=lambda : print("Action 1")) + >>> menu_bar.add_item(menu="Menu 1", item_name="Item 2", method=lambda : print("Action 2")) + >>> + >>> menu_bar.add_item(menu="Menu 2", item_name="Item 1", method=lambda : print("Action 1")) + >>> img = PyXA.XAImage("/Users/exampleUser/Downloads/example.jpg") + >>> menu_bar.add_item("Menu 2", "Item 1", lambda : print("Action 1"), image=img, img_width=100) + >>> menu_bar.display() + + .. versionadded:: 0.0.9 + """ + item = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(item_name, 'action:', '') + + if isinstance(image, XAImage): + img = image.xa_elem.copy() + img.setScalesWhenResized_(True) + img.setSize_((img_width, img_height)) + item.setImage_(img) + + if menu not in self._menus: + self.add_menu(menu) + self._menu_items[item_name] = item + self._menus[menu].menu().addItem_(item) + self._methods[item_name] = method
+ +
[docs] def set_image(self, item_name: str, image: 'XAImage', img_width: int = 30, img_height: int = 30): + """Sets the image displayed for a menu or menu item. + + :param item_name: The name of the item to update + :type item_name: str + :param image: The image to display + :type image: XAImage + :param img_width: The width of the image, in pixels, defaults to 30 + :type img_width: int, optional + :param img_height: The height of the image, in pixels, defaults to 30 + :type img_height: int, optional + + :Example: Set Image on State Change + + >>> import PyXA + >>> current_state = True # On + >>> img_on = PyXA.XAImage("/Users/exampleUser/Documents/on.jpg") + >>> img_off = PyXA.XAImage("/Users/exampleUser/Documents/off.jpg") + >>> menu_bar = PyXA.XAMenuBar() + >>> menu_bar.add_menu("Status", image=img_on) + >>> + >>> def update_state(): + >>> global current_state + >>> if current_state is True: + >>> # ... (Actions for turning off) + >>> menu_bar.set_text("Turn off", "Turn on") + >>> menu_bar.set_image("Status", img_off) + >>> current_state = False + >>> else: + >>> # ... (Actions for turning on) + >>> menu_bar.set_text("Turn off", "Turn off") + >>> menu_bar.set_image("Status", img_on) + >>> current_state = True + + menu_bar.add_item("Status", "Turn off", update_state) + menu_bar.display() + + .. versionadded:: 0.0.9 + """ + img = image.xa_elem.copy() + img.setScalesWhenResized_(True) + img.setSize_((img_width, img_height)) + if item_name in self._menus: + self._menus[item_name].button().setImage_(img) + elif item_name in self._methods: + self._menu_items[item_name].setImage_(img)
+ +
[docs] def set_text(self, item_name: str, text: str): + """Sets the text displayed for a menu or menu item. + + :param item_name: The name of the item to update + :type item_name: str + :param text: The new text to display + :type text: str + + :Example: Random Emoji Ticker + + >>> import PyXA + >>> import random + >>> import threading + >>> + >>> menu_bar = PyXA.XAMenuBar() + >>> menu_bar.add_menu("Emoji") + >>> + >>> emojis = ["😀", "😍", "🙂", "😎", "🤩", "🤯", "😭", "😱", "😴", "🤒", "😈", "🤠"] + >>> + >>> def update_display(): + >>> while True: + >>> new_emoji = random.choice(emojis) + >>> menu_bar.set_text("Emoji", new_emoji) + >>> sleep(0.25) + >>> + >>> emoji_ticker = threading.Thread(target=update_display) + >>> emoji_ticker.start() + >>> menu_bar.display() + + .. versionadded:: 0.0.9 + """ + if item_name in self._menus: + self._menus[item_name].setTitle_(text) + elif item_name in self._methods: + self._menu_items[item_name].setTitle_(text) + self._methods[text] = self._methods[item_name]
+ +
[docs] def display(self): + """Displays the custom menus on the menu bar. + + :Example: + + >>> import PyXA + >>> mbar = PyXA.XAMenuBar() + >>> mbar.add_menu("🔥") + >>> mbar.display() + + .. versionadded:: 0.0.9 + """ + try: + AppHelper.runEventLoop(installInterrupt=True) + except Exception as e: + print(e)
+ + + + +############################# +### System / Image Events ### +############################# +# ? Move into separate XAFileSystemBase.py file? +
[docs]class XAEventsApplication(XACanOpenPath): + """A base class for the System and Image events applications. + + .. versionadded:: 0.1.0 + """ +
[docs] class Format(Enum): + """Disk format options. + """ + APPLE_PHOTO = OSType("dfph") + APPLESHARE = OSType("dfas") + AUDIO = OSType("dfau") + HIGH_SIERRA = OSType("dfhs") + ISO_9660 = OSType("fd96") + MACOS_EXTENDED = OSType("dfh+") + MACOS = OSType("dfhf") + MSDOS = OSType("dfms") + NFS = OSType("dfnf") + PRODOS = OSType("dfpr") + QUICKTAKE = OSType("dfqt") + UDF = OSType("dfud") + UFS = OSType("dfuf") + UNKNOWN = OSType("df$$") + WEBDAV = OSType("dfwd")
+ +
[docs]class XADiskItemList(XAList): + """A wrapper around lists of disk items that employs fast enumeration techniques. + + All properties of disk items can be called as methods on the wrapped list, returning a list containing each item's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, object_class = None): + if object_class is None: + object_class = XADiskItem + super().__init__(properties, object_class, filter) + +
[docs] def busy_status(self) -> list['bool']: + """Retrieves the busy status of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("busyStatus"))
+ +
[docs] def container(self) -> 'XADiskItemList': + """Retrieves the containing folder or disk of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("container") + return self._new_element(ls, XADiskItemList)
+ +
[docs] def creation_date(self) -> list['datetime']: + """Retrieves the creation date of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("creationDate"))
+ +
[docs] def displayed_name(self) -> list['str']: + """Retrieves the displayed name of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("displayedName"))
+ +
[docs] def id(self) -> list['str']: + """Retrieves the unique ID of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def modification_date(self) -> list['datetime']: + """Retrieves the last modified date of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modificationDate"))
+ +
[docs] def name(self) -> list['str']: + """Retrieves the name of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def name_extension(self) -> list['str']: + """Retrieves the name extension of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("nameExtension"))
+ +
[docs] def package_folder(self) -> list['bool']: + """Retrieves the package folder status of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("packageFolder"))
+ +
[docs] def path(self) -> list['XAPath']: + """Retrieves the file system path of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("path") + return [XAPath(x) for x in ls]
+ +
[docs] def physical_size(self) -> list['int']: + """Retrieves the actual disk space used by each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("physicalSize"))
+ +
[docs] def posix_path(self) -> list[XAPath]: + """Retrieves the POSIX file system path of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("POSIXPath") + return [XAPath(x) for x in ls]
+ +
[docs] def size(self) -> list['int']: + """Retrieves the logical size of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("size"))
+ +
[docs] def url(self) -> list['XAURL']: + """Retrieves the URL of each disk item in the list. + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("URL") + return [XAURL(x) for x in ls]
+ +
[docs] def visible(self) -> list['bool']: + """Retrieves the visible status of each item in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("visible"))
+ +
[docs] def volume(self) -> list['str']: + """Retrieves the volume on which each item in the list resides. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("volume"))
+ +
[docs] def by_busy_status(self, busy_status: bool) -> 'XADiskItem': + """Retrieves item whose busy status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("busyStatus", busy_status)
+ +
[docs] def by_container(self, container: 'XADiskItem') -> 'XADiskItem': + """Retrieves item whose container matches the given disk item. + + .. versionadded:: 0.1.0 + """ + return self.by_property("container", container.xa_elem)
+ +
[docs] def by_creation_date(self, creation_date: datetime) -> 'XADiskItem': + """Retrieves item whose creation date matches the given date. + + .. versionadded:: 0.1.0 + """ + return self.by_property("creationDate", creation_date)
+ +
[docs] def by_displayed_name(self, displayed_name: str) -> 'XADiskItem': + """Retrieves item whose displayed name matches the given name. + + .. versionadded:: 0.1.0 + """ + return self.by_property("displayedName", displayed_name)
+ +
[docs] def by_id(self, id: str) -> 'XADiskItem': + """Retrieves item whose ID matches the given ID. + + .. versionadded:: 0.1.0 + """ + return self.by_property("id", id)
+ +
[docs] def by_modification_date(self, modification_date: datetime) -> 'XADiskItem': + """Retrieves item whose date matches the given date. + + .. versionadded:: 0.1.0 + """ + return self.by_property("modificationDate", modification_date)
+ +
[docs] def by_name(self, name: str) -> 'XADiskItem': + """Retrieves item whose name matches the given name. + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_name_extension(self, name_extension: str) -> 'XADiskItem': + """Retrieves item whose name extension matches the given extension. + + .. versionadded:: 0.1.0 + """ + return self.by_property("nameExtension", name_extension)
+ +
[docs] def by_package_folder(self, package_folder: bool) -> 'XADiskItem': + """Retrieves item whose package folder status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("packageFolder", package_folder)
+ +
[docs] def by_path(self, path: Union[XAPath, str]) -> 'XADiskItem': + """Retrieves item whose path matches the given path. + + .. versionadded:: 0.1.0 + """ + if isinstance(path, XAPath): + path = path.path + return self.by_property("path", path)
+ +
[docs] def by_physical_size(self, physical_size: int) -> 'XADiskItem': + """Retrieves item whose physical size matches the given size. + + .. versionadded:: 0.1.0 + """ + return self.by_property("physicalSize", physical_size)
+ +
[docs] def by_posix_path(self, posix_path: Union[XAPath, str]) -> 'XADiskItem': + """Retrieves item whose POSIX path matches the given POSIX path. + + .. versionadded:: 0.1.0 + """ + if isinstance(posix_path, XAPath): + posix_path = posix_path.path + return self.by_property("POSIXPath", posix_path)
+ +
[docs] def by_size(self, size: int) -> 'XADiskItem': + """Retrieves item whose size matches the given size. + + .. versionadded:: 0.1.0 + """ + return self.by_property("size", size)
+ +
[docs] def by_url(self, url: XAURL) -> 'XADiskItem': + """Retrieves the item whose URL matches the given URL. + + .. versionadded:: 0.1.0 + """ + return self.by_property("URL", url.xa_elem)
+ +
[docs] def by_visible(self, visible: bool) -> 'XADiskItem': + """Retrieves the item whose visible status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("visible", visible)
+ +
[docs] def by_volume(self, volume: str) -> 'XADiskItem': + """Retrieves the item whose volume matches the given volume. + + .. versionadded:: 0.1.0 + """ + return self.by_property("volume", volume)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XADiskItem(XAObject, XAPathLike): + """An item stored in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + @property + def busy_status(self) -> 'bool': + """Whether the disk item is busy. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.busyStatus() + + @property + def container(self) -> 'XADiskItem': + """The folder or disk which has this disk item as an element. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.container(), XADiskItem) + + @property + def creation_date(self) -> 'datetime': + """The date on which the disk item was created. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.creationDate() + + @property + def displayed_name(self) -> 'str': + """The name of the disk item as displayed in the User Interface. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.displayedName() + + @property + def id(self) -> 'str': + """The unique ID of the disk item. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.id() + + @property + def modification_date(self) -> 'datetime': + """The date on which the disk item was last modified. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.modificationDate() + + @property + def name(self) -> 'str': + """The name of the disk item. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.name() + + @property + def name_extension(self) -> 'str': + """The extension portion of the name. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.nameExtension() + + @property + def package_folder(self) -> 'bool': + """Whether the disk item is a package. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.packageFolder() + + @property + def path(self) -> 'XAPath': + """The file system path of the disk item. + + .. versionadded:: 0.1.0 + """ + return XAPath(self.xa_elem.path()) + + @property + def physical_size(self) -> 'int': + """The actual space used by the disk item on disk. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.physicalSize() + + @property + def posix_path(self) -> XAPath: + """The POSIX file system path of the disk item. + + .. versionadded:: 0.1.0 + """ + return XAPath(self.xa_elem.POSIXPath()) + + @property + def size(self) -> 'int': + """The logical size of the disk item. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.size() + + @property + def url(self) -> 'XAURL': + """The URL of the disk item. + + .. versionadded:: 0.1.0 + """ + return XAURL(self.xa_elem.URL()) + + @property + def visible(self) -> 'bool': + """Whether the disk item is visible. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.visible() + + @property + def volume(self) -> 'str': + """The volume on which the disk item resides. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.volume() + +
[docs] def get_path_representation(self) -> XAPath: + return self.posix_path
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + + +
[docs]class XAAliasList(XADiskItemList): + """A wrapper around lists of aliases that employs fast enumeration techniques. + + All properties of aliases can be called as methods on the wrapped list, returning a list containing each alias' value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAAlias) + +
[docs] def creator_type(self) -> list['str']: + """Retrieves the OSType identifying the application that created each alias in the list + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("creatorType"))
+ +
[docs] def default_application(self) -> 'XADiskItemList': + """Retrieves the applications that will launch if each alias in the list is opened. + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("defaultApplication") + return self._new_element(ls, XADiskItemList)
+ +
[docs] def file_type(self) -> list['str']: + """Retrieves the OSType identifying the type of data contained in each alias in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("fileType"))
+ +
[docs] def kind(self) -> list['str']: + """Retrieves the kind of each alias in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("kind"))
+ +
[docs] def product_version(self) -> list['str']: + """Retrieves the product version of each alias in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("productVersion"))
+ +
[docs] def short_version(self) -> list['str']: + """Retrieves the short version of the application bundle referenced by each alias in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("shortVersion"))
+ +
[docs] def stationery(self) -> list['bool']: + """Retrieves the stationery status of each alias in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("stationery"))
+ +
[docs] def type_identifier(self) -> list['str']: + """Retrieves the type identifier of each alias in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("typeIdentifier"))
+ +
[docs] def version(self) -> list['str']: + """Retrieves the version of the application bundle referenced by each alias in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("version"))
+ +
[docs] def by_creator_type(self, creator_type: str) -> 'XAAlias': + """Retrieves the alias whose creator type matches the given creator type. + + .. versionadded:: 0.1.0 + """ + return self.by_property("creatorType", creator_type)
+ +
[docs] def by_default_application(self, default_application: 'XADiskItem') -> 'XAAlias': + """Retrieves the alias whose default application matches the given application. + + .. versionadded:: 0.1.0 + """ + return self.by_property("defaultApplication", default_application.xa_elem)
+ +
[docs] def by_file_type(self, file_type: str) -> 'XAAlias': + """Retrieves the alias whose file type matches the given file type. + + .. versionadded:: 0.1.0 + """ + return self.by_property("fileType", file_type)
+ +
[docs] def by_kind(self, kind: str) -> 'XAAlias': + """Retrieves the alias whose kind matches the given kind. + + .. versionadded:: 0.1.0 + """ + return self.by_property("kind", kind)
+ +
[docs] def by_product_version(self, product_version: str) -> 'XAAlias': + """Retrieves the alias whose product version matches the given version. + + .. versionadded:: 0.1.0 + """ + return self.by_property("productVersion", product_version)
+ +
[docs] def by_short_version(self, short_version: str) -> 'XAAlias': + """Retrieves the alias whose short version matches the given text. + + .. versionadded:: 0.1.0 + """ + return self.by_property("shortVersion", short_version)
+ +
[docs] def by_stationery(self, stationery: bool) -> 'XAAlias': + """Retrieves the alias whose stationery status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("stationery", stationery)
+ +
[docs] def by_type_identifier(self, type_identifier: str) -> 'XAAlias': + """Retrieves the alias whose type identifier matches the given type identifier. + + .. versionadded:: 0.1.0 + """ + return self.by_property("typeIdentifier", type_identifier)
+ +
[docs] def by_version(self, version: str) -> 'XAAlias': + """Retrieves the alias whose version matches the given version. + + .. versionadded:: 0.1.0 + """ + return self.by_property("version", version)
+ +
[docs]class XAAlias(XADiskItem): + """An alias in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + @property + def creator_type(self) -> 'str': + """The OSType identifying the application that created the alias. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.creatorType() + + @property + def default_application(self) -> 'XADiskItem': + """The application that will launch if the alias is opened. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.defaultApplication(), XADiskItem) + + @property + def file_type(self) -> 'str': + """The OSType identifying the type of data contained in the alias. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.fileType() + + @property + def kind(self) -> 'str': + """The kind of alias, as shown in Finder. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.kind() + + @property + def product_version(self) -> 'str': + """The version of the product (visible at the top of the "Get Info" window). + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.productVersion() + + @property + def short_version(self) -> 'str': + """The short version of the application bundle referenced by the alias. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.shortVersion() + + @property + def stationery(self) -> 'bool': + """Whether the alias is a stationery pad. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.stationery() + + @property + def type_identifier(self) -> 'str': + """The type identifier of the alias. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.typeIdentifier() + + @property + def version(self) -> 'str': + """The version of the application bundle referenced by the alias (visible at the bottom of the "Get Info" window). + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.version() + +
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
+ +
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
+ +
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.files(), XAFileList, filter)
+ +
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.filePackages(), XAFilePackageList, filter)
+ +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
+ + + + +
[docs]class XADiskList(XADiskItemList): + """A wrapper around lists of disks that employs fast enumeration techniques. + + All properties of disks can be called as methods on the wrapped list, returning a list containing each disk's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XADisk) + +
[docs] def capacity(self) -> list['float']: + """Retrieves the total number of bytes (free or used) on each disk in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("capacity"))
+ +
[docs] def ejectable(self) -> list['bool']: + """Retrieves the ejectable status of each disk in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("ejectable"))
+ +
[docs] def format(self) -> list['XAEventsApplication.Format']: + """Retrieves the file system format of each disk in the list. + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("format") + return [XAEventsApplication.Format(OSType(x.stringValue())) for x in ls]
+ +
[docs] def free_space(self) -> list['float']: + """Retrieves the number of free bytes left on each disk in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("freeSpace"))
+ +
[docs] def ignore_privileges(self) -> list['bool']: + """Retrieves the ignore privileges status for each disk in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("ignorePrivileges"))
+ +
[docs] def local_volume(self) -> list['bool']: + """Retrieves the local volume status for each disk in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("localVolume"))
+ +
[docs] def server(self) -> list['str']: + """Retrieves the server on which each disk in the list resides, AFP volumes only. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("server"))
+ +
[docs] def startup(self) -> list['bool']: + """Retrieves the startup disk status of each disk in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("startup"))
+ +
[docs] def zone(self) -> list['str']: + """Retrieves the zone in which each disk's server resides, AFP volumes only. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("zone"))
+ +
[docs] def by_capacity(self, capacity: float) -> 'XADisk': + """Retrieves the disk whose capacity matches the given capacity. + + .. versionadded:: 0.1.0 + """ + return self.by_property("capacity", capacity)
+ +
[docs] def by_ejectable(self, ejectable: bool) -> 'XADisk': + """Retrieves the disk whose ejectable status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("ejectable", ejectable)
+ +
[docs] def by_format(self, format: 'XAEventsApplication.Format') -> 'XADisk': + """Retrieves the disk whose format matches the given format. + + .. versionadded:: 0.1.0 + """ + return self.by_property("format", format.value)
+ +
[docs] def by_free_space(self, free_space: float) -> 'XADisk': + """Retrieves the disk whose free space matches the given amount. + + .. versionadded:: 0.1.0 + """ + return self.by_property("freeSpace", free_space)
+ +
[docs] def by_ignore_privileges(self, ignore_privileges: bool) -> 'XADisk': + """Retrieves the disk whose ignore privileges status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("ignorePrivileges", ignore_privileges)
+ +
[docs] def by_local_volume(self, local_volume: bool) -> 'XADisk': + """Retrieves the disk whose local volume status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("localVolume", local_volume)
+ +
[docs] def by_server(self, server: str) -> 'XADisk': + """Retrieves the disk whose server matches the given server. + + .. versionadded:: 0.1.0 + """ + return self.by_property("server", server)
+ +
[docs] def by_startup(self, startup: bool) -> 'XADisk': + """Retrieves the disk whose startup status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("startup", startup)
+ +
[docs] def by_zone(self, zone: str) -> 'XADisk': + """Retrieves the disk whose zone matches the given zone. + + .. versionadded:: 0.1.0 + """ + return self.by_property("zone", zone)
+ +
[docs]class XADisk(XADiskItem): + """A disk in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + @property + def capacity(self) -> 'float': + """The total number of bytes (free or used) on the disk. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.capacity() + + @property + def ejectable(self) -> 'bool': + """Whether the media can be ejected (floppies, CD's, and so on). + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.ejectable() + + @property + def format(self) -> 'XAEventsApplication.Format': + """The file system format of the disk. + + .. versionadded:: 0.1.0 + """ + return XAEventsApplication.Format(self.xa_elem.format()) + + @property + def free_space(self) -> 'float': + """The number of free bytes left on the disk. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.freeSpace() + + @property + def ignore_privileges(self) -> 'bool': + """Whether to ignore permissions on this disk. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.ignorePrivileges() + + @property + def local_volume(self) -> 'bool': + """Whether the media is a local volume (as opposed to a file server). + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.localVolume() + + @property + def server(self) -> 'str': + """The server on which the disk resides, AFP volumes only. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.server() + + @property + def startup(self) -> 'bool': + """Whether this disk is the boot disk. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.startup() + + @property + def zone(self) -> 'str': + """The zone in which the disk's server resides, AFP volumes only. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.zone() + +
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
+ +
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
+ +
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.files(), XAFileList, filter)
+ +
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.fileOackages(), XAFilePackageList, filter)
+ +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
+ + + + +
[docs]class XADomainList(XAList): + """A wrapper around lists of domains that employs fast enumeration techniques. + + All properties of domains can be called as methods on the wrapped list, returning a list containing each domain's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XADomain, filter) + +
[docs] def id(self) -> list['str']: + """Retrieves the unique identifier of each domain in the list + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def name(self) -> list['str']: + """Retrieves the name of each domain in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def by_id(self, id: str) -> 'XADomain': + """Retrieves the domain whose ID matches the given ID. + + .. versionadded:: 0.1.0 + """ + return self.by_property("id", id)
+ +
[docs] def by_name(self, name: str) -> 'XADomain': + """Retrieves the domain whose name matches the given name. + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XADomain(XAObject): + """A domain in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + @property + def application_support_folder(self) -> 'XAFolder': + """The Application Support folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.applicationSupportFolder(), XAFolder) + + @property + def applications_folder(self) -> 'XAFolder': + """The Applications folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.applicationsFolder(), XAFolder) + + @property + def desktop_pictures_folder(self) -> 'XAFolder': + """The Desktop Pictures folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.desktopPicturesFolder(), XAFolder) + + @property + def folder_action_scripts_folder(self) -> 'XAFolder': + """The Folder Action Scripts folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.folderActionScriptsFolder(), XAFolder) + + @property + def fonts_folder(self) -> 'XAFolder': + """The Fonts folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.fontsFolder(), XAFolder) + + @property + def id(self) -> 'str': + """The unique identifier of the domain. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.id() + + @property + def library_folder(self) -> 'XAFolder': + """The Library folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.libraryFolder(), XAFolder) + + @property + def name(self) -> 'str': + """The name of the domain. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.name() + + @property + def preferences_folder(self) -> 'XAFolder': + """The Preferences folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.preferencesFolder(), XAFolder) + + @property + def scripting_additions_folder(self) -> 'XAFolder': + """The Scripting Additions folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.scriptingAdditionsFolder(), XAFolder) + + @property + def scripts_folder(self) -> 'XAFolder': + """The Scripts folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.scriptsFolder(), XAFolder) + + @property + def shared_documents_folder(self) -> 'XAFolder': + """The Shared Documents folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.sharedDocumentsFolder(), XAFolder) + + @property + def speakable_items_folder(self) -> 'XAFolder': + """The Speakable Items folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.speakableItemsFolder(), XAFolder) + + @property + def utilities_folder(self) -> 'XAFolder': + """The Utilities folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.utilitiesFolder(), XAFolder) + + @property + def workflows_folder(self) -> 'XAFolder': + """The Automator Workflows folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.workflowsFolder(), XAFolder) + +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + + +
[docs]class XAClassicDomainObject(XADomain): + """The Classic domain in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + @property + def apple_menu_folder(self) -> 'XAFolder': + """The Apple Menu Items folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.appleMenuFolder(), XAFolder) + + @property + def control_panels_folder(self) -> 'XAFolder': + """The Control Panels folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.controlPanelsFolder(), XAFolder) + + @property + def control_strip_modules_folder(self) -> 'XAFolder': + """The Control Strip Modules folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.controlStripModulesFolder(), XAFolder) + + @property + def desktop_folder(self) -> 'XAFolder': + """The Classic Desktop folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.desktopFolder(), XAFolder) + + @property + def extensions_folder(self) -> 'XAFolder': + """The Extensions folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.extensionsFolder(), XAFolder) + + @property + def fonts_folder(self) -> 'XAFolder': + """The Fonts folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.fontsFolder(), XAFolder) + + @property + def launcher_items_folder(self) -> 'XAFolder': + """The Launcher Items folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.launcherItemsFolder(), XAFolder) + + @property + def preferences_folder(self) -> 'XAFolder': + """The Classic Preferences folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.preferencesFolder(), XAFolder) + + @property + def shutdown_folder(self) -> 'XAFolder': + """The Shutdown Items folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.shutdownFolder(), XAFolder) + + @property + def startup_items_folder(self) -> 'XAFolder': + """The StartupItems folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.startupItemsFolder(), XAFolder) + + @property + def system_folder(self) -> 'XAFolder': + """The System folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.systemFolder(), XAFolder) + +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
+ + + + +
[docs]class XAFileList(XADiskItemList): + """A wrapper around lists of files that employs fast enumeration techniques. + + All properties of files can be called as methods on the wrapped list, returning a list containing each file's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, object_class = None): + if object_class is None: + object_class = XAFile + super().__init__(properties, filter, object_class) + +
[docs] def creator_type(self) -> list['str']: + """Retrieves the OSType identifying the application that created each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("creatorType"))
+ +
[docs] def default_application(self) -> 'XADiskItemList': + """Retrieves the applications that will launch if each file in the list is opened. + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("defaultApplication") + return self._new_element(ls, XADiskItemList)
+ +
[docs] def file_type(self) -> list['str']: + """Retrieves the OSType identifying the type of data contained in each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("fileType"))
+ +
[docs] def kind(self) -> list['str']: + """Retrieves the kind of each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("kind"))
+ +
[docs] def product_version(self) -> list['str']: + """Retrieves the product version of each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("productVersion"))
+ +
[docs] def short_version(self) -> list['str']: + """Retrieves the short version of each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("shortVersion"))
+ +
[docs] def stationery(self) -> list['bool']: + """Retrieves the stationery status of each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("stationery"))
+ +
[docs] def type_identifier(self) -> list['str']: + """Retrieves the type identifier of each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("typeIdentifier"))
+ +
[docs] def version(self) -> list['str']: + """Retrieves the version of each file in the list. + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("version"))
+ +
[docs] def by_creator_type(self, creator_type: str) -> 'XAFile': + """Retrieves the file whose creator type matches the given creator type. + + .. versionadded:: 0.1.0 + """ + return self.by_property("creatorType", creator_type)
+ +
[docs] def by_default_application(self, default_application: 'XADiskItem') -> 'XAFile': + """Retrieves the file whose default application matches the given application. + + .. versionadded:: 0.1.0 + """ + return self.by_property("defaultApplication", default_application.xa_elem)
+ +
[docs] def by_file_type(self, file_type: str) -> 'XAFile': + """Retrieves the file whose file type matches the given file type. + + .. versionadded:: 0.1.0 + """ + return self.by_property("fileType", file_type)
+ +
[docs] def by_kind(self, kind: str) -> 'XAFile': + """Retrieves the file whose kind matches the given kind. + + .. versionadded:: 0.1.0 + """ + return self.by_property("kind", kind)
+ +
[docs] def by_product_version(self, product_version: str) -> 'XAFile': + """Retrieves the file whose product version matches the given version. + + .. versionadded:: 0.1.0 + """ + return self.by_property("productVersion", product_version)
+ +
[docs] def by_short_version(self, short_version: str) -> 'XAFile': + """Retrieves the file whose short version matches the given text. + + .. versionadded:: 0.1.0 + """ + return self.by_property("shortVersion", short_version)
+ +
[docs] def by_stationery(self, stationery: bool) -> 'XAFile': + """Retrieves the file whose stationery status matches the given boolean value. + + .. versionadded:: 0.1.0 + """ + return self.by_property("stationery", stationery)
+ +
[docs] def by_type_identifier(self, type_identifier: str) -> 'XAFile': + """Retrieves the file whose type identifier matches the given type identifier. + + .. versionadded:: 0.1.0 + """ + return self.by_property("typeIdentifier", type_identifier)
+ +
[docs] def by_version(self, version: str) -> 'XAFile': + """Retrieves the file whose version matches the given version. + + .. versionadded:: 0.1.0 + """ + return self.by_property("version", version)
+ +
[docs]class XAFile(XADiskItem): + """A file in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + @property + def creator_type(self) -> 'str': + """The OSType identifying the application that created the file. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.creatorType() + + @property + def default_application(self) -> 'XADiskItem': + """The application that will launch if the file is opened. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.defaultApplication(), XADiskItem) + + @default_application.setter + def default_application(self, default_application: XADiskItem): + self.set_property('defaultApplication', default_application.xa_elem) + + @property + def file_type(self) -> 'str': + """The OSType identifying the type of data contained in the file. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.fileType() + + @property + def kind(self) -> 'str': + """The kind of file, as shown in Finder. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.kind() + + @property + def product_version(self) -> 'str': + """The version of the product (visible at the top of the "Get Info" window). + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.productVersion() + + @property + def short_version(self) -> 'str': + """The short version of the file. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.shortVersion() + + @property + def stationery(self) -> 'bool': + """Whether the file is a stationery pad. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.stationery() + + @property + def type_identifier(self) -> 'str': + """The type identifier of the file. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.typeIdentifier() + + @property + def version(self) -> 'str': + """The version of the file (visible at the bottom of the "Get Info" window). + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.version()
+ + + + +
[docs]class XAFilePackageList(XAFileList): + """A wrapper around lists of file packages that employs fast enumeration techniques. + + All properties of file packages can be called as methods on the wrapped list, returning a list containing each package's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAFilePackage)
+ +
[docs]class XAFilePackage(XAFile): + """A file package in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
+ +
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
+ +
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.files(), XAFileList, filter)
+ +
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.filePackages(), XAFilePackageList, filter)
+ +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
+ + + + +
[docs]class XAFolderList(XADiskItemList): + """A wrapper around lists of folders that employs fast enumeration techniques. + + All properties of folders can be called as methods on the wrapped list, returning a list containing each folder's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAFolder)
+ +
[docs]class XAFolder(XADiskItem): + """A folder in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
+ +
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
+ +
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.files(), XAFileList, filter)
+ +
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.filePackages(), XAFilePackageList, filter)
+ +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned folders will have, or None + :type filter: Union[dict, None] + :return: The list of folders + :rtype: XAFolderList + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
+ + + + +
[docs]class XALocalDomainObject(XADomain): + """The local domain in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XANetworkDomainObject(XADomain): + """The network domain in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XASystemDomainObject(XADomain): + """The system domain in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUserDomainObject(XADomain): + """The user domain in the file system. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + @property + def desktop_folder(self) -> 'XAFolder': + """The user's Desktop folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.desktopFolder(), XAFolder) + + @property + def documents_folder(self) -> 'XAFolder': + """The user's Documents folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.documentsFolder(), XAFolder) + + @property + def downloads_folder(self) -> 'XAFolder': + """The user's Downloads folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.downloadsFolder(), XAFolder) + + @property + def favorites_folder(self) -> 'XAFolder': + """The user's Favorites folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.favoritesFolder(), XAFolder) + + @property + def home_folder(self) -> 'XAFolder': + """The user's Home folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.homeFolder(), XAFolder) + + @property + def movies_folder(self) -> 'XAFolder': + """The user's Movies folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.moviesFolder(), XAFolder) + + @property + def music_folder(self) -> 'XAFolder': + """The user's Music folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.musicFolder(), XAFolder) + + @property + def pictures_folder(self) -> 'XAFolder': + """The user's Pictures folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.picturesFolder(), XAFolder) + + @property + def public_folder(self) -> 'XAFolder': + """The user's Public folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.publicFolder(), XAFolder) + + @property + def sites_folder(self) -> 'XAFolder': + """The user's Sites folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.sitesFolder(), XAFolder) + + @property + def temporary_items_folder(self) -> 'XAFolder': + """The Temporary Items folder + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.temporaryItemsFolder(), XAFolder)
+ + + + +############# +### Media ### +############# +
[docs]class XAImageList(XAList, XAClipboardCodable): + """A wrapper around lists of images that employs fast enumeration techniques. + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XAImage + super().__init__(properties, obj_class, filter) + + self.modified = False #: Whether the list of images has been modified since it was initialized + + def __partial_init(self): + images = [None] * self.xa_elem.count() + + def init_images(ref, index, stop): + if isinstance(ref, str): + ref = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(ref).xa_elem) + elif isinstance(ref, ScriptingBridge.SBObject): + ref = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(ref.imageFile().POSIXPath()).xa_elem) + elif isinstance(ref, XAObject): + ref = AppKit.NSImage.alloc().initWithContentsOfURL_(ref.image_file.posix_path.xa_elem) + images[index] = ref + + self.xa_elem.enumerateObjectsUsingBlock_(init_images) + return AppKit.NSMutableArray.alloc().initWithArray_(images) + + def __apply_filter(self, filter_block, *args): + images = self.__partial_init() + + filtered_images = [None] * images.count() + def filter_image(image, index, *args): + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = filter_block(image, *args) + filter.setValue_forKey_(img, "inputImage") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + filtered_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(filter_image, [image, index, *args]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(filtered_images) + return self + +
[docs] def file(self) -> list[XAPath]: + return [x.file for x in self]
+ +
[docs] def horizontal_stitch(self) -> 'XAImage': + """Horizontally stacks each image in the list. + + The first image in the list is placed at the left side of the resulting image. + + :return: The resulting image after stitching + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + return XAImage.horizontal_stitch(self)
+ +
[docs] def vertical_stitch(self) -> 'XAImage': + """Vertically stacks each image in the list. + + The first image in the list is placed at the bottom side of the resulting image. + + :return: The resulting image after stitching + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + return XAImage.vertical_stitch(self)
+ +
[docs] def additive_composition(self) -> 'XAImage': + """Creates a composition image by adding the color values of each image in the list. + + :param images: The images to add together + :type images: list[XAImage] + :return: The resulting image composition + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image_data = [None] * self.xa_elem.count() + for index, image in enumerate(self.xa_elem): + if isinstance(image, str): + image = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(image).xa_elem) + image_data[index] = Quartz.CIImage.imageWithData_(image.TIFFRepresentation()) + + current_composition = None + while len(image_data) > 1: + img1 = image_data.pop(0) + img2 = image_data.pop(0) + composition_filter = Quartz.CIFilter.filterWithName_("CIAdditionCompositing") + composition_filter.setDefaults() + composition_filter.setValue_forKey_(img1, "inputImage") + composition_filter.setValue_forKey_(img2, "inputBackgroundImage") + current_composition = composition_filter.outputImage() + image_data.insert(0, current_composition) + + composition_rep = AppKit.NSCIImageRep.imageRepWithCIImage_(current_composition) + composition = AppKit.NSImage.alloc().initWithSize_(composition_rep.size()) + composition.addRepresentation_(composition_rep) + return XAImage(composition)
+ +
[docs] def subtractive_composition(self) -> 'XAImage': + """Creates a composition image by subtracting the color values of each image in the list successively. + + :param images: The images to create the composition from + :type images: list[XAImage] + :return: The resulting image composition + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image_data = [None] * self.xa_elem.count() + for index, image in enumerate(self.xa_elem): + if isinstance(image, str): + image = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(image).xa_elem) + image_data[index] = Quartz.CIImage.imageWithData_(image.TIFFRepresentation()) + + current_composition = None + while len(image_data) > 1: + img1 = image_data.pop(0) + img2 = image_data.pop(0) + composition_filter = Quartz.CIFilter.filterWithName_("CISubtractBlendMode") + composition_filter.setDefaults() + composition_filter.setValue_forKey_(img1, "inputImage") + composition_filter.setValue_forKey_(img2, "inputBackgroundImage") + current_composition = composition_filter.outputImage() + image_data.insert(0, current_composition) + + composition_rep = AppKit.NSCIImageRep.imageRepWithCIImage_(current_composition) + composition = AppKit.NSImage.alloc().initWithSize_(composition_rep.size()) + composition.addRepresentation_(composition_rep) + return XAImage(composition)
+ +
[docs] def edges(self, intensity: float = 1.0) -> 'XAImageList': + """Detects the edges in each image of the list and highlights them colorfully, blackening other areas of the images. + + :param intensity: The degree to which edges are highlighted. Higher is brighter. Defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIEdges") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def gaussian_blur(self, intensity: float = 10) -> 'XAImageList': + """Blurs each image in the list using a Gaussian filter. + + :param intensity: The strength of the blur effect, defaults to 10 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIGaussianBlur") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputRadius") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def reduce_noise(self, noise_level: float = 0.02, sharpness: float = 0.4) -> 'XAImageList': + """Reduces noise in each image of the list by sharpening areas with a luminance delta below the specified noise level threshold. + + :param noise_level: The threshold for luminance changes in an area below which will be considered noise, defaults to 0.02 + :type noise_level: float + :param sharpness: The sharpness of the resulting images, defaults to 0.4 + :type sharpness: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, noise_level, sharpness): + filter = Quartz.CIFilter.filterWithName_("CINoiseReduction") + filter.setDefaults() + filter.setValue_forKey_(noise_level, "inputNoiseLevel") + filter.setValue_forKey_(sharpness, "inputSharpness") + return filter + + return self.__apply_filter(filter_block, noise_level, sharpness)
+ +
[docs] def pixellate(self, pixel_size: float = 8.0) -> 'XAImageList': + """Pixellates each image in the list. + + :param pixel_size: The size of the pixels, defaults to 8.0 + :type pixel_size: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, pixel_size): + filter = Quartz.CIFilter.filterWithName_("CIPixellate") + filter.setDefaults() + filter.setValue_forKey_(pixel_size, "inputScale") + return filter + + return self.__apply_filter(filter_block, pixel_size)
+ +
[docs] def outline(self, threshold: float = 0.1) -> 'XAImageList': + """Outlines detected edges within each image of the list in black, leaving the rest transparent. + + :param threshold: The threshold to use when separating edge and non-edge pixels. Larger values produce thinner edge lines. Defaults to 0.1 + :type threshold: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, threshold): + filter = Quartz.CIFilter.filterWithName_("CILineOverlay") + filter.setDefaults() + filter.setValue_forKey_(threshold, "inputThreshold") + return filter + + return self.__apply_filter(filter_block, threshold)
+ +
[docs] def invert(self) -> 'XAImageList': + """Inverts the colors of each image in the list. + + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image): + filter = Quartz.CIFilter.filterWithName_("CIColorInvert") + filter.setDefaults() + return filter + + return self.__apply_filter(filter_block)
+ +
[docs] def sepia(self, intensity: float = 1.0) -> 'XAImageList': + """Applies a sepia filter to each image in the list; maps all colors of the images to shades of brown. + + :param intensity: The opacity of the sepia effect. A value of 0 will have no impact on the image. Defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CISepiaTone") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def vignette(self, intensity: float = 1.0) -> 'XAImageList': + """Applies vignette shading to the corners of each image in the list. + + :param intensity: The intensity of the vignette effect, defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIVignette") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def depth_of_field(self, focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] = None, intensity: float = 10.0, focal_region_saturation: float = 1.5) -> 'XAImageList': + """Applies a depth of field filter to each image in the list, simulating a tilt & shift effect. + + :param focal_region: Two points defining a line within each image to focus the effect around (pixels around the line will be in focus), or None to use the center third of the image, defaults to None + :type focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] + :param intensity: Controls the amount of distance around the focal region to keep in focus. Higher values decrease the distance before the out-of-focus effect starts. Defaults to 10.0 + :type intensity: float + :param focal_region_saturation: Adjusts the saturation of the focial region. Higher values increase saturation. Defaults to 1.5 (1.5x default saturation) + :type focal_region_saturation: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, focal_region, intensity, focal_region_saturation): + if focal_region is None: + center_top = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 3) + center_bottom = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 3 * 2) + focal_region = (center_top, center_bottom) + else: + point1 = Quartz.CIVector.vectorWithX_Y_(focal_region[0]) + point2 = Quartz.CIVector.vectorWithX_Y_(focal_region[1]) + focal_region = (point1, point2) + + filter = Quartz.CIFilter.filterWithName_("CIDepthOfField") + filter.setDefaults() + filter.setValue_forKey_(focal_region[0], "inputPoint0") + filter.setValue_forKey_(focal_region[1], "inputPoint1") + filter.setValue_forKey_(intensity, "inputRadius") + filter.setValue_forKey_(focal_region_saturation, "inputSaturation") + return filter + + return self.__apply_filter(filter_block, focal_region, intensity, focal_region_saturation)
+ +
[docs] def crystallize(self, crystal_size: float = 20.0) -> 'XAImageList': + """Applies a crystallization filter to each image in the list. Creates polygon-shaped color blocks by aggregating pixel values. + + :param crystal_size: The radius of the crystals, defaults to 20.0 + :type crystal_size: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, crystal_size): + filter = Quartz.CIFilter.filterWithName_("CICrystallize") + filter.setDefaults() + filter.setValue_forKey_(crystal_size, "inputRadius") + return filter + + return self.__apply_filter(filter_block, crystal_size)
+ +
[docs] def comic(self) -> 'XAImageList': + """Applies a comic filter to each image in the list. Outlines edges and applies a color halftone effect. + + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image): + filter = Quartz.CIFilter.filterWithName_("CIComicEffect") + filter.setDefaults() + return filter + + return self.__apply_filter(filter_block)
+ +
[docs] def pointillize(self, point_size: float = 20.0) -> 'XAImageList': + """Applies a pointillization filter to each image in the list. + + :param crystal_size: The radius of the points, defaults to 20.0 + :type crystal_size: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, point_size): + filter = Quartz.CIFilter.filterWithName_("CIPointillize") + filter.setDefaults() + filter.setValue_forKey_(point_size, "inputRadius") + return filter + + return self.__apply_filter(filter_block, point_size)
+ +
[docs] def bloom(self, intensity: float = 0.5) -> 'XAImageList': + """Applies a bloom effect to each image in the list. Softens edges and adds a glow. + + :param intensity: The strength of the softening and glow effects, defaults to 0.5 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIBloom") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def monochrome(self, color: XAColor, intensity: float = 1.0) -> 'XAImageList': + """Remaps the colors of each image in the list to shades of the specified color. + + :param color: The color of map each images colors to + :type color: XAColor + :param intensity: The strength of recoloring effect. Higher values map colors to darker shades of the provided color. Defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + ci_color = Quartz.CIColor.alloc().initWithColor_(color.xa_elem) + + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIColorMonochrome") + filter.setDefaults() + filter.setValue_forKey_(ci_color, "inputColor") + filter.setValue_forKey_(intensity, "inputIntensity") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def bump(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, curvature: float = 0.5) -> 'XAImageList': + """Adds a concave (inward) or convex (outward) bump to each image in the list at the specified location within each image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The radius of the bump in pixels, defaults to 300.0 + :type radius: float + :param curvature: Controls the direction and intensity of the bump's curvature. Positive values create convex bumps while negative values create concave bumps. Defaults to 0.5 + :type curvature: float + :return: The resulting images after applying the distortion + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + bumped_images = [None] * images.count() + def bump_image(image, index, center, radius, curvature): + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = Quartz.CIFilter.filterWithName_("CIBumpDistortion") + filter.setDefaults() + filter.setValue_forKey_(img, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(curvature, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + bumped_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(bump_image, [image, index, center, radius, curvature]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(bumped_images) + return self
+ +
[docs] def pinch(self, center: Union[tuple[int, int], None] = None, intensity: float = 0.5) -> 'XAImageList': + """Adds an inward pinch distortion to each image in the list at the specified location within each image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param intensity: Controls the scale of the pinch effect. Higher values stretch pixels away from the specified center to a greater degree. Defaults to 0.5 + :type intensity: float + :return: The resulting images after applying the distortion + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + pinched_images = [None] * images.count() + def pinch_image(image, index, center, intensity): + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = Quartz.CIFilter.filterWithName_("CIPinchDistortion") + filter.setDefaults() + filter.setValue_forKey_(img, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(intensity, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + pinched_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(pinch_image, [image, index, center, intensity]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(pinched_images) + return self
+ +
[docs] def twirl(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, angle: float = 3.14) -> 'XAImageList': + """Adds a twirl distortion to each image in the list by rotating pixels around the specified location within each image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The pixel radius around the centerpoint that defines the area to apply the effect to, defaults to 300.0 + :type radius: float + :param angle: The angle of the twirl in radians, defaults to 3.14 + :type angle: float + :return: The resulting images after applying the distortion + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + twirled_images = [None] * images.count() + def twirl_image(image, index, center, radius, angle): + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = Quartz.CIFilter.filterWithName_("CITwirlDistortion") + filter.setDefaults() + filter.setValue_forKey_(img, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(angle, "inputAngle") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + twirled_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(twirl_image, [image, index, center, radius, angle]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(twirled_images) + return self
+ +
[docs] def auto_enhance(self, correct_red_eye: bool = False, crop_to_features: bool = False, correct_rotation: bool = False) -> 'XAImageList': + """Attempts to enhance each image in the list by applying suggested filters. + + :param correct_red_eye: Whether to attempt red eye removal, defaults to False + :type correct_red_eye: bool, optional + :param crop_to_features: Whether to crop the images to focus on their main features, defaults to False + :type crop_to_features: bool, optional + :param correct_rotation: Whether attempt perspective correction by rotating the images, defaults to False + :type correct_rotation: bool, optional + :return: The list of enhanced images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + enhanced_images = [None] * images.count() + def enhance_image(image, index): + ci_image = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + + options = { + Quartz.kCIImageAutoAdjustRedEye: correct_red_eye, + Quartz.kCIImageAutoAdjustCrop: crop_to_features, + Quartz.kCIImageAutoAdjustLevel: correct_rotation + } + + enhancements = ci_image.autoAdjustmentFiltersWithOptions_(options) + for filter in enhancements: + filter.setValue_forKey_(ci_image, "inputImage") + ci_image = filter.outputImage() + + # Crop the result to the original image size + cropped = ci_image.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + enhanced_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(enhance_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(enhanced_images) + return self
+ +
[docs] def flip_horizontally(self) -> 'XAImageList': + """Flips each image in the list horizontally. + + :return: The list of flipped images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + flipped_images = [None] * images.count() + def flip_image(image, index): + flipped_image = AppKit.NSImage.alloc().initWithSize_(image.size()) + imageBounds = AppKit.NSMakeRect(0, 0, image.size().width, image.size().height) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(image.size().width, 0) + transform.scaleXBy_yBy_(-1, 1) + + flipped_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + flipped_images[index] = flipped_image + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(flip_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(flipped_images) + return self
+ +
[docs] def flip_vertically(self) -> 'XAImageList': + """Flips each image in the list vertically. + + :return: The list of flipped images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + flipped_images = [None] * images.count() + def flip_image(image, index): + flipped_image = AppKit.NSImage.alloc().initWithSize_(image.size()) + imageBounds = AppKit.NSMakeRect(0, 0, image.size().width, image.size().height) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(0, image.size().height) + transform.scaleXBy_yBy_(1, -1) + + flipped_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + flipped_images[index] = flipped_image + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(flip_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(flipped_images) + return self
+ +
[docs] def rotate(self, degrees: float) -> 'XAImageList': + """Rotates each image in the list by the specified amount of degrees. + + :param degrees: The number of degrees to rotate the images by + :type degrees: float + :return: The list of rotated images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + sinDegrees = abs(math.sin(degrees * math.pi / 180.0)) + cosDegrees = abs(math.cos(degrees * math.pi / 180.0)) + + images = self.__partial_init() + + rotated_images = [None] * images.count() + def rotate_image(image, index): + new_size = Quartz.CGSizeMake(image.size().height * sinDegrees + image.size().width * cosDegrees, image.size().width * sinDegrees + image.size().height * cosDegrees) + rotated_image = AppKit.NSImage.alloc().initWithSize_(new_size) + + imageBounds = Quartz.CGRectMake((new_size.width - image.size().width) / 2, (new_size.height - image.size().height) / 2, image.size().width, image.size().height) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(new_size.width / 2, new_size.height / 2) + transform.rotateByDegrees_(degrees) + transform.translateXBy_yBy_(-new_size.width / 2, -new_size.height / 2) + + rotated_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + rotated_image.unlockFocus() + + rotated_images[index] = rotated_image + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(rotate_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(rotated_images) + return self
+ +
[docs] def crop(self, size: tuple[int, int], corner: Union[tuple[int, int], None] = None) -> 'XAImageList': + """Crops each image in the list to the specified dimensions. + + :param size: The dimensions to crop each image to + :type size: tuple[int, int] + :param corner: The bottom-left location to crom each image from, or None to use (0, 0), defaults to None + :type corner: Union[tuple[int, int], None] + :return: The list of cropped images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if corner is None: + # No corner provided -- use (0,0) by default + corner = (0, 0) + + images = self.__partial_init() + + cropped_images = [None] * images.count() + def crop_image(image, index): + cropped_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(size[0], size[1])) + imageBounds = AppKit.NSMakeRect(corner[0], corner[1], image.size().width, image.size().height) + + cropped_image.lockFocus() + image.drawInRect_(imageBounds) + cropped_image.unlockFocus() + cropped_images[index] = cropped_image + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(crop_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(cropped_images) + return self
+ +
[docs] def scale(self, scale_factor_x: float, scale_factor_y: Union[float, None] = None) -> 'XAImageList': + """Scales each image in the list by the specified horizontal and vertical factors. + + :param scale_factor_x: The factor by which to scale each image in the X dimension + :type scale_factor_x: float + :param scale_factor_y: The factor by which to scale each image in the Y dimension, or None to match the horizontal factor, defaults to None + :type scale_factor_y: Union[float, None] + :return: The list of scaled images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if scale_factor_y is None: + scale_factor_y = scale_factor_x + + images = self.__partial_init() + + scaled_images = [None] * self.xa_elem.count() + def scale_image(image, index): + scaled_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(image.size().width * scale_factor_x, image.size().height * scale_factor_y)) + imageBounds = AppKit.NSMakeRect(0, 0, image.size().width, image.size().height) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.scaleXBy_yBy_(scale_factor_x, scale_factor_y) + + scaled_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + scaled_image.unlockFocus() + scaled_images[index] = scaled_image + + threads = [None] * self.xa_elem.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(scale_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(scaled_images) + return self
+ +
[docs] def pad(self, horizontal_border_width: int = 50, vertical_border_width: int = 50, pad_color: Union[XAColor, None] = None) -> 'XAImageList': + """Pads each image in the list with the specified color; add a border around each image in the list with the specified vertical and horizontal width. + + :param horizontal_border_width: The border width, in pixels, in the x-dimension, defaults to 50 + :type horizontal_border_width: int + :param vertical_border_width: The border width, in pixels, in the y-dimension, defaults to 50 + :type vertical_border_width: int + :param pad_color: The color of the border, or None for a white border, defaults to None + :type pad_color: Union[XAColor, None] + :return: The list of padded images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if pad_color is None: + # No color provided -- use white by default + pad_color = XAColor.white() + + images = self.__partial_init() + + padded_images = [None] * images.count() + def pad_image(image, index): + new_width = image.size().width + horizontal_border_width * 2 + new_height = image.size().height + vertical_border_width * 2 + color_swatch = pad_color.make_swatch(new_width, new_height) + + color_swatch.xa_elem.lockFocus() + bounds = AppKit.NSMakeRect(horizontal_border_width, vertical_border_width, image.size().width, image.size().height) + image.drawInRect_(bounds) + color_swatch.xa_elem.unlockFocus() + padded_images[index] = color_swatch.xa_elem + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(pad_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(padded_images) + return self
+ +
[docs] def overlay_image(self, image: 'XAImage', location: Union[tuple[int, int], None] = None, size: Union[tuple[int, int], None] = None) -> 'XAImageList': + """Overlays an image on top of each image in the list, at the specified location, with the specified size. + + :param image: The image to overlay on top of each image in the list + :type image: XAImage + :param location: The bottom-left point of the overlaid image in the results, or None to use the bottom-left point of each background image, defaults to None + :type location: Union[tuple[int, int], None] + :param size: The width and height of the overlaid image, or None to use the overlaid's images existing width and height, or (-1, -1) to use the dimensions of each background images, defaults to None + :type size: Union[tuple[int, int], None] + :return: The list of images with the specified image overlaid on top of them + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use the bottom-left point of the background image by default + location = (0, 0) + + images = self.__partial_init() + overlayed_images = [None] * images.count() + def overlay_image(img, index, image, size, location): + if size is None: + # No dimensions provided -- use size of overlay image by default + size = image.size + elif size == (-1, -1): + # Use remaining width/height of background image + size = (img.size().width - location[0], img.size().height - location[1]) + elif size[0] == -1: + # Use remaining width of background image + provided height + size = (img.size().width - location[0], size[1]) + elif size[1] == -1: + # Use remaining height of background image + provided width + size = (size[1], img.size().width - location[1]) + + img.lockFocus() + bounds = AppKit.NSMakeRect(location[0], location[1], size[0], size[1]) + image.xa_elem.drawInRect_(bounds) + img.unlockFocus() + overlayed_images[index] = img + + threads = [None] * images.count() + for index, img in enumerate(images): + threads[index] = self._spawn_thread(overlay_image, [img, index, image, size, location]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(overlayed_images) + return self
+ +
[docs] def overlay_text(self, text: str, location: Union[tuple[int, int], None] = None, font_size: float = 12, font_color: Union[XAColor, None] = None) -> 'XAImageList': + """Overlays text of the specified size and color at the provided location within each image of the list. + + :param text: The text to overlay onto each image of the list + :type text: str + :param location: The bottom-left point of the start of the text, or None to use (5, 5), defaults to None + :type location: Union[tuple[int, int], None] + :param font_size: The font size, in pixels, of the text, defaults to 12 + :type font_size: float + :param font_color: The color of the text, or None to use black, defaults to None + :type font_color: XAColor + :return: The list of images with the specified text overlaid on top of them + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use (5, 5) by default + location = (5, 5) + + if font_color is None: + # No color provided -- use black by default + font_color = XAColor.black() + + font = AppKit.NSFont.userFontOfSize_(font_size) + images = self.__partial_init() + overlayed_images = [None] * self.xa_elem.count() + def overlay_text(image, index): + textRect = Quartz.CGRectMake(location[0], 0, image.size().width - location[0], location[1]) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: font_color.xa_elem + } + + image.lockFocus() + AppKit.NSString.alloc().initWithString_(text).drawInRect_withAttributes_(textRect, attributes) + image.unlockFocus() + overlayed_images[index] = image + + threads = [None] * self.xa_elem.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(overlay_text, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(overlayed_images) + return self
+ +
[docs] def extract_text(self) -> list[str]: + """Extracts and returns a list of all visible text in each image of the list. + + :return: The array of extracted text strings + :rtype: list[str] + + :Example: + + >>> import PyXA + >>> test = PyXA.XAImage("/Users/ExampleUser/Downloads/Example.jpg") + >>> print(test.extract_text()) + ["HERE'S TO THE", 'CRAZY ONES', 'the MISFITS the REBELS', 'THE TROUBLEMAKERS', ...] + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + extracted_strings = [None] * self.xa_elem.count() + def get_text(image, index): + # Prepare CGImage + ci_image = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + context = Quartz.CIContext.alloc().initWithOptions_(None) + img = context.createCGImage_fromRect_(ci_image, ci_image.extent()) + + # Handle request completion + image_strings = [] + def recognize_text_handler(request, error): + observations = request.results() + for observation in observations: + recognized_strings = observation.topCandidates_(1)[0].string() + image_strings.append(recognized_strings) + + # Perform request and return extracted text + request = Vision.VNRecognizeTextRequest.alloc().initWithCompletionHandler_(recognize_text_handler) + request_handler = Vision.VNImageRequestHandler.alloc().initWithCGImage_options_(img, None) + request_handler.performRequests_error_([request], None) + extracted_strings[index] = image_strings + + threads = [None] * self.xa_elem.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(get_text, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + return extracted_strings
+ +
[docs] def show_in_preview(self): + """Opens each image in the list in Preview. + + .. versionadded:: 0.1.0 + """ + for image in self: + image.show_in_preview()
+ +
[docs] def save(self, file_paths: list[Union[XAPath, str]]): + """Saves each image to a file on the disk. + + :param file_path: The path at which to save the image file. Any existing file at that location will be overwritten, defaults to None + :type file_path: Union[XAPath, str, None] + + .. versionadded:: 0.1.0 + """ + for index, image in enumerate(self): + path = None + if len(file_paths) > index: + path = file_paths[index] + image.save(path)
+ +
[docs] def get_clipboard_representation(self) -> list[AppKit.NSImage]: + """Gets a clipboard-codable representation of each image in the list. + + When the clipboard content is set to a list of image, the raw data of each image is added to the clipboard. You can then + + :return: A list of media item file URLs + :rtype: list[NSURL] + + .. versionadded:: 0.0.8 + """ + data = [] + for image in self.__partial_init(): + if image.TIFFRepresentation(): + data.append(image) + return data
+ +
[docs]class XAImage(XAObject, XAClipboardCodable): + """A wrapper around NSImage with specialized automation methods. + + .. versionadded:: 0.0.2 + """ + + def __init__(self, image_reference: Union[str, XAPath, AppKit.NSURL, AppKit.NSImage, None] = None, data: Union[AppKit.NSData, None] = None): + self.size: tuple[int, int] #: The dimensions of the image + self.file: Union[XAPath, None] = None #: The path to the image file, if one exists + self.data: str #: The TIFF representation of the image + self.modified: bool = False #: Whether the image data has been modified since the object was originally created + + self.xa_elem = None + + self.__vibrance = None + self.__gamma = None + self.__tint = None + self.__temperature = None + self.__white_point = None + self.__highlight = None + self.__shadow = None + + if data is not None: + # Deprecated as of 0.1.0 -- Pass data as the image_reference instead + self.xa_elem = AppKit.NSImage.alloc().initWithData_(data) + else: + if image_reference is None: + # No reference provided, just initialize a blank image + self.xa_elem = AppKit.NSImage.alloc().init() + else: + self.file = image_reference + if isinstance(image_reference, dict): + # Image reference is provided by an XAList + self.xa_elem = image_reference["element"] + if isinstance(self.xa_elem, str): + # The reference is a string -- reinitialize using it + self.file = self.xa_elem + self.xa_elem = XAImage(self.xa_elem).xa_elem + elif isinstance(self.xa_elem, XAImage): + # The reference is another XAImage object + self.file = self.xa_elem.file + self.xa_elem = self.xa_elem.xa_elem + + elif isinstance(image_reference, AppKit.NSImage): + # The reference is an Objective-C image object + self.xa_elem = AppKit.NSImage.alloc().initWithData_(image_reference.TIFFRepresentation()) + + elif isinstance(image_reference, AppKit.NSData): + # The reference is raw image data + self.xa_elem = AppKit.NSImage.alloc().initWithData_(data) + + elif isinstance(image_reference, XAPath): + # The reference is an XAPath + self.file = image_reference.path + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(image_reference.xa_elem) + + elif isinstance(image_reference, XAURL): + # The reference is an XAURL + self.file = image_reference.url + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(image_reference.xa_elem) + + elif isinstance(image_reference, XAImage): + # The reference is another XAImage + self.file = image_reference.file + self.xa_elem = image_reference.xa_elem + + elif isinstance(image_reference, str): + # The reference is to a file or a none-file URL + if "://" in image_reference: + # The reference is to a non-file URL + image_reference = XAURL(image_reference).xa_elem + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(image_reference) + elif os.path.exists(image_reference) or os.path.exists(os.getcwd() + "/" + image_reference): + # The reference is to a file + image_reference = XAPath(image_reference).xa_elem + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(image_reference) + else: + # The reference is just text -- make an image out of it! + font = AppKit.NSFont.monospacedSystemFontOfSize_weight_(15, AppKit.NSFontWeightMedium) + text = AppKit.NSString.alloc().initWithString_(image_reference) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: XAColor.black().xa_elem + } + text_size = text.sizeWithAttributes_(attributes) + + # Make a white background to overlay the text on + swatch = XAColor.white().make_swatch(text_size.width + 20, text_size.height + 20) + text_rect = AppKit.NSMakeRect(10, 10, text_size.width, text_size.height) + + # Overlay the text + swatch.xa_elem.lockFocus() + text.drawInRect_withAttributes_(text_rect, attributes) + swatch.xa_elem.unlockFocus() + self.xa_elem = swatch.xa_elem + + elif isinstance(image_reference, XAObject): + # Must obtain the image representation using the XAImageLike protocol + self.xa_elem = XAImage(image_reference.get_image_representation()).xa_elem + + def __update_image(self, modified_image: Quartz.CIImage) -> 'XAImage': + # Crop the result to the original image size + cropped = modified_image.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, self.size[0] * 2, self.size[1] * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + + # Update internal data + self.xa_elem = result + self.modified = True + return self + + @property + def size(self) -> tuple[int, int]: + """The dimensions of the image, in pixels. + + .. versionadded:: 0.1.0 + """ + return tuple(self.xa_elem.size()) + + @property + def data(self) -> AppKit.NSData: + return self.xa_elem.TIFFRepresentation() + + @property + def has_alpha_channel(self) -> bool: + """Whether the image has an alpha channel or not. + + .. versionadded:: 0.1.0 + """ + reps = self.xa_elem.representations() + if len(reps) > 0: + return reps[0].hasAlpha() + # TODO: Make sure this is never a false negative + return False + + @property + def is_opaque(self) -> bool: + """Whether the image contains transparent pixels or not. + + .. versionadded:: 0.1.0 + """ + reps = self.xa_elem.representations() + if len(reps) > 0: + return reps[0].isOpaque() + # TODO: Make sure this is never a false negative + return False + + @property + def color_space_name(self) -> Union[str, None]: + """The name of the color space that the image currently uses. + + .. versionadded:: 0.1.0 + """ + reps = self.xa_elem.representations() + if len(reps) > 0: + return reps[0].colorSpaceName() + # TODO: Make sure this is never a false negative + return None + + @property + def gamma(self) -> float: + """The gamma value for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__gamma is not None: + return self.__gamma + return -1 + + @gamma.setter + def gamma(self, gamma: float): + self.__gamma = gamma + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIGammaAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(gamma, "inputPower") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + + @property + def vibrance(self) -> Union[float, None]: + """The vibrance value for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__vibrance is not None: + return self.__vibrance + return -1 + + @vibrance.setter + def vibrance(self, vibrance: float = 1): + self.__vibrance = vibrance + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIVibrance") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(vibrance, "inputAmount") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped) + + @property + def tint(self) -> Union[float, None]: + """The tint setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__tint is not None: + return self.__tint + return -1 + + @tint.setter + def tint(self, tint: float): + # -100 to 100 + temp_and_tint = Quartz.CIVector.vectorWithX_Y_(6500, tint) + self.__tint = tint + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CITemperatureAndTint") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(temp_and_tint, "inputTargetNeutral") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + + @property + def temperature(self) -> Union[float, None]: + """The temperature setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__temperature is not None: + return self.__temperature + return -1 + + @temperature.setter + def temperature(self, temperature: float): + # 2000 to inf + temp_and_tint = Quartz.CIVector.vectorWithX_Y_(temperature, 0) + self.__temperature = temperature + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CITemperatureAndTint") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(temp_and_tint, "inputTargetNeutral") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + + @property + def white_point(self) -> Union['XAColor', None]: + """The white point setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__white_point is not None: + return self.__white_point + return -1 + + @white_point.setter + def white_point(self, white_point: XAColor): + self.__white_point = white_point + ci_white_point = Quartz.CIColor.alloc().initWithColor_(white_point.xa_elem) + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIWhitePointAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(ci_white_point, "inputColor") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + + @property + def highlight(self) -> float: + """The highlight setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__highlight is not None: + return self.__highlight + return -1 + + @highlight.setter + def highlight(self, highlight: float): + self.__highlight = highlight + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIHighlightShadowAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(highlight, "inputHighlightAmount") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + + @property + def shadow(self) -> float: + """The shadow setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__shadow is not None: + return self.__shadow + return -1 + + @shadow.setter + def shadow(self, shadow: float): + self.__shadow = shadow + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIHighlightShadowAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(self.__highlight or 1, "inputHighlightAmount") + filter.setValue_forKey_(shadow, "inputShadowAmount") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + +
[docs] def open(*images: Union[str, XAPath, list[Union[str, XAPath]]]) -> Union['XAImage', XAImageList]: + """Initializes one or more images from files. + + :param images: The image(s) to open + :type images: Union[str, XAPath, list[Union[str, XAPath]]] + :return: The newly created image object, or a list of image objects + :rtype: Union[XAImage, XAImageList] + + .. versionadded:: 0.1.0 + """ + if len(images) == 1: + images = images[0] + + if isinstance(images, list) or isinstance(images, tuple): + return XAImageList({"element": images}) + else: + return XAImage(images)
+ +
[docs] def image_from_text(text: str, font_size: int = 15, font_color: XAColor = XAColor.black(), background_color: XAColor = XAColor.white(), inset: int = 10) -> 'XAImage': + """Initializes an image of the provided text overlaid on the specified background color. + + :param text: The text to create an image of + :type text: str + :param font_size: The font size of the text, defaults to 15 + :type font_size: int, optional + :param font_color: The color of the text, defaults to XAColor.black() + :type font_color: XAColor, optional + :param background_color: The color to overlay the text on top of, defaults to XAColor.white() + :type background_color: XAColor, optional + :param inset: The width of the space between the text and the edge of the background color in the resulting image, defaults to 10 + :type inset: int, optional + :return: XAImage + :rtype: The resulting image object + + .. versionadded:: 0.1.0 + """ + font = AppKit.NSFont.monospacedSystemFontOfSize_weight_(font_size, AppKit.NSFontWeightMedium) + text = AppKit.NSString.alloc().initWithString_(text) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: font_color.xa_elem + } + text_size = text.sizeWithAttributes_(attributes) + + # Make a white background to overlay the text on + swatch = background_color.make_swatch(text_size.width + inset * 2, text_size.height + inset * 2) + text_rect = AppKit.NSMakeRect(inset, inset, text_size.width, text_size.height) + + # Overlay the text + swatch.xa_elem.lockFocus() + text.drawInRect_withAttributes_(text_rect, attributes) + swatch.xa_elem.unlockFocus() + return swatch
+ +
[docs] def edges(self, intensity: float = 1.0) -> 'XAImage': + """Detects the edges in the image and highlights them colorfully, blackening other areas of the image. + + :param intensity: The degree to which edges are highlighted. Higher is brighter. Defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIEdges") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def gaussian_blur(self, intensity: float = 10) -> 'XAImage': + """Blurs the image using a Gaussian filter. + + :param intensity: The strength of the blur effect, defaults to 10 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIGaussianBlur") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputRadius") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def reduce_noise(self, noise_level: float = 0.02, sharpness: float = 0.4) -> 'XAImage': + """Reduces noise in the image by sharpening areas with a luminance delta below the specified noise level threshold. + + :param noise_level: The threshold for luminance changes in an area below which will be considered noise, defaults to 0.02 + :type noise_level: float + :param sharpness: The sharpness of the resulting image, defaults to 0.4 + :type sharpness: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CINoiseReduction") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(noise_level, "inputNoiseLevel") + filter.setValue_forKey_(sharpness, "inputSharpness") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def pixellate(self, pixel_size: float = 8.0) -> 'XAImage': + """Pixellates the image. + + :param pixel_size: The size of the pixels, defaults to 8.0 + :type pixel_size: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIPixellate") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(pixel_size, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def outline(self, threshold: float = 0.1) -> 'XAImage': + """Outlines detected edges within the image in black, leaving the rest transparent. + + :param threshold: The threshold to use when separating edge and non-edge pixels. Larger values produce thinner edge lines. Defaults to 0.1 + :type threshold: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CILineOverlay") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(threshold, "inputThreshold") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def invert(self) -> 'XAImage': + """Inverts the color of the image. + + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIColorInvert") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def sepia(self, intensity: float = 1.0) -> 'XAImage': + """Applies a sepia filter to the image; maps all colors of the image to shades of brown. + + :param intensity: The opacity of the sepia effect. A value of 0 will have no impact on the image. Defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CISepiaTone") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def vignette(self, intensity: float = 1.0) -> 'XAImage': + """Applies vignette shading to the corners of the image. + + :param intensity: The intensity of the vignette effect, defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIVignette") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def depth_of_field(self, focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] = None, intensity: float = 10.0, focal_region_saturation: float = 1.5) -> 'XAImage': + """Applies a depth of field filter to the image, simulating a tilt & shift effect. + + :param focal_region: Two points defining a line within the image to focus the effect around (pixels around the line will be in focus), or None to use the center third of the image, defaults to None + :type focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] + :param intensity: Controls the amount of distance around the focal region to keep in focus. Higher values decrease the distance before the out-of-focus effect starts. Defaults to 10.0 + :type intensity: float + :param focal_region_saturation: Adjusts the saturation of the focial region. Higher values increase saturation. Defaults to 1.5 (1.5x default saturation) + :type focal_region_saturation: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if focal_region is None: + center_top = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 3) + center_bottom = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 3 * 2) + focal_region = (center_top, center_bottom) + else: + point1 = Quartz.CIVector.vectorWithX_Y_(focal_region[0]) + point2 = Quartz.CIVector.vectorWithX_Y_(focal_region[1]) + focal_region = (point1, point2) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIDepthOfField") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(focal_region[0], "inputPoint0") + filter.setValue_forKey_(focal_region[1], "inputPoint1") + filter.setValue_forKey_(intensity, "inputRadius") + filter.setValue_forKey_(focal_region_saturation, "inputSaturation") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def crystallize(self, crystal_size: float = 20.0) -> 'XAImage': + """Applies a crystallization filter to the image. Creates polygon-shaped color blocks by aggregating pixel values. + + :param crystal_size: The radius of the crystals, defaults to 20.0 + :type crystal_size: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CICrystallize") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(crystal_size, "inputRadius") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def comic(self) -> 'XAImage': + """Applies a comic filter to the image. Outlines edges and applies a color halftone effect. + + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIComicEffect") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def pointillize(self, point_size: float = 20.0) -> 'XAImage': + """Applies a pointillization filter to the image. + + :param crystal_size: The radius of the points, defaults to 20.0 + :type crystal_size: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIPointillize") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(point_size, "inputRadius") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def bloom(self, intensity: float = 0.5) -> 'XAImage': + """Applies a bloom effect to the image. Softens edges and adds a glow. + + :param intensity: The strength of the softening and glow effects, defaults to 0.5 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIBloom") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def monochrome(self, color: XAColor, intensity: float = 1.0) -> 'XAImage': + """Remaps the colors of the image to shades of the specified color. + + :param color: The color of map the image's colors to + :type color: XAColor + :param intensity: The strength of recoloring effect. Higher values map colors to darker shades of the provided color. Defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + ci_color = Quartz.CIColor.alloc().initWithColor_(color.xa_elem) + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIColorMonochrome") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(ci_color, "inputColor") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def bump(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, curvature: float = 0.5) -> 'XAImage': + """Creates a concave (inward) or convex (outward) bump at the specified location within the image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The radius of the bump in pixels, defaults to 300.0 + :type radius: float + :param curvature: Controls the direction and intensity of the bump's curvature. Positive values create convex bumps while negative values create concave bumps. Defaults to 0.5 + :type curvature: float + :return: The resulting image after applying the distortion + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIBumpDistortion") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(curvature, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def pinch(self, center: Union[tuple[int, int], None] = None, intensity: float = 0.5) -> 'XAImage': + """Creates an inward pinch distortion at the specified location within the image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param intensity: Controls the scale of the pinch effect. Higher values stretch pixels away from the specified center to a greater degree. Defaults to 0.5 + :type intensity: float + :return: The resulting image after applying the distortion + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIPinchDistortion") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(intensity, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def twirl(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, angle: float = 3.14) -> 'XAImage': + """Creates a twirl distortion by rotating pixels around the specified location within the image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The pixel radius around the centerpoint that defines the area to apply the effect to, defaults to 300.0 + :type radius: float + :param angle: The angle of the twirl in radians, defaults to 3.14 + :type angle: float + :return: The resulting image after applying the distortion + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CITwirlDistortion") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(angle, "inputAngle") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def auto_enhance(self, correct_red_eye: bool = False, crop_to_features: bool = False, correct_rotation: bool = False) -> 'XAImage': + """Attempts to enhance the image by applying suggested filters. + + :param correct_red_eye: Whether to attempt red eye removal, defaults to False + :type correct_red_eye: bool, optional + :param crop_to_features: Whether to crop the image to focus on the main features with it, defaults to False + :type crop_to_features: bool, optional + :param correct_rotation: Whether attempt perspective correction by rotating the image, defaults to False + :type correct_rotation: bool, optional + :return: The resulting image after applying the enchantments + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + ci_image = Quartz.CIImage.imageWithData_(self.data) + options = { + Quartz.kCIImageAutoAdjustRedEye: correct_red_eye, + Quartz.kCIImageAutoAdjustCrop: crop_to_features, + Quartz.kCIImageAutoAdjustLevel: correct_rotation + } + enhancements = ci_image.autoAdjustmentFiltersWithOptions_(options) + print(enhancements) + for filter in enhancements: + filter.setValue_forKey_(ci_image, "inputImage") + ci_image = filter.outputImage() + return self.__update_image(ci_image)
+ +
[docs] def flip_horizontally(self) -> 'XAImage': + """Flips the image horizontally. + + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + flipped_image = AppKit.NSImage.alloc().initWithSize_(self.xa_elem.size()) + imageBounds = AppKit.NSMakeRect(0, 0, self.size[0], self.size[1]) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(self.size[0], 0) + transform.scaleXBy_yBy_(-1, 1) + + flipped_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + self.xa_elem = flipped_image + self.modified = True + return self
+ +
[docs] def flip_vertically(self) -> 'XAImage': + """Flips the image vertically. + + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + flipped_image = AppKit.NSImage.alloc().initWithSize_(self.xa_elem.size()) + imageBounds = AppKit.NSMakeRect(0, 0, self.size[0], self.size[1]) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(0, self.size[1]) + transform.scaleXBy_yBy_(1, -1) + + flipped_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + self.xa_elem = flipped_image + self.modified = True + return self
+ +
[docs] def rotate(self, degrees: float) -> 'XAImage': + """Rotates the image clockwise by the specified number of degrees. + + :param degrees: The number of degrees to rotate the image by + :type degrees: float + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + sinDegrees = abs(math.sin(degrees * math.pi / 180.0)) + cosDegrees = abs(math.cos(degrees * math.pi / 180.0)) + newSize = Quartz.CGSizeMake(self.size[1] * sinDegrees + self.size[0] * cosDegrees, self.size[0] * sinDegrees + self.size[1] * cosDegrees) + rotated_image = AppKit.NSImage.alloc().initWithSize_(newSize) + + imageBounds = Quartz.CGRectMake((newSize.width - self.size[0]) / 2, (newSize.height - self.size[1]) / 2, self.size[0], self.size[1]) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(newSize.width / 2, newSize.height / 2) + transform.rotateByDegrees_(degrees) + transform.translateXBy_yBy_(-newSize.width / 2, -newSize.height / 2) + + rotated_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + rotated_image.unlockFocus() + self.xa_elem = rotated_image + self.modified = True + return self
+ +
[docs] def crop(self, size: tuple[int, int], corner: Union[tuple[int, int], None] = None) -> 'XAImage': + """Crops the image to the specified dimensions. + + :param size: The width and height of the resulting image + :type size: tuple[int, int] + :param corner: The bottom-left corner location from which to crop the image, or None to use (0, 0), defaults to None + :type corner: Union[tuple[int, int], None] + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if corner is None: + # No corner provided -- use (0,0) by default + corner = (0, 0) + + cropped_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(size[0], size[1])) + imageBounds = AppKit.NSMakeRect(corner[0], corner[1], self.size[0], self.size[1]) + + cropped_image.lockFocus() + self.xa_elem.drawInRect_(imageBounds) + cropped_image.unlockFocus() + self.xa_elem = cropped_image + self.modified = True + return self
+ +
[docs] def scale(self, scale_factor_x: float, scale_factor_y: Union[float, None] = None) -> 'XAImage': + """Scales the image by the specified horizontal and vertical factors. + + :param scale_factor_x: The factor by which to scale the image in the X dimension + :type scale_factor_x: float + :param scale_factor_y: The factor by which to scale the image in the Y dimension, or None to match the horizontal factor, defaults to None + :type scale_factor_y: Union[float, None] + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if scale_factor_y is None: + scale_factor_y = scale_factor_x + + scaled_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(self.size[0] * scale_factor_x, self.size[1] * scale_factor_y)) + imageBounds = AppKit.NSMakeRect(0, 0, self.size[0], self.size[1]) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.scaleXBy_yBy_(scale_factor_x, scale_factor_y) + + scaled_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + scaled_image.unlockFocus() + self.xa_elem = scaled_image + self.modified = True + return self
+ +
[docs] def pad(self, horizontal_border_width: int = 50, vertical_border_width: int = 50, pad_color: Union[XAColor, None] = None) -> 'XAImage': + """Pads the image with the specified color; adds a border around the image with the specified vertical and horizontal width. + + :param horizontal_border_width: The border width, in pixels, in the x-dimension, defaults to 50 + :type horizontal_border_width: int + :param vertical_border_width: The border width, in pixels, in the y-dimension, defaults to 50 + :type vertical_border_width: int + :param pad_color: The color of the border, or None for a white border, defaults to None + :type pad_color: Union[XAColor, None] + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if pad_color is None: + # No color provided -- use white by default + pad_color = XAColor.white() + + new_width = self.size[0] + horizontal_border_width * 2 + new_height = self.size[1] + vertical_border_width * 2 + color_swatch = pad_color.make_swatch(new_width, new_height) + + color_swatch.xa_elem.lockFocus() + bounds = AppKit.NSMakeRect(horizontal_border_width, vertical_border_width, self.size[0], self.size[1]) + self.xa_elem.drawInRect_(bounds) + color_swatch.xa_elem.unlockFocus() + self.xa_elem = color_swatch.xa_elem + self.modified = True + return self
+ +
[docs] def overlay_image(self, image: 'XAImage', location: Union[tuple[int, int], None] = None, size: Union[tuple[int, int], None] = None) -> 'XAImage': + """Overlays an image on top of this image, at the specified location, with the specified size. + + :param image: The image to overlay on top of this image + :type image: XAImage + :param location: The bottom-left point of the overlaid image in the result, or None to use the bottom-left point of the background image, defaults to None + :type location: Union[tuple[int, int], None] + :param size: The width and height of the overlaid image, or None to use the overlaid's images existing width and height, or (-1, -1) to use the dimensions of the background image, defaults to None + :type size: Union[tuple[int, int], None] + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use the bottom-left point of the background image by default + location = (0, 0) + + if size is None: + # No dimensions provided -- use size of overlay image by default + size = image.size + elif size == (-1, -1): + # Use remaining width/height of background image + size = (self.size[0] - location[0], self.size[1] - location[1]) + elif size[0] == -1: + # Use remaining width of background image + provided height + size = (self.size[0] - location[0], size[1]) + elif size[1] == -1: + # Use remaining height of background image + provided width + size = (size[1], self.size[1] - location[1]) + + self.xa_elem.lockFocus() + bounds = AppKit.NSMakeRect(location[0], location[1], size[0], size[1]) + image.xa_elem.drawInRect_(bounds) + self.xa_elem.unlockFocus() + self.modified = True + return self.xa_elem
+ +
[docs] def overlay_text(self, text: str, location: Union[tuple[int, int], None] = None, font_size: float = 12, font_color: Union[XAColor, None] = None) -> 'XAImage': + """Overlays text of the specified size and color at the provided location within the image. + + :param text: The text to overlay onto the image + :type text: str + :param location: The bottom-left point of the start of the text, or None to use (5, 5), defaults to None + :type location: Union[tuple[int, int], None] + :param font_size: The font size, in pixels, of the text, defaults to 12 + :type font_size: float + :param font_color: The color of the text, or None to use black, defaults to None + :type font_color: XAColor + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use (5, 5) by default + location = (5, 5) + + if font_color is None: + # No color provided -- use black by default + font_color = XAColor.black() + + font = AppKit.NSFont.userFontOfSize_(font_size) + textRect = Quartz.CGRectMake(location[0], 0, self.size[0] - location[0], location[1]) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: font_color.xa_elem + } + + self.xa_elem.lockFocus() + AppKit.NSString.alloc().initWithString_(text).drawInRect_withAttributes_(textRect, attributes) + self.xa_elem.unlockFocus() + self.modified = True + return self
+ +
[docs] def extract_text(self) -> list[str]: + """Extracts and returns all visible text in the image. + + :return: The array of extracted text strings + :rtype: list[str] + + :Example: + + >>> import PyXA + >>> test = PyXA.XAImage("/Users/ExampleUser/Downloads/Example.jpg") + >>> print(test.extract_text()) + ["HERE'S TO THE", 'CRAZY ONES', 'the MISFITS the REBELS', 'THE TROUBLEMAKERS', ...] + + .. versionadded:: 0.1.0 + """ + # Prepare CGImage + ci_image = Quartz.CIImage.imageWithData_(self.data) + context = Quartz.CIContext.alloc().initWithOptions_(None) + img = context.createCGImage_fromRect_(ci_image, ci_image.extent()) + + # Handle request completion + extracted_strings = [] + def recognize_text_handler(request, error): + observations = request.results() + for observation in observations: + recognized_strings = observation.topCandidates_(1)[0].string() + extracted_strings.append(recognized_strings) + + # Perform request and return extracted text + request = Vision.VNRecognizeTextRequest.alloc().initWithCompletionHandler_(recognize_text_handler) + request_handler = Vision.VNImageRequestHandler.alloc().initWithCGImage_options_(img, None) + request_handler.performRequests_error_([request], None) + return extracted_strings
+ +
[docs] def show_in_preview(self): + """Opens the image in preview. + + .. versionadded:: 0.0.8 + """ + if not self.modified and self.file is not None and isinstance(self.file, XAPath): + AppKit.NSWorkspace.sharedWorkspace().openFile_withApplication_(self.file.path, "Preview") + else: + tmp_file = tempfile.NamedTemporaryFile() + with open(tmp_file.name, 'wb') as f: + f.write(self.xa_elem.TIFFRepresentation()) + + img_url = XAPath(tmp_file.name).xa_elem + preview_url = XAPath("/System/Applications/Preview.app/").xa_elem + AppKit.NSWorkspace.sharedWorkspace().openURLs_withApplicationAtURL_configuration_completionHandler_([img_url], preview_url, None, None) + time.sleep(1)
+ +
[docs] def save(self, file_path: Union[XAPath, str, None] = None): + """Saves the image to a file on the disk. Saves to the original file (if there was one) by default. + + :param file_path: The path at which to save the image file. Any existing file at that location will be overwritten, defaults to None + :type file_path: Union[XAPath, str, None] + + .. versionadded:: 0.1.0 + """ + if file_path is None and self.file is not None: + file_path = self.file.path + elif isinstance(file_path, XAPath): + file_path = file_path.path + fm = AppKit.NSFileManager.defaultManager() + fm.createFileAtPath_contents_attributes_(file_path, self.xa_elem.TIFFRepresentation(), None)
+ +
[docs] def get_clipboard_representation(self) -> AppKit.NSImage: + """Gets a clipboard-codable representation of the iimage. + + When the clipboard content is set to an image, the image itself, including any modifications, is added to the clipboard. Pasting will then insert the image into the active document. + + :return: The raw NSImage object for this XAIMage + :rtype: AppKit.NSImage + + .. versionadded:: 0.1.0 + """ + return self.xa_elem
+ + + + +
[docs]class XASoundList(XAList, XAClipboardCodable): + """A wrapper around lists of sounds that employs fast enumeration techniques. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XASound, filter) + +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSSound, AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of each sound in the list. + + When the clipboard content is set to a list of sounds, each sound's raw sound data, its associated file URL, and its file path string are added to the clipboard. + + :return: The clipboard-codable form of the sound + :rtype: Any + + .. versionadded:: 0.1.0 + """ + return [self.xa_elem, self.file.xa_elem, self.file.xa_elem.path()]
+ +
[docs]class XASound(XAObject, XAClipboardCodable): + """A class for playing and interacting with audio files and data. + + .. versionadded:: 0.0.1 + """ + def __init__(self, sound_reference: Union[str, XAURL, XAPath]): + self.file = None + + if isinstance(sound_reference, str): + # Sound reference is a string + if "://" in sound_reference: + # Sound reference is a URL + self.file = XAURL(sound_reference) + sound_reference = self.file.xa_elem + if "/" in sound_reference: + # Sound reference is a file path + self.file = XAPath(sound_reference) + sound_reference = self.file.xa_elem + else: + # Sound reference is the name of a default sound + self.file = XAPath("/System/Library/Sounds/" + sound_reference + ".aiff") + sound_reference = self.file.xa_elem + elif isinstance(sound_reference, dict): + # Sound reference comes from XAList + sound_reference = sound_reference["element"] + # Sound reference is a string + if isinstance(sound_reference, str): + if "://" in sound_reference: + # Sound reference is a URL + self.file = XAURL(sound_reference) + sound_reference = self.file.xa_elem + if "/" in sound_reference: + # Sound reference is a file path + self.file = XAPath(sound_reference) + sound_reference = self.file.xa_elem + else: + # Sound reference is the name of a default sound + self.file = XAPath("/System/Library/Sounds/" + sound_reference + ".aiff") + sound_reference = XAPath(self.file) + elif isinstance(sound_reference, XAPath): + # Sound reference is an XAPath object + self.file = sound_reference + sound_reference = sound_reference.xa_elem + elif isinstance(sound_reference, XAURL): + # Sound reference is an XAURL object + self.file = sound_reference + sound_reference = sound_reference + elif isinstance(sound_reference, XASound): + self.file = sound_reference.file + sound_reference = sound_reference.xa_elem + + if isinstance(sound_reference, AVFoundation.AVAudioFile): + # Sound reference is an NSSound object + self.xa_elem = sound_reference + + self.duration: float #: The duration of the sound in seconds + + self.__audio_file = AVFoundation.AVAudioFile.alloc().initForReading_error_(self.file.xa_elem if self.file is not None else None, None)[0] + + self.__audio_engine = AVFoundation.AVAudioEngine.alloc().init() + self.__player_node = AVFoundation.AVAudioPlayerNode.alloc().init() + self.__audio_engine.attachNode_(self.__player_node) + + self.__audio_engine.connect_to_format_(self.__player_node, self.__audio_engine.mainMixerNode(), self.__audio_file.processingFormat()) + + self.__player_node.stop() + self.__audio_engine.stop() + + self.xa_elem = self.__audio_file + + @property + def num_sample_frames(self) -> int: + """The number of sample frames in the audio file. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.length() + + @property + def sample_rate(self) -> float: + """The sample rate for the sound format, in hertz. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.processingFormat().sampleRate() + + @property + def duration(self) -> float: + """The duration of the sound in seconds. + + .. versionadded:: 0.1.0 + """ + return self.num_sample_frames / self.sample_rate + +
[docs] def open(*sound_references: Union[str, XAPath, list[Union[str, XAPath]]]) -> Union['XASound', XASoundList]: + """Initializes one or more sounds from files. + + :param sound_references: The sound(s) to open + :type sound_references: Union[str, XAPath, list[Union[str, XAPath]]] + :return: The newly created sound object, or a list of sound objects + :rtype: Union[XASound, XASoundList] + + .. versionadded:: 0.1.0 + """ + if len(sound_references) == 1: + sound_references = sound_references[0] + + if isinstance(sound_references, list) or isinstance(sound_references, tuple): + return XASoundList({"element": sound_references}) + else: + return XASound(sound_references)
+ +
[docs] def beep(): + """Plays the system Beep sound. + + .. versionadded:: 0.1.0 + """ + AppleScript(""" + beep + delay 0.5 + """).run()
+ +
[docs] def play(self) -> Self: + """Plays the sound from the beginning. + + Audio playback runs in a separate thread. For the sound the play properly, you must keep the main thread alive over the duration of the desired playback. + + :return: A reference to this sound object. + :rtype: Self + + :Example: + + >>> import PyXA + >>> import time + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.play() + >>> time.sleep(glass_sound.duration) + + .. seealso:: :func:`pause`, :func:`stop` + + .. versionadded:: 0.0.1 + """ + def play_sound(self): + self.__player_node.scheduleFile_atTime_completionHandler_(self.xa_elem, None, None) + self.__audio_engine.startAndReturnError_(None) + self.__player_node.play() + while self.__player_node.isPlaying(): + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.1)) + + self._spawn_thread(play_sound, [self]) + return self
+ +
[docs] def pause(self) -> Self: + """Pauses the sound. + + :return: A reference to this sound object. + :rtype: Self + + :Example: + + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.pause() + + .. seealso:: :func:`resume`, :func:`stop` + + .. versionadded:: 0.0.1 + """ + self.__player_node.pause() + return self
+ +
[docs] def resume(self) -> Self: + """Plays the sound starting from the time it was last paused at. + + Audio playback runs in a separate thread. For the sound the play properly, you must keep the main thread alive over the duration of the desired playback. + + :return: A reference to this sound object. + :rtype: Self + + :Example: + + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.resume() + + .. seealso:: :func:`pause`, :func:`play` + + .. versionadded:: 0.0.1 + """ + def play_sound(self): + self.__player_node.scheduleFile_atTime_completionHandler_(self.xa_elem, None, None) + self.__audio_engine.startAndReturnError_(None) + self.__player_node.play() + while self.__player_node.isPlaying(): + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.1)) + + self._spawn_thread(play_sound, [self]) + return self
+ +
[docs] def stop(self) -> 'XASound': + """Stops playback of the sound and rewinds it to the beginning. + + :return: A reference to this sound object. + :rtype: XASound + + :Example: + + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.stop() + + .. seealso:: :func:`pause`, :func:`play` + + .. versionadded:: 0.0.1 + """ + self.__audio_engine.stop() + return self
+ +
[docs] def set_volume(self, volume: float) -> Self: + """Sets the volume of the sound. + + :param volume: The desired volume of the sound in the range [0.0, 1.0]. + :type volume: int + :return: A reference to this sound object. + :rtype: Self + + :Example: + + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.set_volume(1.0) + + .. seealso:: :func:`volume` + + .. versionadded:: 0.0.1 + """ + self.__audio_engine.mainMixerNode().setOutputVolume_(volume) + return self
+ +
[docs] def volume(self) -> float: + """Returns the current volume of the sound. + + :return: The volume level of the sound. + :rtype: int + + :Example: + + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> print(glass_sound.volume()) + 1.0 + + .. seealso:: :func:`set_volume` + + .. versionadded:: 0.0.1 + """ + return self.__audio_engine.mainMixerNode().volume()
+ +
[docs] def loop(self, times: int) -> Self: + """Plays the sound the specified number of times. + + Audio playback runs in a separate thread. For the sound the play properly, you must keep the main thread alive over the duration of the desired playback. + + :param times: The number of times to loop the sound. + :type times: int + :return: A reference to this sound object. + :rtype: Self + + :Example: + + >>> import PyXA + >>> import time + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.loop(10) + >>> time.sleep(glass_sound.duration * 10) + + .. versionadded:: 0.0.1 + """ + def play_sound(): + num_plays = 0 + while num_plays < times: + sound = XASound(self.file) + sound.play() + num_plays += 1 + time.sleep(self.duration) + + self._spawn_thread(play_sound) + return self
+ +
[docs] def trim(self, start_time: float, end_time: float) -> Self: + """Trims the sound to the specified start and end time, in seconds. + + This will create a momentary sound data file in the current working directory for storing the intermediary trimmed sound data. + + :param start_time: The start time in seconds + :type start_time: float + :param end_time: The end time in seconds + :type end_time: float + :return: The updated sound object + :rtype: Self + + .. versionadded:: 0.1.0 + """ + # Clear the temp data path + file_path = "sound_data_tmp.m4a" + if os.path.exists(file_path): + AppKit.NSFileManager.defaultManager().removeItemAtPath_error_(file_path, None) + + # Configure the export session + asset = AVFoundation.AVAsset.assetWithURL_(self.file.xa_elem) + export_session = AVFoundation.AVAssetExportSession.exportSessionWithAsset_presetName_(asset, AVFoundation.AVAssetExportPresetAppleM4A) + + start_time = CoreMedia.CMTimeMake(start_time * 100, 100) + end_time = CoreMedia.CMTimeMake(end_time * 100, 100) + time_range = CoreMedia.CMTimeRangeFromTimeToTime(start_time, end_time); + + export_session.setTimeRange_(time_range) + export_session.setOutputURL_(XAPath(file_path).xa_elem) + export_session.setOutputFileType_(AVFoundation.AVFileTypeAppleM4A) + + # Export to file path + waiting = False + def handler(): + nonlocal waiting + waiting = True + + export_session.exportAsynchronouslyWithCompletionHandler_(handler) + + while not waiting: + time.sleep(0.01) + + # Load the sound file back into active memory + self.__audio_file = AVFoundation.AVAudioFile.alloc().initForReading_error_(XAPath(file_path).xa_elem, None)[0] + self.xa_elem = self.__audio_file + AppKit.NSFileManager.defaultManager().removeItemAtPath_error_(file_path, None) + return self
+ +
[docs] def save(self, file_path: Union[XAPath, str]): + """Saves the sound to the specified file path. + + :param file_path: The path to save the sound to + :type file_path: Union[XAPath, str] + + .. versionadded:: 0.1.0 + """ + if isinstance(file_path, str): + file_path = XAPath(file_path) + + # Configure the export session + asset = AVFoundation.AVAsset.assetWithURL_(self.file.xa_elem) + export_session = AVFoundation.AVAssetExportSession.exportSessionWithAsset_presetName_(asset, AVFoundation.AVAssetExportPresetAppleM4A) + + start_time = CoreMedia.CMTimeMake(0, 100) + end_time = CoreMedia.CMTimeMake(self.duration * 100, 100) + time_range = CoreMedia.CMTimeRangeFromTimeToTime(start_time, end_time); + + export_session.setTimeRange_(time_range) + export_session.setOutputURL_(file_path.xa_elem) + # export_session.setOutputFileType_(AVFoundation.AVFileTypeAppleM4A) + + # Export to file path + waiting = False + def handler(): + nonlocal waiting + waiting = True + + export_session.exportAsynchronouslyWithCompletionHandler_(handler) + + while not waiting: + time.sleep(0.01)
+ +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSSound, AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of the sound. + + When the clipboard content is set to a sound, the raw sound data, the associated file URL, and the path string of the file are added to the clipboard. + + :return: The clipboard-codable form of the sound + :rtype: Any + + .. versionadded:: 0.0.8 + """ + return [self.xa_elem, self.file.xa_elem, self.file.xa_elem.path()]
+ + + +
[docs]class XAVideo(XAObject): + """A class for interacting with video files and data. + + .. versionadded:: 0.1.0 + """ + def __init__(self, video_reference: Union[str, XAURL, XAPath]): + if isinstance(video_reference, str): + # References is to some kind of path or URL + if "://" in video_reference: + video_reference = XAURL(video_reference) + else: + video_reference = XAPath(video_reference) + + self.xa_elem = AVFoundation.AVURLAsset.alloc().initWithURL_options_(video_reference.xa_elem, { AVFoundation.AVURLAssetPreferPreciseDurationAndTimingKey: objc.YES }) + +
[docs] def reverse(self, output_file: Union[XAPath, str]): + """Reverses the video and exports the result to the specified output file path. + + :param output_file: The file to export the reversed video to + :type output_file: Union[XAPath, str] + + .. versionadded:: 0.1.0 + """ + if isinstance(output_file, str): + output_file = XAPath(output_file) + output_url = output_file.xa_elem + + reader = AVFoundation.AVAssetReader.alloc().initWithAsset_error_(self.xa_elem, None)[0] + + video_track = self.xa_elem.tracksWithMediaType_(AVFoundation.AVMediaTypeVideo)[-1] + + reader_output = AVFoundation.AVAssetReaderTrackOutput.alloc().initWithTrack_outputSettings_(video_track, { Quartz.CoreVideo.kCVPixelBufferPixelFormatTypeKey: Quartz.CoreVideo.kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange }) + + reader.addOutput_(reader_output) + reader.startReading() + + samples = [] + while sample := reader_output.copyNextSampleBuffer(): + samples.append(sample) + + writer = AVFoundation.AVAssetWriter.alloc().initWithURL_fileType_error_(output_url, AVFoundation.AVFileTypeMPEG4, None)[0] + + writer_settings = { + AVFoundation.AVVideoCodecKey: AVFoundation.AVVideoCodecTypeH264, + AVFoundation.AVVideoWidthKey: video_track.naturalSize().width, + AVFoundation.AVVideoHeightKey: video_track.naturalSize().height, + AVFoundation.AVVideoCompressionPropertiesKey: { AVFoundation.AVVideoAverageBitRateKey: video_track.estimatedDataRate() } + } + + format_hint = video_track.formatDescriptions()[-1] + writer_input = AVFoundation.AVAssetWriterInput.alloc().initWithMediaType_outputSettings_sourceFormatHint_(AVFoundation.AVMediaTypeVideo, writer_settings, format_hint) + + writer_input.setExpectsMediaDataInRealTime_(False) + + pixel_buffer_adaptor = AVFoundation.AVAssetWriterInputPixelBufferAdaptor.alloc().initWithAssetWriterInput_sourcePixelBufferAttributes_(writer_input, None) + writer.addInput_(writer_input) + writer.startWriting() + writer.startSessionAtSourceTime_(CoreMedia.CMSampleBufferGetPresentationTimeStamp(samples[0])) + + for index, sample in enumerate(samples): + presentation_time = CoreMedia.CMSampleBufferGetPresentationTimeStamp(sample) + + image_buffer_ref = CoreMedia.CMSampleBufferGetImageBuffer(samples[len(samples) - index - 1]) + if image_buffer_ref is not None: + pixel_buffer_adaptor.appendPixelBuffer_withPresentationTime_(image_buffer_ref, presentation_time) + + while not writer_input.isReadyForMoreMediaData(): + time.sleep(0.1) + + self._spawn_thread(writer.finishWriting) + return AVFoundation.AVAsset.assetWithURL_(output_url)
+ +
[docs] def show_in_quicktime(self): + """Shows the video in QuickTime Player. + + This will create a momentary video data file in the current working directory to store intermediary video data. + + .. versionadded:: 0.1.0 + """ + self.save("video-data-tmp.mp4") + + video_url = XAPath(os.getcwd() + "/video-data-tmp.mp4").xa_elem + quicktime_url = XAPath("/System/Applications/QuickTime Player.app").xa_elem + AppKit.NSWorkspace.sharedWorkspace().openURLs_withApplicationAtURL_configuration_completionHandler_([video_url], quicktime_url, None, None) + time.sleep(1) + + AppKit.NSFileManager.defaultManager().removeItemAtPath_error_(video_url.path(), None)
+ +
[docs] def save(self, file_path: Union[XAPath, str]): + """Saves the video at the specified file path. + + :param file_path: The path to save the video at + :type file_path: Union[XAPath, str] + + .. versionadded:: 0.1.0 + """ + if isinstance(file_path, str): + file_path = XAPath(file_path) + + # Configure the export session + export_session = AVFoundation.AVAssetExportSession.exportSessionWithAsset_presetName_(self.xa_elem, AVFoundation.AVAssetExportPresetHighestQuality) + + start_time = CoreMedia.CMTimeMake(0, 100) + end_time = CoreMedia.CMTimeMake(self.xa_elem.duration().value * self.xa_elem.duration().timescale, 100) + time_range = CoreMedia.CMTimeRangeFromTimeToTime(start_time, end_time); + + export_session.setTimeRange_(time_range) + export_session.setOutputURL_(file_path.xa_elem) + + # Export to file path + waiting = False + def handler(): + nonlocal waiting + waiting = True + + export_session.exportAsynchronouslyWithCompletionHandler_(handler) + + while not waiting: + time.sleep(0.01)
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/XABase.html b/docs/_modules/PyXA/XABase.html index 7616a55..9fb5edc 100644 --- a/docs/_modules/PyXA/XABase.html +++ b/docs/_modules/PyXA/XABase.html @@ -3,7 +3,7 @@ - PyXA.XABase — PyXA 0.0.9 documentation + PyXA.XABase — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -73,36 +75,47 @@

Source code for PyXA.XABase

 General classes and methods applicable to any PyXA object.
 """
 
-from datetime import datetime, timedelta
-from enum import Enum
 import importlib
-from pprint import pprint
+import logging
+import math
+import os
+import random
+import subprocess
+import sys
 import tempfile
-import time, os, sys
-from typing import Any, Callable, Literal, Tuple, Union, List, Dict
 import threading
-from bs4 import BeautifulSoup, element
-import requests
-import subprocess
-import objc
-
-from PyXA.apps import application_classes
-
-from PyObjCTools import AppHelper
+import time
+import xml.etree.ElementTree as ET
+from datetime import datetime, timedelta
+from enum import Enum
+from pprint import pprint
+from typing import Any, Callable, Iterator, Literal, Self, Union
 
 import AppKit
-from Quartz import CGImageSourceRef, CGImageSourceCreateWithData, CFDataRef, CGRectMake
-from CoreLocation import CLLocation
-from ScriptingBridge import SBApplication, SBElementArray
-import ScriptingBridge
-import Speech
+import appscript
 import AVFoundation
 import CoreLocation
+import CoreMedia
+import Foundation
+import LatentSemanticMapping
+import NaturalLanguage
+import objc
+import Quartz
+import requests
+import ScriptingBridge
+import Speech
+import Vision
+from bs4 import BeautifulSoup, element
+from CoreLocation import CLLocation
+from PyObjCTools import AppHelper
+from Quartz import (CFDataRef, CGImageSourceCreateWithData, CGImageSourceRef, CGRectMake)
+from ScriptingBridge import SBApplication, SBElementArray
 
-import threading
-
+from PyXA.apps import application_classes
 from PyXA.XAErrors import InvalidPredicateError
-from .XAProtocols import XAClipboardCodable
+
+from .XAProtocols import XACanOpenPath, XAClipboardCodable, XAPathLike
+
 
 
[docs]def OSType(s: str): return int.from_bytes(s.encode("UTF-8"), "big")
@@ -111,8 +124,22 @@

Source code for PyXA.XABase

     return i.to_bytes((i.bit_length() + 7) // 8, 'big').decode()
+PYXA_VERSION = "0.1.0" - +# Set up logging +if not os.path.exists("logs"): + os.makedirs("logs") + +logging.basicConfig( + filename="logs/pyxa_debug.log", + level=logging.DEBUG, + format="%(asctime)s:%(levelname)s:%(name)s:%(message)s" +) + +############### +### General ### +############### +# XAObject, XAApplication
[docs]class XAObject(): """A general class for PyXA scripting objects. @@ -120,6 +147,7 @@

Source code for PyXA.XABase

 
     .. versionadded:: 0.0.1
     """
+    _xa_sevt = None
     def __init__(self, properties: dict = None):
         """Instantiates a PyXA scripting object.
 
@@ -133,15 +161,26 @@ 

Source code for PyXA.XABase

         """
         if properties is not None:
             self.xa_prnt = properties.get("parent", None)
-            self.xa_apsp = properties.get("appspace", None)
+            self._xa_apsp = properties.get("appspace", None)
             self.xa_wksp = properties.get("workspace", None)
             self.xa_elem = properties.get("element", None)
             self.xa_scel = properties.get("scriptable_element", None)
             self.xa_aref = properties.get("appref", None)
-            self.xa_sevt = properties.get("system_events", SBApplication.alloc().initWithBundleIdentifier_("com.apple.systemevents"))
 
         self.properties: dict #: The scriptable properties dictionary for the object
 
+    @property
+    def xa_apsp(self):
+        if not isinstance(self.__xa_apsp, AppKit.NSApplication):
+            self.__xa_apsp = list(self.__xa_apsp)[0]
+        return self.__xa_apsp
+
+    @property
+    def xa_sevt(self):
+        if XAObject._xa_sevt is None:
+            XAObject._xa_sevt = SBApplication.alloc().initWithBundleIdentifier_("com.apple.systemevents")
+        return XAObject._xa_sevt
+
     def _exec_suppresed(self, f: Callable[..., Any], *args: Any) -> Any:
         """Silences unwanted and otherwise unavoidable warning messages.
 
@@ -173,7 +212,7 @@ 

Source code for PyXA.XABase

             raise error
         return value
 
-    def _new_element(self, obj: AppKit.NSObject, obj_class: type = 'XAObject', *args: List[Any]) -> 'XAObject':
+    def _new_element(self, obj: AppKit.NSObject, obj_class: type = 'XAObject', *args: list[Any]) -> 'XAObject':
         """Wrapper for creating a new PyXA object.
 
         :param folder_obj: The Objective-C representation of an object.
@@ -185,23 +224,22 @@ 

Source code for PyXA.XABase

         """
         properties = {
             "parent": self,
-            "appspace": self.xa_apsp,
-            "workspace": self.xa_wksp,
+            "appspace": self._xa_apsp,
+            "workspace": getattr(self, "xa_wksp", None),
             "element": obj,
-            "appref": self.xa_aref,
-            "system_events": self.xa_sevt,
+            "appref": getattr(self, "xa_aref", None),
         }
         return obj_class(properties, *args)
 
-    def _spawn_thread(self, function: Callable[..., Any], args: Union[List[Any], None] = None, kwargs: Union[List[Any], None] = None, daemon: bool = True) -> threading.Thread:
+    def _spawn_thread(self, function: Callable[..., Any], args: Union[list[Any], None] = None, kwargs: Union[list[Any], None] = None, daemon: bool = True) -> threading.Thread:
         """Spawns a new thread running the specified function.
 
         :param function: The function to run in the new thread
         :type function: Callable[..., Any]
         :param args: Arguments to pass to the function
-        :type args: List[Any]
+        :type args: list[Any]
         :param kwargs: Keyword arguments to pass to the function
-        :type kwargs: List[Any]
+        :type kwargs: list[Any]
         :param daemon: Whether the thread should be a daemon thread, defaults to True
         :type daemon: bool, optional
         :return: The thread object
@@ -292,2447 +330,7218 @@ 

Source code for PyXA.XABase

         titled_parts = [part.title() for part in parts[1:]]
         property_name = parts[0] + "".join(titled_parts)
         self.xa_elem.setValue_forKey_(value, property_name)
-        return self
- + return self
+
[docs] def set_scriptable_property(self, property_name: str, value: Any) -> 'XAObject': + """Updates the value of a single scriptable element property of the scripting element associated with this object. + :param property: The name of the property to assign a new value to. + :type property: str + :param value: The value to assign to the specified property. + :type value: Any + :return: A reference to this PyXA object. + :rtype: XAObject -
[docs]class AppleScript(XAObject): - """A class for constructing and executing AppleScript scripts. + .. versionadded:: 0.1.0 + """ + parts = property_name.split("_") + titled_parts = [part.title() for part in parts[1:]] + property_name = parts[0] + "".join(titled_parts) + self.xa_scel.setValue_forKey_(value, property_name) + return self
- .. versionadded:: 0.0.5 + def __eq__(self, other: 'XAObject'): + if other is None: + return False + + if self.xa_elem == other.xa_elem: + return True + return self.xa_elem.get() == other.xa_elem.get()
+ + + +
[docs]class XAApplication(XAObject, XAClipboardCodable): + """A general application class for both officially scriptable and non-scriptable applications. + + .. seealso:: :class:`XASBApplication`, :class:`XAWindow` + + .. versionadded:: 0.0.1 """ - def __init__(self, script: Union[str, List[str], None] = None): - """Creates a new AppleScript object. + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XAWindow + self.__xa_prcs = None - :param script: A string or list of strings representing lines of AppleScript code, or the path to a script plaintext file, defaults to None - :type script: Union[str, List[str], None], optional + self.xa_apsc: appscript.GenericApp - .. versionadded:: 0.0.5 - """ - self.script: List[str] #: The lines of AppleScript code contained in the script - self.last_result: Any #: The return value of the last execution of the script - self.file_path: XAPath #: The file path of this script, if one exists + @property + def xa_prcs(self): + if self.__xa_prcs == None: + predicate = AppKit.NSPredicate.predicateWithFormat_("displayedName == %@", self.xa_elem.localizedName()) + process = self.xa_sevt.processes().filteredArrayUsingPredicate_(predicate)[0] + + properties = { + "parent": self, + "appspace": self._xa_apsp, + "workspace": self.xa_wksp, + "element": process, + "appref": self.xa_aref, + "window_class": self.xa_wcls + } + self.__xa_prcs = XAProcess(properties) + return self.__xa_prcs - if isinstance(script, str): - if script.startswith("/"): - with open(script, 'r') as f: - script = f.readlines() - else: - self.script = [script] - elif isinstance(script, list): - self.script = script - elif script == None: - self.script = [] + def __getattr__(self, attr): + if attr in self.__dict__: + # If possible, use PyXA attribute + return super().__getattribute__(attr) + else: + # Otherwise, fall back to appscript + return getattr(self.xa_apsc, attr) @property - def last_result(self) -> Any: - return self.__last_result + def xa_apsc(self) -> appscript.GenericApp: + return appscript.app(self.bundle_url.path()) @property - def file_path(self) -> 'XAPath': - return self.__file_path + def bundle_identifier(self) -> str: + """The bundle identifier for the application. -
[docs] def add(self, script: Union[str, List[str], 'AppleScript']): - """Adds the supplied string, list of strings, or script as a new line entry in the script. + .. versionadded:: 0.0.1 + """ + return self.xa_elem.bundleIdentifier() - :param script: The script to append to the current script string. - :type script: Union[str, List[str], AppleScript] + @property + def bundle_url(self) -> str: + """The file URL of the application bundle. - :Example: + .. versionadded:: 0.0.1 + """ + return self.xa_elem.bundleURL() - >>> import PyXA - >>> script = PyXA.AppleScript("tell application \"Safari\"") - >>> script.add("print the document of window 1") - >>> script.add("end tell") - >>> script.run() + @property + def executable_url(self) -> str: + """The file URL of the application's executable. - .. versionadded:: 0.0.5 + .. versionadded:: 0.0.1 """ - if isinstance(script, str): - self.script.append(script) - elif isinstance(script, list): - self.script.extend(script) - elif isinstance(script, AppleScript): - self.script.extend(script.script)
+ return self.xa_elem.executableURL() -
[docs] def insert(self, index: int, script: Union[str, List[str], 'AppleScript']): - """Inserts the supplied string, list of strings, or script as a line entry in the script starting at the given line index. + @property + def frontmost(self) -> bool: + """Whether the application is the active application. - :param index: The line index to begin insertion at - :type index: int - :param script: The script to insert into the current script - :type script: Union[str, List[str], AppleScript] + .. versionadded:: 0.0.1 + """ + return self.xa_elem.isActive() - :Example: + @frontmost.setter + def frontmost(self, frontmost: bool): + if frontmost is True: + self.xa_elem.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps) - >>> import PyXA - >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") - >>> script.insert(1, "activate") - >>> script.run() + @property + def launch_date(self) -> datetime: + """The date and time that the application was launched. - .. versionadded:: 0.0.9 + .. versionadded:: 0.0.1 """ - if isinstance(script, str): - self.script.insert(index, script) - elif isinstance(script, list): - for line in script: - self.script.insert(index, line) - index += 1 - elif isinstance(script, AppleScript): - for line in script.script: - self.script.insert(index, line) - index += 1
+ return self.xa_elem.launchDate() -
[docs] def pop(self, index: int = -1) -> str: - """Removes the line at the given index from the script. + @property + def localized_name(self) -> str: + """The application's name. - :param index: The index of the line to remove - :type index: int - :return: The text of the removed line - :rtype: str + .. versionadded:: 0.0.1 + """ + return self.xa_elem.localizedName() - :Example: + @property + def owns_menu_bar(self) -> bool: + """Whether the application owns the top menu bar. - >>> import PyXA - >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") - >>> print(script.pop(1)) - get chats + .. versionadded:: 0.0.1 + """ + return self.xa_elem.ownsMenuBar() - .. versionadded:: 0.0.9 + @property + def process_identifier(self) -> str: + """The process identifier for the application instance. + + .. versionadded:: 0.0.1 """ - return self.script.pop(index)
+ return self.xa_elem.processIdentifier() -
[docs] def load(path: Union['XAPath', str]) -> 'AppleScript': - """Loads an AppleScript (.scpt) file as a runnable AppleScript object. +
[docs] def activate(self) -> 'XAApplication': + """Activates the application, bringing its window(s) to the front and launching the application beforehand if necessary. - :param path: The path of the .scpt file to load - :type path: Union[XAPath, str] - :return: The newly loaded AppleScript object - :rtype: AppleScript + :return: A reference to the PyXA application object. + :rtype: XAApplication - :Example 1: Load and run a script + .. seealso:: :func:`terminate`, :func:`unhide`, :func:`focus` - >>> import PyXA - >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") - >>> print(script.run()) - { - 'string': None, - 'int': 0, - 'bool': False, - 'float': 0.0, - 'date': None, - 'file_url': None, - 'type_code': 845507684, - 'data': {length = 8962, bytes = 0x646c6532 00000000 6c697374 000022f2 ... 6e756c6c 00000000 }, - 'event': <NSAppleEventDescriptor: [ 'obj '{ ... } ]> - } + .. versionadded:: 0.0.1 + """ + self.xa_elem.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps) + return self
- :Example 2: Load, modify, and run a script +
[docs] def terminate(self) -> 'XAApplication': + """Quits the application. Synonymous with quit(). - >>> import PyXA - >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") - >>> script.pop(1) - >>> script.insert(1, "activate") - >>> script.run() + :return: A reference to the PyXA application object. + :rtype: XAApplication - .. versionadded:: 0.0.8 - """ - if isinstance(path, str): - path = XAPath(path) - script = AppKit.NSAppleScript.alloc().initWithContentsOfURL_error_(path.xa_elem, None)[0] + :Example: - attributed_string = script.richTextSource() - attributed_string = str(attributed_string).split("}") - parts = [] - for x in attributed_string: - parts.extend(x.split("{")) + >>> import PyXA + >>> safari = PyXA.Application("Safari") + >>> safari.terminate() - for x in parts: - if "=" in x: - parts.remove(x) + .. seealso:: :func:`quit`, :func:`activate` - script = AppleScript("".join(parts).split("\n")) - script.__file_path = path - return script
+ .. versionadded:: 0.0.1 + """ + self.xa_elem.terminate() + return self
-
[docs] def save(self, path: Union['XAPath', str, None] = None): - """Saves the script to the specified file path, or to the path from which the script was loaded. +
[docs] def quit(self) -> 'XAApplication': + """Quits the application. Synonymous with terminate(). - :param path: The path to save the script at, defaults to None - :type path: Union[XAPath, str, None], optional + :return: A reference to the PyXA application object. + :rtype: XAApplication - :Example 1: Save the script to a specified path + :Example: >>> import PyXA - >>> script = PyXA.AppleScript(f\"\"\" - >>> tell application "Safari" - >>> activate - >>> end tell - >>> \"\"\") - >>> script.save("/Users/exampleUser/Downloads/Example.scpt") - - :Example 2: Load a script, modify it, then save it + >>> safari = PyXA.Application("Safari") + >>> safari.quit() - >>> import PyXA - >>> script = PyXA.AppleScript.load("/Users/steven/Downloads/Example.scpt") - >>> script.insert(2, "delay 2") - >>> script.insert(3, "set the miniaturized of window 1 to true") - >>> script.save() + .. seealso:: :func:`terminate`, :func:`activate` - .. versionadded:: 0.0.9 + .. versionadded:: 0.0.1 """ - if path is None and self.file_path is None: - print("No path to save script to!") - return - - if isinstance(path, str): - path = XAPath(path) + self.xa_elem.terminate() + return self
- script = "" - for line in self.script: - script += line + "\n" - script = AppKit.NSAppleScript.alloc().initWithSource_(script) - script.compileAndReturnError_(None) - source = (script.richTextSource().string()) +
[docs] def hide(self) -> 'XAApplication': + """Hides all windows of the application. - if path is not None: - self.__file_path = path + :return: A reference to the PyXA application object. + :rtype: XAApplication - with open(self.file_path.xa_elem.path(), "w") as f: - f.write(source)
+ :Example: -
[docs] def parse_result_data(result: dict) -> List[Tuple[str, str]]: - """Extracts string data from an AppleScript execution result dictionary. + >>> import PyXA + >>> safari = PyXA.Application("Safari") + >>> safari.hide() - :param result: The execution result dictionary to extract data from - :type result: dict - :return: A list of responses contained in the result structured as tuples - :rtype: List[Tuple[str, str]] + .. seealso:: :func:`unhide` + + .. versionadded:: 0.0.1 + """ + self.xa_elem.hide() + return self
+ +
[docs] def unhide(self) -> 'XAApplication': + """Unhides (reveals) all windows of the application, but does not does not activate them. + + :return: A reference to the PyXA application object. + :rtype: XAApplication :Example: >>> import PyXA - >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") - >>> print(script.script) - >>> result = script.run() - >>> print(PyXA.AppleScript.parse_result_data(result)) - ['tell application "Messages"', '\\tget chats', 'end tell'] - [('ID', 'iMessage;-;+12345678910'), ('ID', 'iMessage;-;+12345678911'), ('ID', 'iMessage;-;example@icloud.com'), ...] + >>> safari = PyXA.Application("Safari") + >>> safari.unhide() - .. versionadded:: 0.0.9 - """ - result = result["event"] - response_objects = [] - num_responses = result.numberOfItems() - for response_index in range(1, num_responses + 1): - response = result.descriptorAtIndex_(response_index) + .. seealso:: :func:`hide` - data = () - num_params = response.numberOfItems() - if num_params == 0: - data = response.stringValue().strip() + .. versionadded:: 0.0.1 + """ + self.xa_elem.unhide() + return self
- else: - for param_index in range(1, num_params + 1): - param = response.descriptorAtIndex_(param_index).stringValue() - if param is not None: - data += (param.strip(), ) - response_objects.append(data) +
[docs] def focus(self) -> 'XAApplication': + """Hides the windows of all applications except this one. - return response_objects
+ :return: A reference to the PyXA application object. + :rtype: XAApplication -
[docs] def run(self) -> Any: - """Compiles and runs the script, returning the result. + :Example: - :return: The return value of the script. - :rtype: Any + >>> import PyXA + >>> safari = PyXA.Application("Safari") + >>> safari.focus() - :Example: + .. seealso:: :func:`unfocus` - import PyXA - script = PyXA.AppleScript(f\"\"\"tell application "System Events" - return 1 + 2 - end tell - \"\"\") - print(script.run()) - { - 'string': '3', - 'int': 3, - 'bool': False, - 'float': 3.0, - 'date': None, - 'file_url': None, - 'type_code': 3, - 'data': {length = 4, bytes = 0x03000000}, - 'event': <NSAppleEventDescriptor: 3> - } - - .. versionadded:: 0.0.5 + .. versionadded:: 0.0.1 """ - script = "" - for line in self.script: - script += line + "\n" - script = AppKit.NSAppleScript.alloc().initWithSource_(script) - - result = script.executeAndReturnError_(None)[0] - if result is not None: - self.__last_result = { - "string": result.stringValue(), - "int": result.int32Value(), - "bool": result.booleanValue(), - "float": result.doubleValue(), - "date": result.dateValue(), - "file_url": result.fileURLValue(), - "type_code": result.typeCodeValue(), - "data": result.data(), - "event": result, - } - return self.last_result
+ for app in self.xa_wksp.runningApplications(): + if app.localizedName() != self.xa_elem.localizedName(): + app.hide() + else: + app.unhide() + return self
- def __repr__(self): - return "<" + str(type(self)) + str(self.script) + ">"
+
[docs] def unfocus(self) -> 'XAApplication': + """Unhides (reveals) the windows of all other applications, but does not activate them. + :return: A reference to the PyXA application object. + :rtype: XAApplication + :Example: + >>> import PyXA + >>> safari = PyXA.Application("Safari") + >>> safari.unfocus() -
[docs]class XAClipboard(XAObject): - """A wrapper class for managing and interacting with the system clipboard. + .. seealso:: :func:`focus` - .. versionadded:: 0.0.5 - """ - def __init__(self): - self.xa_elem = AppKit.NSPasteboard.generalPasteboard() - self.content #: The content of the clipboard + .. versionadded:: 0.0.1 + """ + for app in self.xa_wksp.runningApplications(): + app.unhide() + return self
- @property - def content(self) -> Dict[str, List[Any]]: - info_by_type = {} - for item in self.xa_elem.pasteboardItems(): - for item_type in item.types(): - info_by_type[item_type] = { - "data": item.dataForType_(item_type), - "properties": item.propertyListForType_(item_type), - "strings": item.stringForType_(item_type), - } - return info_by_type + def _get_processes(self, processes): + for process in self.xa_sevt.processes(): + processes.append(process) - @content.setter - def content(self, value: List[Any]): - if not isinstance(value, list): - value = [value] - self.xa_elem.clearContents() - for index, item in enumerate(value): - if item == None: - value[index] = "" - elif isinstance(item, XAObject): - if not isinstance(item, XAClipboardCodable): - print(item, "is not a clipboard-codable object.") - continue - if isinstance(item.xa_elem, ScriptingBridge.SBElementArray) and item.xa_elem.get() is None: - value[index] = "" - else: - content = item.get_clipboard_representation() - if isinstance(content, list): - value.pop(index) - value += content - else: - value[index] = content - elif isinstance(item, int) or isinstance(item, float): - value[index] = str(item) - self.xa_elem.writeObjects_(value) +
[docs] def windows(self, filter: dict = None) -> list['XAWindow']: + return self.xa_prcs.windows(filter)
-
[docs] def clear(self): - """Clears the system clipboard. - - .. versionadded:: 0.0.5 - """ - self.xa_elem.clearContents()
+ @property + def front_window(self) -> 'XAWindow': + return self.xa_prcs.front_window -
[docs] def get_strings(self) -> List[str]: - """Retrieves string type data from the clipboard, if any such data exists. +
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': + return self._new_element(self.xa_prcs.xa_elem.menuBars(), XAUIMenuBarList, filter)
- :return: The list of strings currently copied to the clipboard - :rtype: List[str] +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL, AppKit.NSImage]]: + """Gets a clipboard-codable representation of the application. - .. versionadded:: 0.0.8 - """ - items = [] - for item in self.xa_elem.pasteboardItems(): - string = item.stringForType_(AppKit.NSPasteboardTypeString) - if string is not None: - items.append(string) - return items
+ When the clipboard content is set to an application, three items are placed on the clipboard: + 1. The application's name + 2. The URL to the application bundle + 3. The application icon -
[docs] def get_urls(self) -> List['XAURL']: - """Retrieves URL type data from the clipboard, as instances of :class:`XAURL` and :class:`XAPath`, if any such data exists. + After copying an application to the clipboard, pasting will have the following effects: + - In Finder: Paste a copy of the application bundle in the current directory + - In Terminal: Paste the name of the application followed by the path to the application + - In iWork: Paste the application name + - In Safari: Paste the application name + - In Notes: Attach a copy of the application bundle to the active note + The pasted content may different for other applications. - :return: The list of file URLs and web URLs currently copied to the clipboard - :rtype: List[XAURL] + :return: The clipboard-codable representation + :rtype: list[Union[str, AppKit.NSURL, AppKit.NSImage]] .. versionadded:: 0.0.8 """ - items = [] - for item in self.xa_elem.pasteboardItems(): - url = None - string = item.stringForType_(AppKit.NSPasteboardTypeURL) - if string is None: - string = item.stringForType_(AppKit.NSPasteboardTypeFileURL) - if string is not None: - url = XAPath(XAURL(string).xa_elem) - else: - url = XAURL(string) - - if url is not None: - items.append(url) - return items
+ return [self.xa_elem.localizedName(), self.xa_elem.bundleURL(), self.xa_elem.icon()]
-
[docs] def get_images(self) -> List['XAImage']: - """Retrieves image type data from the clipboard, as instances of :class:`XAImage`, if any such data exists. + + + +###################### +### PyXA Utilities ### +###################### +# SDEFParser, XAList, XAPredicate, XAURL, XAPath +
[docs]class SDEFParser(XAObject): + def __init__(self, sdef_file: Union['XAPath', str]): + if isinstance(sdef_file, str): + sdef_file = XAPath(sdef_file) + self.file = sdef_file #: The full path to the SDEF file to parse + + self.app_name = "" + self.scripting_suites = [] + +
[docs] def parse(self): + app_name = self.file.path.split("/")[-1][:-5].title() + xa_prefix = "XA" + app_name + + tree = ET.parse(self.file.path) + + suites = [] + + scripting_suites = tree.findall("suite") + for suite in scripting_suites: + classes = [] + commands = {} + + ### Class Extensions + class_extensions = suite.findall("class-extension") + for extension in class_extensions: + properties = [] + elements = [] + responds_to_commands = [] + + class_name = xa_prefix + extension.attrib.get("extends", "").title() + class_comment = extension.attrib.get("description", "") + + ## Class Extension Properties + class_properties = extension.findall("property") + for property in class_properties: + property_type = property.attrib.get("type", "") + if property_type == "text": + property_type = "str" + elif property_type == "boolean": + property_type = "bool" + elif property_type == "number": + property_type = "float" + elif property_type == "integer": + property_type = "int" + elif property_type == "rectangle": + property_type = "tuple[int, int, int, int]" + else: + property_type = "XA" + app_name + property_type.title() + + property_name = property.attrib.get("name", "").replace(" ", "_").lower() + property_comment = property.attrib.get("description", "") + + properties.append({ + "type": property_type, + "name": property_name, + "comment": property_comment + }) + + ## Class Extension Elements + class_elements = extension.findall("element") + for element in class_elements: + element_name = (element.attrib.get("type", "") + "s").replace(" ", "_").lower() + element_type = "XA" + app_name + element.attrib.get("type", "").title() + + elements.append({ + "name": element_name, + "type": element_type + }) + + ## Class Extension Responds-To Commands + class_responds_to_commands = extension.findall("responds-to") + for command in class_responds_to_commands: + command_name = command.attrib.get("command", "").replace(" ", "_").lower() + responds_to_commands.append(command_name) + + classes.append({ + "name": class_name, + "comment": class_comment, + "properties": properties, + "elements": elements, + "responds-to": responds_to_commands + }) + + ### Classes + scripting_classes = suite.findall("class") + for scripting_class in scripting_classes: + properties = [] + elements = [] + responds_to_commands = [] + + class_name = xa_prefix + scripting_class.attrib.get("name", "").title() + class_comment = scripting_class.attrib.get("description", "") + + ## Class Properties + class_properties = scripting_class.findall("property") + for property in class_properties: + property_type = property.attrib.get("type", "") + if property_type == "text": + property_type = "str" + elif property_type == "boolean": + property_type = "bool" + elif property_type == "number": + property_type = "float" + elif property_type == "integer": + property_type = "int" + elif property_type == "rectangle": + property_type = "tuple[int, int, int, int]" + else: + property_type = "XA" + app_name + property_type.title() + + property_name = property.attrib.get("name", "").replace(" ", "_").lower() + property_comment = property.attrib.get("description", "") + + properties.append({ + "type": property_type, + "name": property_name, + "comment": property_comment + }) + + ## Class Elements + class_elements = scripting_class.findall("element") + for element in class_elements: + element_name = (element.attrib.get("type", "") + "s").replace(" ", "_").lower() + element_type = "XA" + app_name + element.attrib.get("type", "").title() + + elements.append({ + "name": element_name, + "type": element_type + }) + + ## Class Responds-To Commands + class_responds_to_commands = scripting_class.findall("responds-to") + for command in class_responds_to_commands: + command_name = command.attrib.get("command", "").replace(" ", "_").lower() + responds_to_commands.append(command_name) + + classes.append({ + "name": class_name, + "comment": class_comment, + "properties": properties, + "elements": elements, + "responds-to": responds_to_commands + }) + + + ### Commands + script_commands = suite.findall("command") + for command in script_commands: + command_name = command.attrib.get("name", "").lower().replace(" ", "_") + command_comment = command.attrib.get("description", "") + + parameters = [] + direct_param = command.find("direct-parameter") + if direct_param is not None: + direct_parameter_type = direct_param.attrib.get("type", "") + if direct_parameter_type == "specifier": + direct_parameter_type = "XABase.XAObject" + + direct_parameter_comment = direct_param.attrib.get("description") + + parameters.append({ + "name": "direct_param", + "type": direct_parameter_type, + "comment": direct_parameter_comment + }) + + if not "_" in command_name and len(parameters) > 0: + command_name += "_" + + command_parameters = command.findall("parameter") + for parameter in command_parameters: + parameter_type = parameter.attrib.get("type", "") + if parameter_type == "specifier": + parameter_type = "XAObject" + + parameter_name = parameter.attrib.get("name", "").lower().replace(" ", "_") + parameter_comment = parameter.attrib.get("description", "") + + parameters.append({ + "name": parameter_name, + "type": parameter_type, + "comment": parameter_comment, + }) + + commands[command_name] = { + "name": command_name, + "comment": command_comment, + "parameters": parameters + } + + suites.append({ + "classes": classes, + "commands": commands + }) + + self.scripting_suites = suites + return suites
+ +
[docs] def export(self, output_file: Union['XAPath', str]): + if isinstance(output_file, XAPath): + output_file = output_file.path + + lines = [] + + lines.append("from typing import Any, Callable, Union") + lines.append("\nfrom PyXA import XABase") + lines.append("from PyXA.XABase import OSType") + lines.append("from PyXA import XABaseScriptable") + + for suite in self.scripting_suites: + for scripting_class in suite["classes"]: + lines.append("\n\n") + lines.append("class " + scripting_class["name"].replace(" ", "") + "List:") + lines.append("\t\"\"\"A wrapper around lists of " + scripting_class["name"].lower() + "s that employs fast enumeration techniques.") + lines.append("\n\tAll properties of tabs can be called as methods on the wrapped list, returning a list containing each tab's value for the property.") + lines.append("\n\t.. versionadded:: " + PYXA_VERSION) + lines.append("\t\"\"\"") + + lines.append("\tdef __init__(self, properties: dict, filter: Union[dict, None] = None):") + lines.append("\t\tsuper().__init__(properties, " + scripting_class["name"].replace(" ", "") + ", filter)") + + for property in scripting_class["properties"]: + lines.append("") + lines.append("\tdef " + property["name"] + "(self) -> list['" + property["type"].replace(" ", "") + "']:") + lines.append("\t\t\"\"\"" + property["comment"] + "\n\n\t\t.. versionadded:: " + PYXA_VERSION + "\n\t\t\"\"\"") + lines.append("\t\treturn list(self.xa_elem.arrayByApplyingSelector_(\"" + property["name"] + "\"))") + + for property in scripting_class["properties"]: + lines.append("") + lines.append("\tdef by_" + property["name"] + "(self, " + property["name"] + ") -> '" + scripting_class["name"].replace(" ", "") + "':") + lines.append("\t\t\"\"\"Retrieves the " + scripting_class["comment"] + "whose " + property["name"] + " matches the given " + property["name"] + ".\n\n\t\t.. versionadded:: " + PYXA_VERSION + "\n\t\t\"\"\"") + lines.append("\t\treturn self.by_property(\"" + property["name"] + "\", " + property["name"] + ")") + + + lines.append("") + lines.append("class " + scripting_class["name"].replace(" ", "") + ":") + lines.append("\t\"\"\"" + scripting_class["comment"] + "\n\n\t.. versionadded:: " + PYXA_VERSION + "\n\t\"\"\"") + + for property in scripting_class["properties"]: + lines.append("") + lines.append("\t@property") + lines.append("\tdef " + property["name"] + "(self) -> '" + property["type"].replace(" ", "") + "':") + lines.append("\t\t\"\"\"" + property["comment"] + "\n\n\t\t.. versionadded:: " + PYXA_VERSION + "\n\t\t\"\"\"") + lines.append("\t\treturn self.xa_elem." + property["name"] + "()") + + for element in scripting_class["elements"]: + lines.append("") + lines.append("\tdef " + element["name"].replace(" ", "") + "(self, filter: Union[dict, None] = None) -> '" + element["type"].replace(" ", "") + "':") + lines.append("\t\t\"\"\"Returns a list of " + element["name"] + ", as PyXA objects, matching the given filter.") + lines.append("\n\t\t.. versionadded:: " + PYXA_VERSION) + lines.append("\t\t\"\"\"") + lines.append("\t\tself._new_element(self.xa_elem." + element["name"] + "(), " + element["type"].replace(" ", "") + "List, filter)") + + for command in scripting_class["responds-to"]: + if command in suite["commands"]: + lines.append("") + command_str = "\tdef " + suite["commands"][command]["name"] + "(self, " + + for parameter in suite["commands"][command]["parameters"]: + command_str += parameter["name"] + ": '" + parameter["type"] + "', " + + command_str = command_str[:-2] + "):" + lines.append(command_str) + + lines.append("\t\t\"\"\"" + suite["commands"][command]["comment"]) + lines.append("\n\t\t.. versionadded:: " + PYXA_VERSION) + lines.append("\t\t\"\"\"") + + cmd_call_str = "self.xa_elem." + suite["commands"][command]["name"] + "(" + + if len(suite["commands"][command]["parameters"]) > 0: + for parameter in suite["commands"][command]["parameters"]: + cmd_call_str += parameter["name"] + ", " + + cmd_call_str = cmd_call_str[:-2] + ")" + else: + cmd_call_str += ")" + + lines.append("\t\t" + cmd_call_str) + + data = "\n".join(lines) + with open(output_file, "w") as f: + f.write(data)
+ +
[docs]class XAList(XAObject): + """A wrapper around NSArray and NSMutableArray objects enabling fast enumeration and lazy evaluation of Objective-C objects. + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties: dict, object_class: type = None, filter: Union[dict, None] = None): + """Creates an efficient wrapper object around a list of scriptable elements. + + :param properties: PyXA properties passed to this object for utility purposes + :type properties: dict + :param object_class: _description_, defaults to None + :type object_class: type, optional + :param filter: A dictionary of properties and values to filter items by, defaults to None + :type filter: Union[dict, None], optional + + .. versionchanged:: 0.0.8 + The filter property is deprecated and will be removed in a future version. Use the :func:`filter` method instead. + + .. versionadded:: 0.0.3 + """ + super().__init__(properties) + self.xa_ocls = object_class + + if not isinstance(self.xa_elem, AppKit.NSArray): + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(self.xa_elem) + + if filter is not None: + self.xa_elem = XAPredicate().from_dict(filter).evaluate(self.xa_elem) + +
[docs] def by_property(self, property: str, value: Any) -> XAObject: + """Retrieves the first element whose property value matches the given value, if one exists. + + :param property: The property to match + :type property: str + :param value: The value to match + :type value: Any + :return: The matching element, if one is found + :rtype: XAObject + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Photos") + >>> photo = app.media_items().by_property("id", "CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001") + >>> print(photo) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001> + + .. versionadded:: 0.0.6 + """ + predicate = XAPredicate() + predicate.add_eq_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + # if hasattr(ls, "get"): + # ls = predicate.evaluate(self.xa_elem).get() + + if len(ls) == 0: + return None + + obj = ls[0] + return self._new_element(obj, self.xa_ocls)
+ +
[docs] def equalling(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value equals the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("TV") + >>> print(app.tracks().equalling("playedCount", 0)) + <<class 'PyXA.apps.TV.XATVTrackList'>['Frozen', 'Sunshine', 'The Hunger Games: Mockingjay - Part 2', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_eq_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def not_equalling(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value does not equal the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("TV") + >>> print(app.tracks().not_equalling("playedCount", 0)) + <<class 'PyXA.apps.TV.XATVTrackList'>['The Avatar State', 'The Cave of Two Lovers', 'Return to Omashu', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_neq_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def containing(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value contains the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Reminders") + >>> print(app.reminders().containing("name", "PyXA")) + <<class 'PyXA.apps.Reminders.XARemindersReminderList'>['PyXA v0.1.0 release']> + + .. versionadded:: 0.0.6 + """ + predicate = XAPredicate() + predicate.add_contains_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def not_containing(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value does not contain the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Reminders") + >>> print(app.reminders().not_containing("name", " ")) + <<class 'PyXA.apps.Reminders.XARemindersReminderList'>['Trash', 'Thing', 'Reminder', ...]> + + .. versionadded:: 0.1.0 + """ + ls = XAPredicate.evaluate_with_format(self.xa_elem, f"NOT {property} CONTAINS \"{value}\"") + return self._new_element(ls, self.__class__)
+ +
[docs] def beginning_with(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value begins with the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("System Events") + >>> print(app.downloads_folder.files().beginning_with("name", "Example")) + <<class 'PyXA.apps.SystemEvents.XASystemEventsFileList'>['Example.png', 'ExampleImage.png', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_begins_with_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def ending_with(self, property: str, value: str) -> XAObject: + """Retrieves all elements whose property value ends with the given value. + + :param property: The property to match + :type property: str + :param value: The value to search for + :type value: str + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("System Events") + >>> print(app.downloads_folder.files().ending_with("name", ".png")) + <<class 'PyXA.apps.SystemEvents.XASystemEventsFileList'>['Example.png', 'Image.png', ...]> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_ends_with_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def greater_than(self, property: str, value: Union[int, float]) -> XAObject: + """Retrieves all elements whose property value is greater than the given value. + + :param property: The property to match + :type property: str + :param value: The value to compare against + :type value: Union[int, float] + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Photos") + >>> print(app.media_items().greater_than("altitude", 10000)[0].spotlight()) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=53B0F28E-0B39-446B-896C-484CD0DC2D3C/L0/001> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_gt_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def less_than(self, property: str, value: Union[int, float]) -> XAObject: + """Retrieves all elements whose property value is less than the given value. + + :param property: The property to match + :type property: str + :param value: The value to compare against + :type value: Union[int, float] + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> app = PyXA.Application("Music") + >>> tracks = app.tracks() + >>> print(tracks.less_than("playedCount", 5).name()) + ['Outrunning Karma', 'Death of a Hero', '1994', 'Mind Is a Prison'] + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_lt_condition(property, value) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def between(self, property: str, value1: Union[int, float], value2: Union[int, float]) -> XAObject: + """Retrieves all elements whose property value is between the given values. + + :param property: The property to match + :type property: str + :param value1: The lower-end of the range to match + :type value1: Union[int, float] + :param value2: The upper-end of the range to match + :type value2: Union[int, float] + :return: The list of matching elements + :rtype: XAList + + :Example: + + >>> import PyXA + >>> from datetime import datetime, timedelta + >>> + >>> app = PyXA.Application("Calendar") + >>> events = app.calendars()[3].events() + >>> now = datetime.now() + >>> print(events.between("startDate", now, now + timedelta(days=1))) + <<class 'PyXA.apps.Calendar.XACalendarEventList'>['Capstone Meeting', 'Lunch with Dan']> + + .. versionadded:: 0.1.0 + """ + predicate = XAPredicate() + predicate.add_gt_condition(property, value1) + predicate.add_lt_condition(property, value2) + ls = predicate.evaluate(self.xa_elem) + return self._new_element(ls, self.__class__)
+ +
[docs] def filter(self, filter: str, comparison_operation: Union[str, None] = None, value1: Union[Any, None] = None, value2: Union[Any, None] = None) -> 'XAList': + """Filters the list by the given parameters. + + The filter may be either a format string, used to create an NSPredicate, or up to 4 arguments specifying the filtered property name, the comparison operation, and up to two values to compare against. + + :param filter: A format string or a property name + :type filter: str + :param comparison_operation: The symbol or name of a comparison operation, such as > or <, defaults to None + :type comparison_operation: Union[str, None], optional + :param value1: The first value to compare each list item's property value against, defaults to None + :type value1: Union[Any, None], optional + :param value2: The second value to compare each list item's property value against, defaults to None + :type value2: Union[Any, None], optional + :return: The filter XAList object + :rtype: XAList + + :Example 1: Get the last file sent by you (via this machine) in Messages.app + + >>> import PyXA + >>> app = PyXA.Application("Messages") + >>> last_file_transfer = app.file_transfers().filter("direction", "==", app.MessageDirection.OUTGOING)[-1] + >>> print(last_file_transfer) + <<class 'PyXA.apps.Messages.XAMessagesFileTransfer'>Test.jpg> + + :Example 2: Get the list of favorite photos/videos from Photos.app + + >>> import PyXA + >>> app = PyXA.Application("Photos") + >>> favorites = app.media_items().filter("favorite", "==", True) + >>> print(favorites) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItemList'>['CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001', 'EFEB7F37-8373-4972-8E43-21612F597185/L0/001', ...]> + + .. note:: + + For properties that appear to be boolean but fail to return expected filter results, try using the corresponding 0 or 1 value instead. + + :Example 3: Provide a custom format string + + >>> import PyXA + >>> app = PyXA.Application("Photos") + >>> photo = app.media_items().filter("id == 'CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001'")[0] + >>> print(photo) + <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001> + + .. versionadded:: 0.0.8 + """ + if comparison_operation is not None and value1 is not None: + predicate = XAPredicate() + if comparison_operation in ["=", "==", "eq", "EQ", "equals", "EQUALS"]: + predicate.add_eq_condition(filter, value1) + elif comparison_operation in ["!=", "!==", "neq", "NEQ", "not equal to", "NOT EQUAL TO"]: + predicate.add_neq_condition(filter, value1) + elif comparison_operation in [">", "gt", "GT", "greater than", "GREATER THAN"]: + predicate.add_gt_condition(filter, value1) + elif comparison_operation in ["<", "lt", "LT", "less than", "LESS THAN"]: + predicate.add_lt_condition(filter, value1) + elif comparison_operation in [">=", "geq", "GEQ", "greater than or equal to", "GREATER THAN OR EQUAL TO"]: + predicate.add_geq_condition(filter, value1) + elif comparison_operation in ["<=", "leq", "LEQ", "less than or equal to", "LESS THAN OR EQUAL TO"]: + predicate.add_leq_condition(filter, value1) + elif comparison_operation in ["begins with", "beginswith", "BEGINS WITH", "BEGINSWITH"]: + predicate.add_begins_with_condition(filter, value1) + elif comparison_operation in ["contains", "CONTAINS"]: + predicate.add_contains_condition(filter, value1) + elif comparison_operation in ["ends with", "endswith", "ENDS WITH", "ENDSWITH"]: + predicate.add_ends_with_condition(filter, value1) + elif comparison_operation in ["between", "BETWEEN"]: + predicate.add_between_condition(filter, value1, value2) + elif comparison_operation in ["matches", "MATCHES"]: + predicate.add_match_condition(filter, value1) + + filtered_list = predicate.evaluate(self.xa_elem) + return super()._new_element(filtered_list, self.__class__) + else: + filtered_list = XAPredicate.evaluate_with_format(self.xa_elem, filter) + return super()._new_element(filtered_list, self.__class__)
+ +
[docs] def at(self, index: int) -> XAObject: + """Retrieves the element at the specified index. + + :param index: The index of the desired element + :type index: int + :return: The PyXA-wrapped element object + :rtype: XAObject + + .. versionadded:: 0.0.6 + """ + return self._new_element(self.xa_elem[index], self.xa_ocls)
+ + @property + def first(self) -> XAObject: + """Retrieves the first element of the list as a wrapped PyXA object. + + :return: The wrapped object + :rtype: XAObject + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_elem.firstObject(), self.xa_ocls) + + @property + def last(self) -> XAObject: + """Retrieves the last element of the list as a wrapped PyXA object. + + :return: The wrapped object + :rtype: XAObject + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_elem.lastObject(), self.xa_ocls) + +
[docs] def shuffle(self) -> 'XAList': + """Randomizes the order of objects in the list. + + :return: A reference to the shuffled XAList + :rtype: XAList + + .. versionadded:: 0.0.3 + """ + try: + self.xa_elem = self.xa_elem.shuffledArray() + except AttributeError: + random.shuffle(self.xa_elem) + return self
+ +
[docs] def push(self, *elements: list[XAObject]) -> Union[XAObject, list[XAObject], None]: + """Appends the object referenced by the provided PyXA wrapper to the end of the list. + + .. versionadded:: 0.0.3 + """ + objects = [] + for element in elements: + len_before = len(self.xa_elem) + self.xa_elem.addObject_(element.xa_elem) + + if len(self.xa_elem) == len_before: + # Object wasn't added -- try force-getting the list before adding + self.xa_elem.get().addObject_(element.xa_elem) + + if len(self.xa_elem) > len_before: + objects.append(self[len(self.xa_elem) - 1]) + + if len(objects) == 1: + return objects[0] + + if len(objects) == 0: + return None + + return objects
+ +
[docs] def insert(self, element: XAObject, index: int): + """Inserts the object referenced by the provided PyXA wrapper at the specified index. + + .. versionadded:: 0.0.3 + """ + self.xa_elem.insertObject_atIndex_(element.xa_elem, index)
+ +
[docs] def pop(self, index: int = -1) -> XAObject: + """Removes the object at the specified index from the list and returns it. + + .. versionadded:: 0.0.3 + """ + removed = self.xa_elem.lastObject() + self.xa_elem.removeLastObject() + return self._new_element(removed, self.xa_ocls)
+ +
[docs] def count(self, count_function: Callable[[object], bool]): + count = 0 + for index in range(len(self)): + in_count = False + try: + in_count = count_function(self.xa_elem[index]) + except: + # TODO: Add logging message here + pass + + if not in_count: + try: + in_count = count_function(self[index]) + except: + pass + + if in_count: + count += 1 + return count
+ + def __getitem__(self, key: Union[int, slice]): + if isinstance(key, slice): + arr = AppKit.NSMutableArray.alloc().initWithArray_([self.xa_elem[index] for index in range(key.start, key.stop, key.step or 1)]) + return self._new_element(arr, self.__class__) + if key < 0: + key = self.xa_elem.count() + key + return self._new_element(self.xa_elem.objectAtIndex_(key), self.xa_ocls) + + def __len__(self): + return len(self.xa_elem) + + def __reversed__(self): + self.xa_elem = self.xa_elem.reverseObjectEnumerator().allObjects() + return self + + def __iter__(self): + return (self._new_element(object, self.xa_ocls) for object in self.xa_elem.objectEnumerator()) + + def __contains__(self, item): + if isinstance(item, XAObject): + item = item.xa_elem + return item in self.xa_elem + + def __repr__(self): + return "<" + str(type(self)) + str(self.xa_elem) + ">"
+ + + + +
[docs]class XAPredicate(XAObject, XAClipboardCodable): + """A predicate used to filter arrays. + + .. versionadded:: 0.0.4 + """ + def __init__(self): + self.keys: list[str] = [] + self.operators: list[str] = [] + self.values: list[str] = [] + +
[docs] def from_dict(self, ref_dict: dict) -> 'XAPredicate': + """Populates the XAPredicate object from the supplied dictionary. + + The predicate will use == for all comparisons. + + :param ref_dict: A specification of key, value pairs + :type ref_dict: dict + :return: The populated predicate object + :rtype: XAPredicate + + .. versionadded:: 0.0.4 + """ + for key, value in ref_dict.items(): + self.keys.append(key) + self.operators.append("==") + self.values.append(value) + return self
+ +
[docs] def from_args(self, *args) -> 'XAPredicate': + """Populates the XAPredicate object from the supplied key, value argument pairs. + + The number of keys and values must be equal. The predicate will use == for all comparisons. + + :raises InvalidPredicateError: Raised when the number of keys does not match the number of values + :return: The populated predicate object + :rtype: XAPredicate + + .. versionadded:: 0.0.4 + """ + arg_num = len(args) + if arg_num % 2 != 0: + raise InvalidPredicateError("The number of keys and values must be equal; the number of arguments must be an even number.") + + for index, value in enumerate(args): + if index % 2 == 0: + self.keys.append(value) + self.operators.append("==") + self.values.append(args[index + 1]) + return self
+ +
[docs] def evaluate(self, target: Union[AppKit.NSArray, XAList]) -> AppKit.NSArray: + """Evaluates the predicate on the given array. + + :param target: The array to evaluate against the predicate + :type target: AppKit.NSArray + :return: The filtered array + :rtype: AppKit.NSArray + + .. versionadded:: 0.0.4 + """ + target_list = target + if isinstance(target, XAList): + target_list = target.xa_elem + + placeholders = ["%@"] * len(self.values) + expressions = [" ".join(expr) for expr in zip(self.keys, self.operators, placeholders)] + format = "( " + " ) && ( ".join(expressions) + " )" + + predicate = AppKit.NSPredicate.predicateWithFormat_(format, *self.values) + ls = target_list.filteredArrayUsingPredicate_(predicate) + + if len(ls) == 0: + try: + # Not sure why this is necessary sometimes, but it is. + predicate = str(predicate) + ls = target_list.filteredArrayUsingPredicate_(AppKit.NSPredicate.predicateWithFormat_(predicate)) + except ValueError: + pass + + if isinstance(target, XAList): + return target.__class__({ + "parent": target, + "appspace": self.xa_apsp, + "workspace": self.xa_wksp, + "element": ls, + "appref": self.xa_aref, + }) + return ls
+ +
[docs] def evaluate_with_format(target: Union[AppKit.NSArray, XAList], fmt: str) -> AppKit.NSArray: + """Evaluates the specified array against a predicate with the given format. + + :param target: The array to filter + :type target: AppKit.NSArray + :param fmt: The format string for the predicate + :type fmt: str + :return: The filtered array + :rtype: AppKit.NSArray + + .. versionadded:: 0.0.4 + """ + target_list = target + if isinstance(target, XAList): + target_list = target.xa_elem + + predicate = AppKit.NSPredicate.predicateWithFormat_(fmt) + ls = target_list.filteredArrayUsingPredicate_(predicate) + + if isinstance(target, XAList): + return target.__class__({ + "parent": target, + "appspace": AppKit.NSApplication.sharedApplication(), + "workspace": AppKit.NSWorkspace.sharedWorkspace(), + "element": ls, + "appref": AppKit.NSApplication.sharedApplication(), + }) + return ls
+ +
[docs] def evaluate_with_dict(target: Union[AppKit.NSArray, XAList], properties_dict: dict) -> AppKit.NSArray: + """Evaluates the specified array against a predicate constructed from the supplied dictionary. + + The predicate will use == for all comparisons. + + :param target: The array to filter + :type target: AppKit.NSArray + :param properties_dict: The specification of key, value pairs + :type properties_dict: dict + :return: The filtered array + :rtype: AppKit.NSArray + + .. versionadded:: 0.0.4 + """ + target_list = target + if isinstance(target, XAList): + target_list = target.xa_elem + + fmt = "" + for key, value in properties_dict.items(): + if isinstance(value, str): + value = "'" + value + "'" + fmt += f"( {key} == {value} ) &&" + + predicate = AppKit.NSPredicate.predicateWithFormat_(fmt[:-3]) + ls = target_list.filteredArrayUsingPredicate_(predicate) + + if isinstance(target, XAList): + return target.__class__({ + "parent": target, + "appspace": AppKit.NSApplication.sharedApplication(), + "workspace": AppKit.NSWorkspace.sharedWorkspace(), + "element": ls, + "appref": AppKit.NSApplication.sharedApplication(), + }) + return ls
+ + # EQUAL +
[docs] def add_eq_condition(self, property: str, value: Any): + """Appends an `==` condition to the end of the predicate format. + + The added condition will have the form `property == value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("==") + self.values.append(value)
+ +
[docs] def insert_eq_condition(self, index: int, property: str, value: Any): + """Inserts an `==` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property == value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "==") + self.values.insert(index, value)
+ + # NOT EQUAL +
[docs] def add_neq_condition(self, property: str, value: Any): + """Appends a `!=` condition to the end of the predicate format. + + The added condition will have the form `property != value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("!=") + self.values.append(value)
+ +
[docs] def insert_neq_condition(self, index: int, property: str, value: Any): + """Inserts a `!=` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property != value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "!=") + self.values.insert(index, value)
+ + # GREATER THAN OR EQUAL +
[docs] def add_geq_condition(self, property: str, value: Any): + """Appends a `>=` condition to the end of the predicate format. + + The added condition will have the form `property >= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append(">=") + self.values.append(value)
+ +
[docs] def insert_geq_condition(self, index: int, property: str, value: Any): + """Inserts a `>=` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property >= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, ">=") + self.values.insert(index, value)
+ + # LESS THAN OR EQUAL +
[docs] def add_leq_condition(self, property: str, value: Any): + """Appends a `<=` condition to the end of the predicate format. + + The added condition will have the form `property <= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("<=") + self.values.append(value)
+ +
[docs] def insert_leq_condition(self, index: int, property: str, value: Any): + """Inserts a `<=` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property <= value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "<=") + self.values.insert(index, value)
+ + # GREATER THAN +
[docs] def add_gt_condition(self, property: str, value: Any): + """Appends a `>` condition to the end of the predicate format. + + The added condition will have the form `property > value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append(">") + self.values.append(value)
+ +
[docs] def insert_gt_condition(self, index: int, property: str, value: Any): + """Inserts a `>` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property > value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, ">") + self.values.insert(index, value)
+ + # LESS THAN +
[docs] def add_lt_condition(self, property: str, value: Any): + """Appends a `<` condition to the end of the predicate format. + + The added condition will have the form `property < value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("<") + self.values.append(value)
+ +
[docs] def insert_lt_condition(self, index: int, property: str, value: Any): + """Inserts a `<` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property < value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "<") + self.values.insert(index, value)
+ + # BETWEEN +
[docs] def add_between_condition(self, property: str, value1: Union[int, float], value2: Union[int, float]): + """Appends a `BETWEEN` condition to the end of the predicate format. + + The added condition will have the form `property BETWEEN [value1, value2]`. + + :param property: A property of an object to check the condition against + :type property: str + :param value1: The lower target value of the condition + :type value1: Union[int, float] + :param value2: The upper target value of the condition + :type value2: Union[int, float] + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("BETWEEN") + self.values.append([value1, value2])
+ +
[docs] def insert_between_condition(self, index: int, property: str, value1: Union[int, float], value2: Union[int, float]): + """Inserts a `BETWEEN` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property BETWEEN [value1, value2]`. + + :param property: A property of an object to check the condition against + :type property: str + :param value1: The lower target value of the condition + :type value1: Union[int, float] + :param value2: The upper target value of the condition + :type valu2e: Union[int, float] + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "BETWEEN") + self.values.insert(index, [value1, value2])
+ + # BEGINSWITH +
[docs] def add_begins_with_condition(self, property: str, value: Any): + """Appends a `BEGINSWITH` condition to the end of the predicate format. + + The added condition will have the form `property BEGINSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("BEGINSWITH") + self.values.append(value)
+ +
[docs] def insert_begins_with_condition(self, index: int, property: str, value: Any): + """Inserts a `BEGINSWITH` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property BEGINSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "BEGINSWITH") + self.values.insert(index, value)
+ + # ENDSWITH +
[docs] def add_ends_with_condition(self, property: str, value: Any): + """Appends a `ENDSWITH` condition to the end of the predicate format. + + The added condition will have the form `property ENDSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("ENDSWITH") + self.values.append(value)
+ +
[docs] def insert_ends_with_condition(self, index: int, property: str, value: Any): + """Inserts a `ENDSWITH` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property ENDSWITH value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "ENDSWITH") + self.values.insert(index, value)
+ + # CONTAINS +
[docs] def add_contains_condition(self, property: str, value: Any): + """Appends a `CONTAINS` condition to the end of the predicate format. + + The added condition will have the form `property CONTAINS value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("CONTAINS") + self.values.append(value)
+ +
[docs] def insert_contains_condition(self, index: int, property: str, value: Any): + """Inserts a `CONTAINS` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property CONTAINS value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "CONTAINS") + self.values.insert(index, value)
+ + # MATCHES +
[docs] def add_match_condition(self, property: str, value: Any): + """Appends a `MATCHES` condition to the end of the predicate format. + + The added condition will have the form `property MATCHES value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.append(property) + self.operators.append("MATCHES") + self.values.append(value)
+ +
[docs] def insert_match_condition(self, index: int, property: str, value: Any): + """Inserts a `MATCHES` condition to the predicate format at the desired location, specified by index. + + The added condition will have the form `property MATCHES value`. + + :param property: A property of an object to check the condition against + :type property: str + :param value: The target value of the condition + :type value: Any + + .. versionadded:: 0.0.4 + """ + self.keys.insert(index, property) + self.operators.insert(index, "MATCHES") + self.values.insert(index, value)
+ +
[docs] def get_clipboard_representation(self) -> str: + """Gets a clipboard-codable representation of the predicate. + + When a predicate is copied to the clipboard, the string representation of the predicate is added to the clipboard. + + :return: The string representation of the predicate + :rtype: str + + .. versionadded:: 0.0.8 + """ + placeholders = ["%@"] * len(self.values) + expressions = [" ".join(expr) for expr in zip(self.keys, self.operators, placeholders)] + format = "( " + " ) && ( ".join(expressions) + " )" + predicate = AppKit.NSPredicate.predicateWithFormat_(format, *self.values) + return predicate.predicateFormat()
+ + + + +
[docs]class XAURL(XAObject, XAClipboardCodable): + """A URL using any scheme recognized by the system. This can be a file URL. + + .. versionadded:: 0.0.5 + """ + def __init__(self, url: Union[str, AppKit.NSURL, 'XAURL', 'XAPath']): + super().__init__() + self.parameters: str #: The query parameters of the URL + self.scheme: str #: The URI scheme of the URL + self.fragment: str #: The fragment identifier following a # symbol in the URL + self.port: int #: The port that the URL points to + self.html: element.tag #: The html of the URL + self.title: str #: The title of the URL + self.soup: BeautifulSoup = None #: The bs4 object for the URL, starts as None until a bs4-related action is made + self.url: str #: The string form of the URL + + if isinstance(url, str): + logging.debug("Initializing XAURL from string") + # URL-encode spaces + url = url.replace(" ", "%20") + + if url.startswith("/"): + # Prepend file scheme + url = "file://" + url + elif url.replace(".", "").isdecimal(): + # URL is an IP -- must add http:// prefix + if ":" not in url: + # No port provided, add port 80 by default + url = "http://" + url + ":80" + else: + url = "http://" + url + elif "://" not in url: + # URL is not currently valid, try prepending http:// + url = "http://" + url + + self.url = url + url = AppKit.NSURL.alloc().initWithString_(url) + elif isinstance(url, XAURL) or isinstance(url, XAPath): + logging.debug("Initializing XAURL from XAURL/XAPath") + self.url = url.url + url = url.xa_elem + + self.xa_elem = url + + @property + def base_url(self) -> str: + return self.xa_elem.host() + + @property + def parameters(self) -> str: + return self.xa_elem.query() + + @property + def scheme(self) -> str: + return self.xa_elem.scheme() + + @property + def fragment(self) -> str: + return self.xa_elem.fragment() + + @property + def html(self) -> element.Tag: + if self.soup is None: + self.__get_soup() + return self.soup.html + + @property + def title(self) -> str: + if self.soup is None: + self.__get_soup() + return self.soup.title.text + + def __get_soup(self): + req = requests.get(str(self.xa_elem)) + self.soup = BeautifulSoup(req.text, "html.parser") + +
[docs] def open(self): + """Opens the URL in the appropriate default application. + + .. versionadded:: 0.0.5 + """ + AppKit.NSWorkspace.sharedWorkspace().openURL_(self.xa_elem)
+ +
[docs] def extract_text(self) -> list[str]: + """Extracts the visible text from the webpage that the URL points to. + + :return: The list of extracted lines of text + :rtype: list[str] + + .. versionadded:: 0.0.8 + """ + if self.soup is None: + self.__get_soup() + return self.soup.get_text().splitlines()
+ +
[docs] def extract_images(self) -> list['XAImage']: + """Extracts all images from HTML of the webpage that the URL points to. + + :return: The list of extracted images + :rtype: list[XAImage] + + .. versionadded:: 0.0.8 + """ + data = AppKit.NSData.alloc().initWithContentsOfURL_(AppKit.NSURL.URLWithString_(str(self.xa_elem))) + image = AppKit.NSImage.alloc().initWithData_(data) + + if image is not None: + image_object = XAImage(image, name = self.xa_elem.pathComponents()[-1]) + return [image_object] + else: + if self.soup is None: + self.__get_soup() + + images = self.soup.findAll("img") + image_objects = [] + for image in images: + image_src = image["src"] + if image_src.startswith("/"): + image_src = str(self) + str(image["src"]) + + data = AppKit.NSData.alloc().initWithContentsOfURL_(AppKit.NSURL.URLWithString_(image_src)) + image = AppKit.NSImage.alloc().initWithData_(data) + if image is not None: + image_object = XAImage(image, name = XAURL(image_src).xa_elem.pathComponents()[-1]) + image_objects.append(image_object) + + return image_objects
+ +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of the URL. + + When the clipboard content is set to a URL, the raw URL data and the string representation of the URL are added to the clipboard. + + :return: The clipboard-codable form of the URL + :rtype: Any + + .. versionadded:: 0.0.8 + """ + return [self.xa_elem, str(self.xa_elem)]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.xa_elem) + ">"
+ + + + +
[docs]class XAPath(XAObject, XAClipboardCodable): + """A path to a file on the disk. + + .. versionadded:: 0.0.5 + """ + def __init__(self, path: Union[str, AppKit.NSURL]): + super().__init__() + if isinstance(path, str): + path = AppKit.NSURL.alloc().initFileURLWithPath_(path) + self.xa_elem = path + self.path = path.path() #: The path string without the file:// prefix + self.url = str(self.xa_elem) #: The path string with the file:// prefix included + self.xa_wksp = AppKit.NSWorkspace.sharedWorkspace() + +
[docs] def open(self): + """Opens the file in its default application. + + .. versionadded: 0.0.5 + """ + self.xa_wksp.openURL_(self.xa_elem)
+ +
[docs] def show_in_finder(self): + """Opens a Finder window showing the folder containing this path, with the associated file selected. Synonymous with :func:`select`. + + .. versionadded: 0.0.9 + """ + self.select()
+ +
[docs] def select(self): + """Opens a Finder window showing the folder containing this path, with the associated file selected. Synonymous with :func:`show_in_finder`. + + .. versionadded: 0.0.5 + """ + self.xa_wksp.activateFileViewerSelectingURLs_([self.xa_elem])
+ +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of the path. + + When the clipboard content is set to a path, the raw file URL data and the string representation of the path are added to the clipboard. + + :return: The clipboard-codable form of the path + :rtype: Any + + .. versionadded:: 0.0.8 + """ + return [self.xa_elem, self.xa_elem.path()]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.xa_elem) + ">"
+ + + + +######################## +### Interoperability ### +######################## +# AppleScript +
[docs]class AppleScript(XAObject): + """A class for constructing and executing AppleScript scripts. + + .. versionadded:: 0.0.5 + """ + def __init__(self, script: Union[str, list[str], None] = None): + """Creates a new AppleScript object. + + :param script: A string or list of strings representing lines of AppleScript code, or the path to a script plaintext file, defaults to None + :type script: Union[str, list[str], None], optional + + .. versionadded:: 0.0.5 + """ + self.script: list[str] #: The lines of AppleScript code contained in the script + self.last_result: Any #: The return value of the last execution of the script + self.file_path: XAPath #: The file path of this script, if one exists + + if isinstance(script, str): + if script.startswith("/"): + with open(script, 'r') as f: + script = f.readlines() + else: + self.script = [script] + elif isinstance(script, list): + self.script = script + elif script == None: + self.script = [] + + @property + def last_result(self) -> Any: + return self.__last_result + + @property + def file_path(self) -> 'XAPath': + return self.__file_path + +
[docs] def add(self, script: Union[str, list[str], 'AppleScript']): + """Adds the supplied string, list of strings, or script as a new line entry in the script. + + :param script: The script to append to the current script string. + :type script: Union[str, list[str], AppleScript] + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript("tell application \"Safari\"") + >>> script.add("print the document of window 1") + >>> script.add("end tell") + >>> script.run() + + .. versionadded:: 0.0.5 + """ + if isinstance(script, str): + self.script.append(script) + elif isinstance(script, list): + self.script.extend(script) + elif isinstance(script, AppleScript): + self.script.extend(script.script)
+ +
[docs] def insert(self, index: int, script: Union[str, list[str], 'AppleScript']): + """Inserts the supplied string, list of strings, or script as a line entry in the script starting at the given line index. + + :param index: The line index to begin insertion at + :type index: int + :param script: The script to insert into the current script + :type script: Union[str, list[str], AppleScript] + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> script.insert(1, "activate") + >>> script.run() + + .. versionadded:: 0.0.9 + """ + if isinstance(script, str): + self.script.insert(index, script) + elif isinstance(script, list): + for line in script: + self.script.insert(index, line) + index += 1 + elif isinstance(script, AppleScript): + for line in script.script: + self.script.insert(index, line) + index += 1
+ +
[docs] def pop(self, index: int = -1) -> str: + """Removes the line at the given index from the script. + + :param index: The index of the line to remove + :type index: int + :return: The text of the removed line + :rtype: str + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> print(script.pop(1)) + get chats + + .. versionadded:: 0.0.9 + """ + return self.script.pop(index)
+ +
[docs] def load(path: Union['XAPath', str]) -> 'AppleScript': + """Loads an AppleScript (.scpt) file as a runnable AppleScript object. + + :param path: The path of the .scpt file to load + :type path: Union[XAPath, str] + :return: The newly loaded AppleScript object + :rtype: AppleScript + + :Example 1: Load and run a script + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> print(script.run()) + { + 'string': None, + 'int': 0, + 'bool': False, + 'float': 0.0, + 'date': None, + 'file_url': None, + 'type_code': 845507684, + 'data': {length = 8962, bytes = 0x646c6532 00000000 6c697374 000022f2 ... 6e756c6c 00000000 }, + 'event': <NSAppleEventDescriptor: [ 'obj '{ ... } ]> + } + + :Example 2: Load, modify, and run a script + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> script.pop(1) + >>> script.insert(1, "activate") + >>> script.run() + + .. versionadded:: 0.0.8 + """ + if isinstance(path, str): + path = XAPath(path) + script = AppKit.NSAppleScript.alloc().initWithContentsOfURL_error_(path.xa_elem, None)[0] + + attributed_string = script.richTextSource() + attributed_string = str(attributed_string).split("}") + parts = [] + for x in attributed_string: + parts.extend(x.split("{")) + + for x in parts: + if "=" in x: + parts.remove(x) + + script = AppleScript("".join(parts).split("\n")) + script.__file_path = path + return script
+ +
[docs] def save(self, path: Union['XAPath', str, None] = None): + """Saves the script to the specified file path, or to the path from which the script was loaded. + + :param path: The path to save the script at, defaults to None + :type path: Union[XAPath, str, None], optional + + :Example 1: Save the script to a specified path + + >>> import PyXA + >>> script = PyXA.AppleScript(f\"\"\" + >>> tell application "Safari" + >>> activate + >>> end tell + >>> \"\"\") + >>> script.save("/Users/exampleUser/Downloads/Example.scpt") + + :Example 2: Load a script, modify it, then save it + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/steven/Downloads/Example.scpt") + >>> script.insert(2, "delay 2") + >>> script.insert(3, "set the miniaturized of window 1 to true") + >>> script.save() + + .. versionadded:: 0.0.9 + """ + if path is None and self.file_path is None: + print("No path to save script to!") + return + + if isinstance(path, str): + path = XAPath(path) + + script = "" + for line in self.script: + script += line + "\n" + script = AppKit.NSAppleScript.alloc().initWithSource_(script) + script.compileAndReturnError_(None) + source = (script.richTextSource().string()) + + if path is not None: + self.__file_path = path + + with open(self.file_path.xa_elem.path(), "w") as f: + f.write(source)
+ +
[docs] def parse_result_data(result: dict) -> list[tuple[str, str]]: + """Extracts string data from an AppleScript execution result dictionary. + + :param result: The execution result dictionary to extract data from + :type result: dict + :return: A list of responses contained in the result structured as tuples + :rtype: list[tuple[str, str]] + + :Example: + + >>> import PyXA + >>> script = PyXA.AppleScript.load("/Users/exampleUser/Downloads/Test.scpt") + >>> print(script.script) + >>> result = script.run() + >>> print(PyXA.AppleScript.parse_result_data(result)) + ['tell application "Messages"', '\\tget chats', 'end tell'] + [('ID', 'iMessage;-;+12345678910'), ('ID', 'iMessage;-;+12345678911'), ('ID', 'iMessage;-;example@icloud.com'), ...] + + .. versionadded:: 0.0.9 + """ + result = result["event"] + response_objects = [] + num_responses = result.numberOfItems() + for response_index in range(1, num_responses + 1): + response = result.descriptorAtIndex_(response_index) + + data = () + num_params = response.numberOfItems() + if num_params == 0: + data = response.stringValue().strip() + + else: + for param_index in range(1, num_params + 1): + param = response.descriptorAtIndex_(param_index).stringValue() + if param is not None: + data += (param.strip(), ) + response_objects.append(data) + + return response_objects
+ +
[docs] def run(self) -> Any: + """Compiles and runs the script, returning the result. + + :return: The return value of the script. + :rtype: Any + + :Example: + + import PyXA + script = PyXA.AppleScript(f\"\"\"tell application "System Events" + return 1 + 2 + end tell + \"\"\") + print(script.run()) + { + 'string': '3', + 'int': 3, + 'bool': False, + 'float': 3.0, + 'date': None, + 'file_url': None, + 'type_code': 3, + 'data': {length = 4, bytes = 0x03000000}, + 'event': <NSAppleEventDescriptor: 3> + } + + .. versionadded:: 0.0.5 + """ + script = "" + for line in self.script: + script += line + "\n" + script = AppKit.NSAppleScript.alloc().initWithSource_(script) + + result = script.executeAndReturnError_(None)[0] + if result is not None: + self.__last_result = { + "string": result.stringValue(), + "int": result.int32Value(), + "bool": result.booleanValue(), + "float": result.doubleValue(), + "date": result.dateValue(), + "file_url": result.fileURLValue(), + "type_code": result.typeCodeValue(), + "data": result.data(), + "event": result, + } + return self.last_result
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.script) + ">"
+ + + + +######################## +### System Utilities ### +######################## +# XAClipboard, XANotification, XAProcess, XACommandDetector, XASpeechRecognizer, XASpeech, XASpotlight +
[docs]class XAClipboard(XAObject): + """A wrapper class for managing and interacting with the system clipboard. + + .. versionadded:: 0.0.5 + """ + def __init__(self): + self.xa_elem = AppKit.NSPasteboard.generalPasteboard() + self.content #: The content of the clipboard + + @property + def content(self) -> dict[str, list[Any]]: + info_by_type = {} + for item in self.xa_elem.pasteboardItems(): + for item_type in item.types(): + info_by_type[item_type] = { + "data": item.dataForType_(item_type), + "properties": item.propertyListForType_(item_type), + "strings": item.stringForType_(item_type), + } + return info_by_type + + @content.setter + def content(self, value: list[Any]): + if not isinstance(value, list): + value = [value] + self.xa_elem.clearContents() + for index, item in enumerate(value): + if item == None: + value[index] = "" + elif isinstance(item, XAObject): + if not isinstance(item, XAClipboardCodable): + print(item, "is not a clipboard-codable object.") + continue + if isinstance(item.xa_elem, ScriptingBridge.SBElementArray) and item.xa_elem.get() is None: + value[index] = "" + else: + content = item.get_clipboard_representation() + if isinstance(content, list): + value.pop(index) + value += content + else: + value[index] = content + elif isinstance(item, int) or isinstance(item, float): + value[index] = str(item) + self.xa_elem.writeObjects_(value) + +
[docs] def clear(self): + """Clears the system clipboard. + + .. versionadded:: 0.0.5 + """ + self.xa_elem.clearContents()
+ +
[docs] def get_strings(self) -> list[str]: + """Retrieves string type data from the clipboard, if any such data exists. + + :return: The list of strings currently copied to the clipboard + :rtype: list[str] + + .. versionadded:: 0.0.8 + """ + items = [] + for item in self.xa_elem.pasteboardItems(): + string = item.stringForType_(AppKit.NSPasteboardTypeString) + if string is not None: + items.append(string) + return items
+ +
[docs] def get_urls(self) -> list['XAURL']: + """Retrieves URL type data from the clipboard, as instances of :class:`XAURL` and :class:`XAPath`, if any such data exists. + + :return: The list of file URLs and web URLs currently copied to the clipboard + :rtype: list[XAURL] + + .. versionadded:: 0.0.8 + """ + items = [] + for item in self.xa_elem.pasteboardItems(): + url = None + string = item.stringForType_(AppKit.NSPasteboardTypeURL) + if string is None: + string = item.stringForType_(AppKit.NSPasteboardTypeFileURL) + if string is not None: + url = XAPath(XAURL(string).xa_elem) + else: + url = XAURL(string) + + if url is not None: + items.append(url) + return items
+ +
[docs] def get_images(self) -> list['XAImage']: + """Retrieves image type data from the clipboard, as instances of :class:`XAImage`, if any such data exists. :return: The list of images currently copied to the clipboard - :rtype: List[XAImage] + :rtype: list[XAImage] + + .. versionadded:: 0.0.8 + """ + image_types = [AppKit.NSPasteboardTypePNG, AppKit.NSPasteboardTypeTIFF, 'public.jpeg', 'com.apple.icns'] + items = [] + for item in self.xa_elem.pasteboardItems(): + for image_type in image_types: + if image_type in item.types(): + img = XAImage(data = item.dataForType_(image_type)) + items.append(img) + return items
+ +
[docs] def set_contents(self, content: list[Any]): + """Sets the content of the clipboard + + :param content: A list of the content to add fill the clipboard with. + :type content: list[Any] + + .. deprecated:: 0.0.8 + Set the :ivar:`content` property directly instead. + + .. versionadded:: 0.0.5 + """ + self.xa_elem.clearContents() + self.xa_elem.writeObjects_(content)
+ + + + +
[docs]class XANotification(XAObject): + """A class for managing and interacting with notifications. + + .. versionadded:: 0.0.9 + """ + def __init__(self, text: str, title: Union[str, None] = None, subtitle: Union[str, None] = None, sound_name: Union[str, None] = None): + """Initializes a notification object. + + :param text: The main text of the notification + :type text: str + :param title: The title of the notification, defaults to None + :type title: Union[str, None], optional + :param subtitle: The subtitle of the notification, defaults to None + :type subtitle: Union[str, None], optional + :param sound_name: The sound to play when the notification is displayed, defaults to None + :type sound_name: Union[str, None], optional + + .. versionadded:: 0.0.9 + """ + self.text = text + self.title = title + self.subtitle = subtitle + self.sound_name = sound_name + +
[docs] def display(self): + """Displays the notification. + + .. todo:: + + Currently uses :func:`subprocess.Popen`. Should use UserNotifications in the future. + + .. versionadded:: 0.0.9 + """ + script = AppleScript() + script.add(f"display notification \\\"{self.text}\\\"") + + if self.title is not None: + script.add(f"with title \\\"{self.title}\\\"") + + if self.subtitle is not None: + script.add(f"subtitle \\\"{self.subtitle}\\\"") + + if self.sound_name is not None: + script.add(f"sound name \\\"{self.sound_name}\\\"") + + cmd = "osascript -e \"" + " ".join(script.script) + "\"" + subprocess.Popen([cmd], shell=True)
+ + + + +
[docs]class XAProcess(XAObject): + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = properties["window_class"] + + self.front_window: XAWindow #: The front window of the application process + + @property + def front_window(self) -> 'XAWindow': + return self._new_element(self.xa_elem.windows()[0], XAWindow) + +
[docs] def windows(self, filter: dict = None) -> 'XAWindowList': + return self._new_element(self.xa_elem.windows(), XAWindowList, filter)
+ +
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': + return self._new_element(self.xa_elem.menuBars(), XAUIMenuBarList, filter)
+ + + + +
[docs]class XACommandDetector(XAObject): + """A command-based query detector. + + .. versionadded:: 0.0.9 + """ + def __init__(self, command_function_map: Union[dict[str, Callable[[], Any]], None] = None): + """Creates a command detector object. + + :param command_function_map: A dictionary mapping command strings to function objects + :type command_function_map: dict[str, Callable[[], Any]] + + .. versionadded:: 0.0.9 + """ + self.command_function_map = command_function_map or {} #: The dictionary of commands and corresponding functions to run upon detection + +
[docs] def on_detect(self, command: str, function: Callable[[], Any]): + """Adds or replaces a command to listen for upon calling :func:`listen`, and associates the given function with that command. + + :param command: The command to listen for + :type command: str + :param function: The function to call when the command is heard + :type function: Callable[[], Any] + + :Example: + + >>> detector = PyXA.XACommandDetector() + >>> detector.on_detect("go to google", PyXA.XAURL("http://google.com").open) + >>> detector.listen() + + .. versionadded:: 0.0.9 + """ + self.command_function_map[command] = function
+ +
[docs] def listen(self) -> Any: + """Begins listening for the specified commands. + + :return: The execution return value of the corresponding command function + :rtype: Any + + :Example: + + >>> import PyXA + >>> PyXA.speak("What app do you want to open?") + >>> PyXA.XACommandDetector({ + >>> "safari": PyXA.Application("Safari").activate, + >>> "messages": PyXA.Application("Messages").activate, + >>> "shortcuts": PyXA.Application("Shortcuts").activate, + >>> "mail": PyXA.Application("Mail").activate, + >>> "calendar": PyXA.Application("Calendar").activate, + >>> "notes": PyXA.Application("Notes").activate, + >>> "music": PyXA.Application("Music").activate, + >>> "tv": PyXA.Application("TV").activate, + >>> "pages": PyXA.Application("Pages").activate, + >>> "numbers": PyXA.Application("Numbers").activate, + >>> "keynote": PyXA.Application("Keynote").activate, + >>> }).listen() + + .. versionadded:: 0.0.9 + """ + command_function_map = self.command_function_map + return_value = None + class NSSpeechRecognizerDelegate(AppKit.NSObject): + def speechRecognizer_didRecognizeCommand_(self, recognizer, cmd): + return_value = command_function_map[cmd]() + AppHelper.stopEventLoop() + + recognizer = AppKit.NSSpeechRecognizer.alloc().init() + recognizer.setCommands_(list(command_function_map.keys())) + recognizer.setBlocksOtherRecognizers_(True) + recognizer.setDelegate_(NSSpeechRecognizerDelegate.alloc().init().retain()) + recognizer.startListening() + AppHelper.runConsoleEventLoop() + + return return_value
+ + + + +
[docs]class XASpeechRecognizer(XAObject): + """A rule-based query detector. + + .. versionadded:: 0.0.9 + """ + def __init__(self, finish_conditions: Union[None, dict[Callable[[str], bool], Callable[[str], bool]]] = None): + """Creates a speech recognizer object. + + By default, with no other rules specified, the Speech Recognizer will timeout after 10 seconds once :func:`listen` is called. + + :param finish_conditions: A dictionary of rules and associated methods to call when a rule evaluates to true, defaults to None + :type finish_conditions: Union[None, dict[Callable[[str], bool], Callable[[str], bool]]], optional + + .. versionadded:: 0.0.9 + """ + default_conditions = { + lambda x: self.time_elapsed > timedelta(seconds = 10): lambda x: self.spoken_query, + } + self.finish_conditions: Callable[[str], bool] = finish_conditions or default_conditions #: A dictionary of rules and associated methods to call when a rule evaluates to true + self.spoken_query: str = "" #: The recognized spoken input + self.start_time: datetime #: The time that the Speech Recognizer begins listening + self.time_elapsed: timedelta #: The amount of time passed since the start time + + def __prepare(self): + # Request microphone access if we don't already have it + Speech.SFSpeechRecognizer.requestAuthorization_(None) + + # Set up audio session + self.audio_session = AVFoundation.AVAudioSession.sharedInstance() + self.audio_session.setCategory_mode_options_error_(AVFoundation.AVAudioSessionCategoryRecord, AVFoundation.AVAudioSessionModeMeasurement, AVFoundation.AVAudioSessionCategoryOptionDuckOthers, None) + self.audio_session.setActive_withOptions_error_(True, AVFoundation.AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation, None) + + # Set up recognition request + self.recognizer = Speech.SFSpeechRecognizer.alloc().init() + self.recognition_request = Speech.SFSpeechAudioBufferRecognitionRequest.alloc().init() + self.recognition_request.setShouldReportPartialResults_(True) + + # Set up audio engine + self.audio_engine = AVFoundation.AVAudioEngine.alloc().init() + self.input_node = self.audio_engine.inputNode() + recording_format = self.input_node.outputFormatForBus_(0) + self.input_node.installTapOnBus_bufferSize_format_block_(0, 1024, recording_format, + lambda buffer, _when: self.recognition_request.appendAudioPCMBuffer_(buffer)) + self.audio_engine.prepare() + self.audio_engine.startAndReturnError_(None) + +
[docs] def on_detect(self, rule: Callable[[str], bool], method: Callable[[str], bool]): + """Sets the given rule to call the specified method if a spoken query passes the rule. + + :param rule: A function that takes the spoken query as a parameter and returns a boolean value depending on whether the query passes a desired rule + :type rule: Callable[[str], bool] + :param method: A function that takes the spoken query as a parameter and acts on it + :type method: Callable[[str], bool] + + .. versionadded:: 0.0.9 + """ + self.finish_conditions[rule] = method
+ +
[docs] def listen(self) -> Any: + """Begins listening for a query until a rule returns True. + + :return: The value returned by the method invoked upon matching some rule + :rtype: Any + + .. versionadded:: 0.0.9 + """ + self.start_time = datetime.now() + self.time_elapsed = None + self.__prepare() + + old_self = self + def detect_speech(transcription, error): + if error is not None: + print("Failed to detect speech. Error: ", error) + else: + old_self.spoken_query = transcription.bestTranscription().formattedString() + print(old_self.spoken_query) + + recognition_task = self.recognizer.recognitionTaskWithRequest_resultHandler_(self.recognition_request, detect_speech) + while self.spoken_query == "" or not any(x(self.spoken_query) for x in self.finish_conditions): + self.time_elapsed = datetime.now() - self.start_time + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.5)) + + self.audio_engine.stop() + for rule, method in self.finish_conditions.items(): + if rule(self.spoken_query): + return method(self.spoken_query)
+ + + + +
[docs]class XASpeech(XAObject): + def __init__(self, message: str = "", voice: Union[str, None] = None, volume: float = 0.5, rate: int = 200): + self.message: str = message #: The message to speak + self.voice: Union[str, None] = voice #: The voice that the message is spoken in + self.volume: float = volume #: The speaking volume + self.rate: int = rate #: The speaking rate + +
[docs] def voices(self) -> list[str]: + """Gets the list of voice names available on the system. + + :return: The list of voice names + :rtype: list[str] + + :Example: + + >>> import PyXA + >>> speaker = PyXA.XASpeech() + >>> print(speaker.voices()) + ['Agnes', 'Alex', 'Alice', 'Allison', + + .. versionadded:: 0.0.9 + """ + ls = AppKit.NSSpeechSynthesizer.availableVoices() + return [x.replace("com.apple.speech.synthesis.voice.", "").replace(".premium", "").title() for x in ls]
+ +
[docs] def speak(self, path: Union[str, XAPath, None] = None): + """Speaks the provided message using the desired voice, volume, and speaking rate. + + :param path: The path to a .AIFF file to output sound to, defaults to None + :type path: Union[str, XAPath, None], optional + + :Example 1: Speak a message aloud + + >>> import PyXA + >>> PyXA.XASpeech("This is a test").speak() + + :Example 2: Output spoken message to an AIFF file + + >>> import PyXA + >>> speaker = PyXA.XASpeech("Hello, world!") + >>> speaker.speak("/Users/steven/Downloads/Hello.AIFF") + + :Example 3: Control the voice, volume, and speaking rate + + >>> import PyXA + >>> speaker = PyXA.XASpeech( + >>> message = "Hello, world!", + >>> voice = "Alex", + >>> volume = 1, + >>> rate = 500 + >>> ) + >>> speaker.speak() + + .. versionadded:: 0.0.9 + """ + # Get the selected voice by name + voice = None + for v in AppKit.NSSpeechSynthesizer.availableVoices(): + if self.voice.lower() in v.lower(): + voice = v + + # Set up speech synthesis object + synthesizer = AppKit.NSSpeechSynthesizer.alloc().initWithVoice_(voice) + synthesizer.setVolume_(self.volume) + synthesizer.setRate_(self.rate) + + # Start speaking + if path is None: + synthesizer.startSpeakingString_(self.message) + else: + if isinstance(path, str): + path = XAPath(path) + synthesizer.startSpeakingString_toURL_(self.message, path.xa_elem) + + # Wait for speech to complete + while synthesizer.isSpeaking(): + time.sleep(0.01)
+ + + + +
[docs]class XASpotlight(XAObject): + """A Spotlight query for files on the disk. + + .. versionadded:: 0.0.9 + """ + def __init__(self, *query: list[Any]): + self.query: list[Any] = query #: The query terms to search + self.timeout: int = 10 #: The amount of time in seconds to timeout the search after + self.predicate: Union[str, XAPredicate] = None #: The predicate to filter search results by + self.results: list[XAPath] #: The results of the search + self.__results = None + + self.query_object = AppKit.NSMetadataQuery.alloc().init() + nc = AppKit.NSNotificationCenter.defaultCenter() + nc.addObserver_selector_name_object_(self, '_queryNotification:', None, self.query_object) + + @property + def results(self) -> list['XAPath']: + if len(self.query) == 0 and self.predicate is None: + return [] + self.run() + total_time = 0 + while self.__results is None and total_time < self.timeout: + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.01)) + total_time += 0.01 + if self.__results is None: + return [] + return self.__results + +
[docs] def run(self): + """Runs the search. + + :Example: + + >>> import PyXA + >>> from datetime import date, datetime, time + >>> date1 = datetime.combine(date(2022, 5, 17), time(0, 0, 0)) + >>> date2 = datetime.combine(date(2022, 5, 18), time(0, 0, 0)) + >>> search = PyXA.XASpotlight(date1, date2) + >>> print(search.results) + [<<class 'PyXA.XAPath'>file:///Users/exampleUser/Downloads/>, <<class 'PyXA.XAPath'>file:///Users/exampleUser/Downloads/Example.txt>, ...] + + .. versionadded:: 0.0.9 + """ + if self.predicate is not None: + # Search with custom predicate + if isinstance(self.predicate, XAPredicate): + self.predicate = self.predicate.get_clipboard_representation() + self.__search_with_predicate(self.predicate) + elif len(self.query) == 1 and isinstance(self.query[0], datetime): + # Search date + or - 24 hours + self.__search_by_date(self.query) + elif len(self.query) == 2 and isinstance(self.query[0], datetime) and isinstance(self.query[1], datetime): + # Search date range + self.__search_by_date_range(self.query[0], self.query[1]) + elif all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query): + # Search matching multiple strings + self.__search_by_strs(self.query) + elif isinstance(self.query[0], datetime) and all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query[1:]): + # Search by date and string + self.__search_by_date_strings(self.query[0], self.query[1:]) + elif isinstance(self.query[0], datetime) and isinstance(self.query[1], datetime) and all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query[2:]): + # Search by date range and string + self.__search_by_date_range_strings(self.query[0], self.query[1], self.query[2:]) + + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.01))
+ +
[docs] def show_in_finder(self): + """Shows the search in Finder. This might not reveal the same search results. + + .. versionadded:: 0.0.9 + """ + AppKit.NSWorkspace.sharedWorkspace().showSearchResultsForQueryString_(str(self.query))
+ + def __search_by_strs(self, terms: tuple[str]): + expanded_terms = [[x]*3 for x in terms] + expanded_terms = [x for sublist in expanded_terms for x in sublist] + format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) + self.__search_with_predicate(format[:-5], *expanded_terms) + + def __search_by_date(self, date: datetime): + self.__search_with_predicate(f"((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@))", *[date - timedelta(hours=12), date + timedelta(hours=12)]*5) + + def __search_by_date_range(self, date1: datetime, date2: datetime): + self.__search_with_predicate(f"((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@))", *[date1, date2]*5) + + def __search_by_date_strings(self, date: datetime, terms: tuple[str]): + expanded_terms = [[x]*3 for x in terms] + expanded_terms = [x for sublist in expanded_terms for x in sublist] + format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) + format += "(((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@)))" + self.__search_with_predicate(format, *expanded_terms, *[date - timedelta(hours=12), date + timedelta(hours=12)]*5) + + def __search_by_date_range_strings(self, date1: datetime, date2: datetime, terms: tuple[str]): + expanded_terms = [[x]*3 for x in terms] + expanded_terms = [x for sublist in expanded_terms for x in sublist] + format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) + format += "(((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@)))" + self.__search_with_predicate(format, *expanded_terms, *[date1, date2]*5) + + def __search_with_predicate(self, predicate_format: str, *args: list[Any]): + predicate = AppKit.NSPredicate.predicateWithFormat_(predicate_format, *args) + self.query_object.setPredicate_(predicate) + self.query_object.startQuery() + + def _queryNotification_(self, notification): + if notification.name() == AppKit.NSMetadataQueryDidFinishGatheringNotification: + self.query_object.stopQuery() + results = notification.object().results() + self.__results = [XAPath(x.valueForAttribute_(AppKit.NSMetadataItemPathKey)) for x in results]
+ + + + +###################### +### User Interface ### +###################### +
[docs]class XAUIElementList(XAList): + """A wrapper around a list of UI elements. + + All properties of UI elements can be accessed via methods on this list, returning a list of the method's return value for each element in the list. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_type = None): + if obj_type is None: + obj_type = XAUIElement + super().__init__(properties, obj_type, filter) + +
[docs] def properties(self) -> list[dict]: + return list(self.xa_elem.arrayByApplyingSelector_("properties"))
+ +
[docs] def accessibility_description(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("accessibilityDescription"))
+ +
[docs] def enabled(self) -> list[bool]: + return list(self.xa_elem.arrayByApplyingSelector_("enabled"))
+ +
[docs] def entire_contents(self) -> list[list[Any]]: + return list(self.xa_elem.arrayByApplyingSelector_("entireContents"))
+ +
[docs] def focused(self) -> list[bool]: + return list(self.xa_elem.arrayByApplyingSelector_("focused"))
+ +
[docs] def name(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def title(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("title"))
+ +
[docs] def position(self) -> list[tuple[tuple[int, int], tuple[int, int]]]: + return list(self.xa_elem.arrayByApplyingSelector_("position"))
+ +
[docs] def size(self) -> list[tuple[int, int]]: + return list(self.xa_elem.arrayByApplyingSelector_("size"))
+ +
[docs] def maximum_value(self) -> list[Any]: + return list(self.xa_elem.arrayByApplyingSelector_("maximumValue"))
+ +
[docs] def minimum_value(self) -> list[Any]: + return list(self.xa_elem.arrayByApplyingSelector_("minimumValue"))
+ +
[docs] def value(self) -> list[Any]: + return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ +
[docs] def role(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("role"))
+ +
[docs] def role_description(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("roleDescription"))
+ +
[docs] def subrole(self) -> list[str]: + return list(self.xa_elem.arrayByApplyingSelector_("subrole"))
+ +
[docs] def selected(self) -> list[bool]: + return list(self.xa_elem.arrayByApplyingSelector_("selected"))
+ +
[docs] def by_properties(self, properties: dict) -> 'XAUIElement': + return self.by_property("properties", properties)
+ +
[docs] def by_accessibility_description(self, accessibility_description: str) -> 'XAUIElement': + return self.by_property("accessibilityDescription", accessibility_description)
+ +
[docs] def by_entire_contents(self, entire_contents: list[Any]) -> 'XAUIElement': + return self.by_property("entireContents", entire_contents)
+ +
[docs] def by_focused(self, focused: bool) -> 'XAUIElement': + return self.by_property("focused", focused)
+ +
[docs] def by_name(self, name: str) -> 'XAUIElement': + return self.by_property("name", name)
+ +
[docs] def by_title(self, title: str) -> 'XAUIElement': + return self.by_property("title", title)
+ +
[docs] def by_position(self, position: tuple[tuple[int, int], tuple[int, int]]) -> 'XAUIElement': + return self.by_property("position", position)
+ +
[docs] def by_size(self, size: tuple[int, int]) -> 'XAUIElement': + return self.by_property("size", size)
+ +
[docs] def by_maximum_value(self, maximum_value: Any) -> 'XAUIElement': + return self.by_property("maximumValue", maximum_value)
+ +
[docs] def by_minimum_value(self, minimum_value: Any) -> 'XAUIElement': + return self.by_property("minimumValue", minimum_value)
+ +
[docs] def by_value(self, value: Any) -> 'XAUIElement': + return self.by_property("value", value)
+ +
[docs] def by_role(self, role: str) -> 'XAUIElement': + return self.by_property("role", role)
+ +
[docs] def by_role_description(self, role_description: str) -> 'XAUIElement': + return self.by_property("roleDescription", role_description)
+ +
[docs] def by_subrole(self, subrole: str) -> 'XAUIElement': + return self.by_property("subrole", subrole)
+ +
[docs] def by_selected(self, selected: bool) -> 'XAUIElement': + return self.by_property("selected", selected)
+ +
[docs]class XAUIElement(XAObject): + def __init__(self, properties): + super().__init__(properties) + + self.properties: dict #: All properties of the UI element + self.accessibility_description: str #: The accessibility description of the element + self.enabled: bool #: Whether the UI element is currently enabled + self.entire_contents: Any #: The entire contents of the element + self.focused: bool #: Whether the window is the currently element + self.name: str #: The name of the element + self.title: str #: The title of the element (often the same as its name) + self.position: tuple[int, int] #: The position of the top left corner of the element + self.size: tuple[int, int] #: The width and height of the element, in pixels + self.maximum_value: Any #: The maximum value that the element can have + self.minimum_value: Any #: The minimum value that the element can have + self.value: Any #: The current value of the element + self.role: str #: The element's role + self.role_description: str #: The description of the element's role + self.subrole: str #: The subrole of the UI element + self.selected: bool #: Whether the element is currently selected + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def accessibility_description(self) -> str: + return self.xa_elem.accessibilityDescription() + + @property + def enabled(self) -> bool: + return self.xa_elem.enabled() + + @property + def entire_contents(self) -> list[XAObject]: + ls = self.xa_elem.entireContents() + return [self._new_element(x, XAUIElement) for x in ls] + + @property + def focused(self) -> bool: + return self.xa_elem.focused() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def title(self) -> str: + return self.xa_elem.title() + + @property + def position(self) -> tuple[int, int]: + return self.xa_elem.position() + + @property + def size(self) -> tuple[int, int]: + return self.xa_elem.size() + + @property + def maximum_value(self) -> Any: + return self.xa_elem.maximumValue() + + @property + def minimum_value(self) -> Any: + return self.xa_elem.minimumValue() + + @property + def value(self) -> Any: + return self.xa_elem.value() + + @property + def role(self) -> str: + return self.xa_elem.role() + + @property + def role_description(self) -> str: + return self.xa_elem.roleDescription() + + @property + def subrole(self) -> str: + return self.xa_elem.subrole() + + @property + def selected(self) -> bool: + return self.xa_elem.selected() + +
[docs] def ui_elements(self, filter: dict = None) -> 'XAUIElementList': + return self._new_element(self.xa_elem.UIElements(), XAUIElementList, filter)
+ +
[docs] def windows(self, filter: dict = None) -> 'XAWindowList': + return self._new_element(self.xa_elem.windows(), XAWindowList, filter)
+ +
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': + return self._new_element(self.xa_elem.menuBars(), XAUIMenuBarList, filter)
+ +
[docs] def menu_bar_items(self, filter: dict = None) -> 'XAUIMenuBarItemList': + return self._new_element(self.xa_elem.menuBarItems(), XAUIMenuBarItemList, filter)
+ +
[docs] def menus(self, filter: dict = None) -> 'XAUIMenuList': + return self._new_element(self.xa_elem.menus(), XAUIMenuList, filter)
+ +
[docs] def menu_items(self, filter: dict = None) -> 'XAUIMenuItemList': + return self._new_element(self.xa_elem.menuItems(), XAUIMenuItemList, filter)
+ +
[docs] def splitters(self, filter: dict = None) -> 'XAUISplitterList': + return self._new_element(self.xa_elem.splitters(), XAUISplitterList, filter)
+ +
[docs] def toolbars(self, filter: dict = None) -> 'XAUIToolbarList': + return self._new_element(self.xa_elem.toolbars(), XAUIToolbarList, filter)
+ +
[docs] def tab_groups(self, filter: dict = None) -> 'XAUITabGroupList': + return self._new_element(self.xa_elem.tabGroups(), XAUITabGroupList, filter)
+ +
[docs] def scroll_areas(self, filter: dict = None) -> 'XAUIScrollAreaList': + return self._new_element(self.xa_elem.scrollAreas(), XAUIScrollAreaList, filter)
+ +
[docs] def groups(self, filter: dict = None) -> 'XAUIGroupList': + return self._new_element(self.xa_elem.groups(), XAUIGroupList, filter)
+ +
[docs] def buttons(self, filter: dict = None) -> 'XAButtonList': + return self._new_element(self.xa_elem.buttons(), XAButtonList, filter)
+ +
[docs] def radio_buttons(self, filter: dict = None) -> 'XAUIRadioButtonList': + return self._new_element(self.xa_elem.radioButtons(), XAUIRadioButtonList, filter)
+ +
[docs] def actions(self, filter: dict = None) -> 'XAUIActionList': + return self._new_element(self.xa_elem.actions(), XAUIActionList, filter)
+ +
[docs] def text_fields(self, filter: dict = None) -> 'XAUITextfieldList': + return self._new_element(self.xa_elem.textFields(), XAUITextfieldList, filter)
+ +
[docs] def static_texts(self, filter: dict = None) -> 'XAUIStaticTextList': + return self._new_element(self.xa_elem.staticTexts(), XAUIStaticTextList, filter)
+ + + + +
[docs]class XAWindowList(XAUIElementList): + """A wrapper around a list of windows. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAWindow) + +
[docs] def collapse(self): + """Collapses all windows in the list. + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Keychain Access") + >>> app.windows().collapse() + + .. versionadded:: 0.0.5 + """ + for window in self: + window.collapse()
+ +
[docs] def uncollapse(self): + """Uncollapses all windows in the list. + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Keychain Access") + >>> app.windows().uncollapse() + + .. versionadded:: 0.0.6 + """ + for window in self: + window.uncollapse()
+ +
[docs] def close(self): + """Closes all windows in the list.add() + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Keychain Access") + >>> app.windows().close() + + .. versionadded:: 0.0.6 + """ + for window in self: + window.close()
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title()) + ">"
+ +
[docs]class XAWindow(XAUIElement): + """A general window class for windows of both officially scriptable and non-scriptable applications. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def close(self) -> 'XAWindow': + """Collapses (minimizes) the window. + + :return: A reference to the now-collapsed window object. + :rtype: XAWindow + + :Example: + + >>> import PyXA + >>> PyXA.Application("App Store").front_window.close() + + .. versionadded:: 0.0.1 + """ + close_button = self.buttons({"subrole": "AXCloseButton"})[0] + close_button.click() + return self
+ +
[docs] def collapse(self) -> 'XAWindow': + """Collapses (minimizes) the window. + + :return: A reference to the now-collapsed window object. + :rtype: XAWindow + + :Example: + + >>> import PyXA + >>> PyXA.Application("App Store").front_window.collapse() + + .. versionadded:: 0.0.1 + """ + if hasattr(self.xa_elem.properties(), "miniaturized"): + self.xa_elem.setValue_forKey_(True, "miniaturized") + else: + close_button = self.buttons({"subrole": "AXMinimizeButton"})[0] + close_button.click() + return self
+ +
[docs] def uncollapse(self) -> 'XAWindow': + """Uncollapses (unminimizes/expands) the window. + + :return: A reference to the uncollapsed window object. + :rtype: XAWindow + + :Example: + + >>> import PyXA + >>> PyXA.Application("App Store").front_window.uncollapse() + + .. versionadded:: 0.0.1 + """ + ls = self.xa_sevt.applicationProcesses() + dock_process = XAPredicate.evaluate_with_format(ls, "name == 'Dock'")[0] + + ls = dock_process.lists()[0].UIElements() + name = self.name + + app_icon = XAPredicate.evaluate_with_format(ls, f"name == '{name}'")[0] + app_icon.actions()[0].perform() + return self
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title) + ">"
+ + + + +
[docs]class XAUIMenuBarList(XAUIElementList): + """A wrapper around a list of menu bars. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenuBar)
+ +
[docs]class XAUIMenuBar(XAUIElement): + """A menubar UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIMenuBarItemList(XAUIElementList): + """A wrapper around a list of menu bar items. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenuBarItem)
+ +
[docs]class XAUIMenuBarItem(XAUIElement): + """A menubar item UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIMenuList(XAUIElementList): + """A wrapper around a list of menus. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenu)
+ +
[docs]class XAUIMenu(XAUIElement): + """A menu UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIMenuItemList(XAUIElementList): + """A wrapper around a list of menu items. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIMenuItem)
+ +
[docs]class XAUIMenuItem(XAUIElement): + """A menu item UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def click(self) -> 'XAUIMenuItem': + """Clicks the menu item. Synonymous with :func:`press`. + + :return: The menu item object. + :rtype: XAUIMenuItem + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ +
[docs] def press(self) -> 'XAUIMenuItem': + """Clicks the menu item. Synonymous with :func:`click`. + + :return: The menu item object. + :rtype: XAUIMenuItem + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ + + + +
[docs]class XAUISplitterList(XAUIElementList): + """A wrapper around a list of splitters. + + .. versionadded:: 0.0.8 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUISplitter)
+ +
[docs]class XAUISplitter(XAUIElement): + """A splitter UI element. + + .. versionadded:: 0.0.8 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIToolbarList(XAUIElementList): + """A wrapper around a list of toolbars. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIToolbar)
+ +
[docs]class XAUIToolbar(XAUIElement): + """A toolbar UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIGroupList(XAUIElementList): + """A wrapper around a list of UI element groups. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIGroup)
+ +
[docs]class XAUIGroup(XAUIElement): + """A group of UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUITabGroupList(XAUIElementList): + """A wrapper around a list of UI element tab groups. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUITabGroup)
+ +
[docs]class XAUITabGroup(XAUIElement): + """A tab group UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIScrollAreaList(XAUIElementList): + """A wrapper around a list of scroll areas. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIScrollArea)
+ +
[docs]class XAUIScrollArea(XAUIElement): + """A scroll area UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAButtonList(XAUIElementList): + """A wrapper around lists of buttons that employs fast enumeration techniques. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAButton)
+ +
[docs]class XAButton(XAUIElement): + """A button UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def click(self) -> 'XAButton': + """Clicks the button. Synonymous with :func:`press`. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ +
[docs] def press(self) -> 'XAButton': + """Clicks the button. Synonymous with :func:`click`. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXPress"})[0].perform() + return self
+ +
[docs] def option_click(self) -> 'XAButton': + """Option-Clicks the button. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXZoomWindow"})[0].perform() + return self
+ +
[docs] def show_menu(self) -> 'XAButton': + """Right clicks the button, invoking a menu. + + :return: The button object + :rtype: XAButton + + .. versionadded:: 0.0.2 + """ + self.actions({"name": "AXShowMenu"})[0].perform() + return self
+ + + + +
[docs]class XAUIRadioButtonList(XAUIElementList): + """A wrapper around lists of radio buttons that employs fast enumeration techniques. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIRadioButton)
+ +
[docs]class XAUIRadioButton(XAUIElement): + """A radio button UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIActionList(XAUIElementList): + """A wrapper around a list of UI element actions. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIAction)
+ +
[docs]class XAUIAction(XAUIElement): + """An action associated with a UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def perform(self): + """Executes the action. + + .. versionadded:: 0.0.2 + """ + self.xa_elem.perform()
+ + + + +
[docs]class XAUITextfieldList(XAUIElementList): + """A wrapper around a list of textfields. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUITextfield)
+ +
[docs]class XAUITextfield(XAUIElement): + """A textfield UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAUIStaticTextList(XAUIElementList): + """A wrapper around a list of static text elements. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAUIStaticText)
+ +
[docs]class XAUIStaticText(XAUIElement): + """A static text UI element. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties)
+ + +############ +### Text ### +############ +
[docs]class XATextDocumentList(XAList, XAClipboardCodable): + """A wrapper around lists of text documents that employs fast enumeration techniques. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XATextDocument + super().__init__(properties, obj_class, filter) + +
[docs] def properties(self) -> list[dict]: + """Gets the properties of each document in the list. + + :return: A list of document properties dictionaries + :rtype: list[dict] + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("properties") + return [dict(x) for x in ls]
+ +
[docs] def text(self) -> 'XATextList': + """Gets the text of each document in the list. + + :return: A list of document texts + :rtype: XATextList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("text") + return self._new_element(ls, XATextList)
+ +
[docs] def by_properties(self, properties: dict) -> Union['XATextDocument', None]: + """Retrieves the document whose properties match the given properties dictionary, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XATextDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("properties", properties)
+ +
[docs] def by_text(self, text: str) -> Union['XATextDocument', None]: + """Retrieves the first documents whose text matches the given text. + + :return: The desired document, if it is found + :rtype: Union[XATextDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("text", text)
+ +
[docs] def paragraphs(self) -> 'XAParagraphList': + """Gets the paragraphs of each document in the list. + + :return: A combined list of all paragraphs in each document of the list + :rtype: XAParagraphList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("paragraphs") + return self._new_element([plist for plist in ls], XAParagraphList)
+ +
[docs] def words(self) -> 'XAWordList': + """Gets the words of each document in the list. + + :return: A combined list of all words in each document of the list + :rtype: XAWordList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("words") + return [self._new_element([plist for plist in ls], XAWordList)]
+ +
[docs] def characters(self) -> 'XACharacterList': + """Gets the characters of each document in the list. + + :return: A combined list of all characters in each document of the list + :rtype: XACharacterList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("characters") + return [self._new_element([plist for plist in ls], XACharacterList)]
+ +
[docs] def attribute_runs(self) -> 'XAAttributeRunList': + """Gets the attribute runs of each document in the list. + + :return: A combined list of all attribute runs in each document of the list + :rtype: XAAttributeRunList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("attributeRuns") + return [self._new_element([plist for plist in ls], XAAttributeRunList)]
+ +
[docs] def attachments(self) -> 'XAAttachmentList': + """Gets the attachments of each document in the list. + + :return: A combined list of all attachments in each document of the list + :rtype: XAAttachmentList + + .. versionadded:: 0.0.3 + """ + ls = self.xa_elem.arrayByApplyingSelector_("attachments") + return [self._new_element([plist for plist in ls], XAAttachmentList)]
+ +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of each document in the list. + + When the clipboard content is set to a list of documents, each documents's file URL and name are added to the clipboard. + + :return: A list of each document's file URL and name + :rtype: list[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + return [str(x) for x in self.text()]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.text()) + ">"
+ +
[docs]class XATextDocument(XAObject): + """A class for managing and interacting with text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties) + self.text: XAText #: The text of the document. + + @property + def text(self) -> 'XAText': + return self._new_element(self.xa_elem.text(), XAText) + + @text.setter + def text(self, text: str): + self.set_property("text", text) + +
[docs] def prepend(self, text: str) -> 'XATextDocument': + """Inserts the provided text at the beginning of the document. + + :param text: The text to insert. + :type text: str + :return: A reference to the document object. + :rtype: XATextDocument + + .. seealso:: :func:`append`, :func:`set_text` + + .. versionadded:: 0.0.1 + """ + old_text = str(self.text) + self.set_property("text", text + old_text) + return self
+ +
[docs] def append(self, text: str) -> 'XATextDocument': + """Appends the provided text to the end of the document. + + :param text: The text to append. + :type text: str + :return: A reference to the document object. + :rtype: XATextDocument + + .. seealso:: :func:`prepend`, :func:`set_text` + + .. versionadded:: 0.0.1 + """ + old_text = str(self.text) + self.set_property("text", old_text + text) + return self
+ +
[docs] def reverse(self) -> 'XATextDocument': + """Reverses the text of the document. + + :return: A reference to the document object. + :rtype: XATextDocument + + .. versionadded:: 0.0.4 + """ + self.set_property("text", reversed(str(self.text))) + return self
+ +
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': + return self.text.paragraphs(filter)
+ +
[docs] def sentences(self, filter: dict = None) -> 'XASentenceList': + return self.text.sentences(filter)
+ +
[docs] def words(self, filter: dict = None) -> 'XAWordList': + return self.text.words(filter)
+ +
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': + return self.text.characters(filter)
+ +
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': + return self.text.attribute_runs(filter)
+ +
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': + return self.text.attachments(filter)
+ + + + +
[docs]class XATextList(XAList): + """A wrapper around lists of text objects that employs fast enumeration techniques. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XAText + super().__init__(properties, obj_class, filter) + +
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': + """Gets the paragraphs of every text item in the list. + + :return: The list of paragraphs + :rtype: XAParagraphList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("paragraphs") + else: + ls = [x.xa_elem.split("\n") for x in self] + ls = [paragraph for paragraphlist in ls for paragraph in paragraphlist if paragraph.strip() != ''] + return self._new_element(ls, XAParagraphList, filter)
+ +
[docs] def sentences(self) -> 'XASentenceList': + """Gets the sentences of every text item in the list. + + :return: The list of sentences + :rtype: XASentenceList + + .. versionadded:: 0.1.0 + """ + ls = [x.sentences() for x in self] + ls = [sentence for sentencelist in ls for sentence in sentencelist] + return self._new_element(ls, XASentenceList)
+ +
[docs] def words(self, filter: dict = None) -> 'XAWordList': + """Gets the words of every text item in the list. + + :return: The list of words + :rtype: XAWordList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("words") + else: + ls = [x.xa_elem.split() for x in self] + ls = [word for wordlist in ls for word in wordlist] + return self._new_element(ls, XAWordList, filter)
+ +
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': + """Gets the characters of every text item in the list. + + :return: The list of characters + :rtype: XACharacterList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("characters") + else: + ls = [list(x.xa_elem) for x in self] + ls = [character for characterlist in ls for character in characterlist] + return self._new_element(ls, XACharacterList, filter)
+ +
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': + """Gets the attribute runs of every text item in the list. + + :return: The list of attribute runs + :rtype: XAAttributeRunList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("attributeRuns") + ls = [attribute_run for attribute_run_list in ls for attribute_run in attribute_run_list] + return self._new_element(ls, XAAttributeRunList, filter)
+ +
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': + """Gets the attachments of every text item in the list. + + :return: The list of attachments + :rtype: XAAttachmentList + + .. versionadded:: 0.0.1 + """ + ls = [] + if hasattr(self.xa_elem, "get"): + ls = self.xa_elem.arrayByApplyingSelector_("attachments") + ls = [attachment for attachment_list in ls for attachment in attachment_list] + return self._new_element(ls, XAAttachmentList, filter)
+ + def __repr__(self): + try: + if isinstance(self.xa_elem[0], ScriptingBridge.SBObject): + # List items will not resolved to text upon dereferencing the list; need to resolve items individually + count = self.xa_elem.count() + if count <= 500: + # Too many unresolved pointers, save time by just reporting the length + return "<" + str(type(self)) + str([x.get() for x in self.xa_elem]) + ">" + return "<" + str(type(self)) + "length: " + str(self.xa_elem.count()) + ">" + + # List items will resolve to text upon dereferencing the list + return "<" + str(type(self)) + str(self.xa_elem.get()) + ">" + except: + return "<" + str(type(self)) + str(list(self.xa_elem)) + ">"
+ +
[docs]class XAText(XAObject): + """A class for managing and interacting with the text of documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + if isinstance(properties, dict): + super().__init__(properties) + elif isinstance(properties, str): + super().__init__({"element": properties}) + + self.text: str #: The plaintext contents of the rich text + self.color: XAColor #: The color of the first character + self.font: str #: The name of the font of the first character + self.size: int #: The size in points of the first character + + @property + def text(self) -> str: + if isinstance(self.xa_elem, str): + return self.xa_elem + else: + return self.xa_elem.text() + + @text.setter + def text(self, text: str): + if isinstance(self.xa_elem, str): + self.xa_elem = text + else: + self.set_property("text", text) + + @property + def color(self) -> 'XAColor': + if isinstance(self.xa_elem, str): + return None + else: + return XAColor(self.xa_elem.color()) + + @color.setter + def color(self, color: 'XAColor'): + if isinstance(self.xa_elem, str): + self.color = color.xa_elem + else: + self.set_property("color", color.xa_elem) + + @property + def font(self) -> str: + if isinstance(self.xa_elem, str): + return None + else: + return self.xa_elem.font() + + @font.setter + def font(self, font: str): + if isinstance(self.xa_elem, str): + self.font = font + else: + self.set_property("font", font) + + @property + def size(self) -> int: + if isinstance(self.xa_elem, str): + return 0 + else: + return self.xa_elem.size() + + @size.setter + def size(self, size: int): + if isinstance(self.xa_elem, str): + self.size = size + else: + self.set_property("size", size) + + # def spelling_suggestions(self): + # suggestions = [] + # text = str(self.xa_elem) + # spellchecker = AppKit.NSSpellChecker.sharedSpellChecker() + + # orthography = None + # word_count = 0 + + # pprint(dir(LatentSemanticMapping.LSMMapCreate(None, 0))) + + # # c = spellchecker.checkString_range_types_options_inSpellDocumentWithTag_orthography_wordCount_(text, (0, len(text)), AppKit.NSTextCheckingTypeSpelling | AppKit.NSTextCheckingTypeGrammar | AppKit.NSTextCheckingTypeCorrection, {}, 0, orthography, None) + # # print(c[1].languageMap()) + + # for word in text.split(): + # completions = spellchecker.completionsForPartialWordRange_inString_language_inSpellDocumentWithTag_((0, len(word)), word, "", 0) + # suggestions.append(completions) + + # # for word in text.split(): + # # guesses = spellchecker.guessesForWordRange_inString_language_inSpellDocumentWithTag_((0, len(word)), word, "", 0) + # # suggestions.append(guesses) + # return suggestions + +
[docs] def tag_parts_of_speech(self, unit: Literal["word", "sentence", "paragraph", "document"] = "word") -> list[tuple[str, str]]: + """Tags each word of the text with its associated part of speech. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "word" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each word of the text and its part of speech + :rtype: list[tuple[str, str]] + + :Example 1: Extract nouns from a text + + >>> import PyXA + >>> text = PyXA.XAText("Here’s to the crazy ones. The misfits. The rebels.") + >>> nouns = [pos[0] for pos in text.tag_parts_of_speech() if pos[1] == "Noun"] + >>> print(nouns) + ['ones', 'misfits', 'rebels'] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeLexicalClass]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_pos = [] + def apply_tags(tag, token_range, error): + word_phrase = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + tagged_pos.append((word_phrase, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeLexicalClass, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace, apply_tags) + return tagged_pos
+ +
[docs] def tag_languages(self, unit: Literal["word", "sentence", "paragraph", "document"] = "paragraph") -> list[tuple[str, str]]: + """Tags each paragraph of the text with its language. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "paragraph" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each paragraph of the text and its language + :rtype: list[tuple[str, str]] + + :Example: + + >>> import PyXA + >>> text = PyXA.XAText("This is English.\nQuesto è Italiano.\nDas ist deutsch.\nこれは日本語です。") + >>> print(text.tag_languages()) + [('This is English.\n', 'en'), ('Questo è Italiano.\n', 'it'), ('Das ist deutsch.\n', 'de'), ('これは日本語です。', 'ja')] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeLanguage]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_languages = [] + def apply_tags(tag, token_range, error): + paragraph = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if paragraph.strip() != "": + tagged_languages.append((paragraph, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeLanguage, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace, apply_tags) + return tagged_languages
+ +
[docs] def tag_entities(self, unit: Literal["word", "sentence", "paragraph", "document"] = "word") -> list[tuple[str, str]]: + """Tags each word of the text with either the category of entity it represents (i.e. person, place, or organization) or its part of speech. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "word" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each word of the text and its entity category or part of speech + :rtype: list[tuple[str, str]] + + :Example: + + >>> import PyXA + >>> text = PyXA.XAText("Tim Cook is the CEO of Apple.") + >>> print(text.tag_entities()) + [('Tim', 'PersonalName'), ('Cook', 'PersonalName'), ('is', 'Verb'), ('the', 'Determiner'), ('CEO', 'Noun'), ('of', 'Preposition'), ('Apple', 'OrganizationName')] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeNameTypeOrLexicalClass]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_languages = [] + def apply_tags(tag, token_range, error): + word_phrase = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if word_phrase.strip() != "": + tagged_languages.append((word_phrase, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeNameTypeOrLexicalClass, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace, apply_tags) + return tagged_languages
+ +
[docs] def tag_lemmas(self, unit: Literal["word", "sentence", "paragraph", "document"] = "word") -> list[tuple[str, str]]: + """Tags each word of the text with its stem word. + + :param unit: The grammatical unit to divide the text into for tagging, defaults to "word" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each word of the text and its stem words + :rtype: list[tuple[str, str]] + + :Example 1: Lemmatize each word in a text + + >>> import PyXA + >>> text = PyXA.XAText("Here’s to the crazy ones. The misfits. The rebels.") + >>> print(text.tag_lemmas()) + [('Here’s', 'here'), ('to', 'to'), ('the', 'the'), ('crazy', 'crazy'), ('ones', 'one'), ('The', 'the'), ('misfits', 'misfit'), ('The', 'the'), ('rebels', 'rebel')] + + :Example 2: Combine parts of speech tagging and lemmatization + + >>> import PyXA + >>> text = PyXA.XAText("The quick brown fox tries to jump over the sleeping lazy dog.") + >>> verbs = [pos[0] for pos in text.tag_parts_of_speech() if pos[1] == "Verb"] + >>> for index, verb in enumerate(verbs): + >>> print(index, PyXA.XAText(verb).tag_lemmas()) + 0 [('tries', 'try')] + 1 [('jump', 'jump')] + 2 [('sleeping', 'sleep')] + + .. versionadded:: 0.1.0 + """ + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeLemma]) + tagger.setString_(str(self.xa_elem)) + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagged_lemmas = [] + def apply_tags(tag, token_range, error): + word_phrase = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if word_phrase.strip() != "": + tagged_lemmas.append((word_phrase, tag)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(str(self.xa_elem))), unit, NaturalLanguage.NLTagSchemeLemma, NaturalLanguage.NLTaggerOmitPunctuation | NaturalLanguage.NLTaggerOmitWhitespace | NaturalLanguage.NLTaggerJoinContractions, apply_tags) + return tagged_lemmas
+ +
[docs] def tag_sentiments(self, sentiment_scale: list[str] = None, unit: Literal["word", "sentence", "paragraph", "document"] = "paragraph") -> list[tuple[str, str]]: + """Tags each paragraph of the text with a sentiment rating. + + :param sentiment_scale: A list of terms establishing a range of sentiments from most negative to most postive + :type sentiment_scale: list[str] + :param unit: The grammatical unit to divide the text into for tagging, defaults to "paragraph" + :type unit: Literal["word", "sentence", "paragraph", "document"] + :return: A list of tuples identifying each paragraph of the text and its sentiment rating + :rtype: list[tuple[str, str]] + + :Example 1: Assess the sentiment of a string + + >>> import PyXA + >>> text = PyXA.XAText("This sucks.\nBut this is great!") + >>> print(text.tag_sentiments()) + [('This sucks.\n', 'Negative'), ('But this is great!', 'Positive')] + + :Example 2: Use a custom sentiment scale + + >>> import PyXA + >>> text = PyXA.XAText("This sucks.\nBut this is good!\nAnd this is great!") + >>> print(text.tag_sentiments(sentiment_scale=["Very Negative", "Negative", "Somewhat Negative", "Neutral", "Somewhat Positive", "Positive", "Very Positive"])) + [('This sucks.\n', 'Very Negative'), ('But this is good!\n', 'Neutral'), ('And this is great!', 'Very Positive')] + + :Example 3: Use other tag units + + >>> import PyXA + >>> text = PyXA.XAText("This sucks.\nBut this is good!\nAnd this is great!") + >>> print(1, text.tag_sentiments()) + >>> print(2, text.tag_sentiments(unit="word")) + >>> print(3, text.tag_sentiments(unit="document")) + 1 [('This sucks.\n', 'Negative'), ('But this is good!\n', 'Neutral'), ('And this is great!', 'Positive')] + 2 [('This', 'Negative'), ('sucks', 'Negative'), ('.', 'Negative'), ('But', 'Neutral'), ('this', 'Neutral'), ('is', 'Neutral'), ('good', 'Neutral'), ('!', 'Neutral'), ('And', 'Positive'), ('this', 'Positive'), ('is', 'Positive'), ('great', 'Positive'), ('!', 'Positive')] + 3 [('This sucks.\nBut this is good!\nAnd this is great!', 'Neutral')] + + .. versionadded:: 0.1.0 + """ + if sentiment_scale is None or len(sentiment_scale) == 0: + sentiment_scale = ["Negative", "Neutral", "Positive"] + + if unit == "word": + unit = NaturalLanguage.NLTokenUnitWord + elif unit == "sentence": + unit = NaturalLanguage.NLTokenUnitSentence + elif unit == "paragraph": + unit = NaturalLanguage.NLTokenUnitParagraph + elif unit == "document": + unit = NaturalLanguage.NLTokenUnitDocument + + tagger = NaturalLanguage.NLTagger.alloc().initWithTagSchemes_([NaturalLanguage.NLTagSchemeSentimentScore]) + tagger.setString_(str(self.xa_elem)) + + tagged_sentiments = [] + def apply_tags(tag, token_range, error): + paragraph = str(self.xa_elem)[token_range.location:token_range.location + token_range.length] + if paragraph.strip() != "": + # Map raw tag value to range length + raw_value = float(tag or 0) + scaled = (raw_value + 1.0) / 2.0 * (len(sentiment_scale) - 1) + + label = sentiment_scale[int(scaled)] + tagged_sentiments.append((paragraph, label)) + + tagger.enumerateTagsInRange_unit_scheme_options_usingBlock_((0, len(self.xa_elem)), unit, NaturalLanguage.NLTagSchemeSentimentScore, 0, apply_tags) + return tagged_sentiments
+ +
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': + """Gets a list of paragraphs in the text. + + :param filter: The properties and associated values to filter paragraphs by, defaults to None + :type filter: dict, optional + :return: The list of paragraphs + :rtype: XAParagraphList + + :Example 1: Get paragraphs of a text string + + >>> import PyXA + >>> string = \"\"\"This is the first paragraph. + >>> + >>> This is the second paragraph.\"\"\" + >>> text = PyXA.XAText(string) + >>> print(text.paragraphs()) + <<class 'PyXA.XAWordList'>['This is the first paragraph.', 'This is the second paragraph. Neat! Very cool.']> + + :Example 2: Get paragraphs of a Note + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.paragraphs()) + <<class 'PyXA.XAWordList'>['This is the first paragraph.', 'This is the second paragraph. Neat! Very cool.']> + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + ls = [x for x in self.xa_elem.split("\n") if x.strip() != ''] + return self._new_element(ls, XAWordList, filter) + else: + return self._new_element(self.xa_elem.paragraphs(), XAParagraphList, filter)
+ +
[docs] def sentences(self) -> 'XASentenceList': + """Gets a list of sentences in the text. + + :return: The list of sentencnes + :rtype: XASentenceList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.sentences()) + <<class 'PyXA.XASentenceList'>['This is the first paragraph.\\n', '\\n', 'This is the second paragraph. ', 'Neat! ', 'Very cool.']> + + .. versionadded:: 0.1.0 + """ + raw_string = self.xa_elem + if hasattr(self.xa_elem, "get"): + raw_string = self.xa_elem.get() + + sentences = [] + tokenizer = AppKit.NLTokenizer.alloc().initWithUnit_(AppKit.kCFStringTokenizerUnitSentence) + tokenizer.setString_(raw_string) + for char_range in tokenizer.tokensForRange_((0, len(raw_string))): + start = char_range.rangeValue().location + end = start + char_range.rangeValue().length + sentences.append(raw_string[start:end]) + + ls = AppKit.NSArray.alloc().initWithArray_(sentences) + return self._new_element(sentences, XASentenceList)
+ +
[docs] def words(self, filter: dict = None) -> 'XAWordList': + """Gets a list of words in the text. + + :return: The list of words + :rtype: XAWordList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.words()) + <<class 'PyXA.XAWordList'>['This', 'is', 'the', 'first', 'paragraph.', 'This', 'is', 'the', 'second', 'paragraph.', 'Neat!', 'Very', 'cool.']> + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + ls = self.xa_elem.split() + return self._new_element(ls, XAWordList, filter) + else: + return self._new_element(self.xa_elem.words(), XAWordList, filter)
+ +
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': + """Gets a list of characters in the text. + + :return: The list of characters + :rtype: XACharacterList + + :Example 1: Get all characters in a text + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.characters()) + <<class 'PyXA.XACharacterList'>['T', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 'f', 'i', 'r', 's', 't', ' ', 'p', 'a', 'r', 'a', 'g', 'r', 'a', 'p', 'h', '.', '\n', '\n', 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 't', 'h', 'e', ' ', 's', 'e', 'c', 'o', 'n', 'd', ' ', 'p', 'a', 'r', 'a', 'g', 'r', 'a', 'p', 'h', '.', ' ', 'N', 'e', 'a', 't', '!', ' ', 'V', 'e', 'r', 'y', ' ', 'c', 'o', 'o', 'l', '.']> + + :Example 2: Get the characters of the first word in a text + + >>> import PyXA + >>> app = PyXA.Application("Notes") + >>> note = app.notes()[0] + >>> text = PyXA.XAText(note.plaintext) + >>> print(text.words()[0].characters()) + <<class 'PyXA.XACharacterList'>['T', 'h', 'i', 's']> + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + ls = list(self.xa_elem) + return self._new_element(ls, XACharacterList, filter) + else: + return self._new_element(self.xa_elem.characters().get(), XACharacterList, filter)
+ +
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': + """Gets a list of attribute runs in the text. For formatted text, this returns all sequences of characters sharing the same attributes. + + :param filter: The properties and associated values to filter attribute runs by, defaults to None + :type filter: dict, optional + :return: The list of attribute runs + :rtype: XAAttributeRunList + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + return [] + else: + return self._new_element(self.xa_elem.attributeRuns(), XAAttributeRunList, filter)
+ +
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': + """Gets a list of attachments of the text. + + :param filter: The properties and associated values to filter attachments by, defaults to None + :type filter: dict, optional + :return: The list of attachments + :rtype: XAAttachmentList + + .. versionadded:: 0.0.1 + """ + if isinstance(self.xa_elem, str): + return [] + else: + return self._new_element(self.xa_elem.attachments(), XAAttachmentList, filter)
+ + def __len__(self): + return len(self.xa_elem.get()) + + def __str__(self): + if isinstance(self.xa_elem, str): + return self.xa_elem + return str(self.xa_elem.get()) + + def __repr__(self): + if isinstance(self.xa_elem, str): + return self.xa_elem + return str(self.xa_elem.get())
+ + + + +
[docs]class XAParagraphList(XATextList): + """A wrapper around lists of paragraphs that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAParagraph)
+ +
[docs]class XAParagraph(XAText): + """A class for managing and interacting with paragraphs in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + +
[docs]class XASentenceList(XATextList): + """A wrapper around lists of sentences that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XASentence)
+ +
[docs]class XASentence(XAText): + """A class for managing and interacting with sentences in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAWordList(XATextList): + """A wrapper around lists of words that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAWord)
+ +
[docs]class XAWord(XAText): + """A class for managing and interacting with words in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XACharacterList(XATextList): + """A wrapper around lists of characters that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XACharacter)
+ +
[docs]class XACharacter(XAText): + """A class for managing and interacting with characters in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAAttributeRunList(XATextList): + """A wrapper around lists of attribute runs that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAAttributeRun)
+ +
[docs]class XAAttributeRun(XAText): + """A class for managing and interacting with attribute runs in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAAttachmentList(XATextList): + """A wrapper around lists of text attachments that employs fast enumeration techniques. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAAttachment)
+ +
[docs]class XAAttachment(XAObject): + """A class for managing and interacting with attachments in text documents. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XALSM(XAObject): + def __init__(self, dataset: Union[dict[str, list[str]], None] = None, from_file: bool = False): + """Initializes a Latent Semantic Mapping environment. + + :param dataset: The initial dataset, specified as a dictionary where keys are categories and values are list of corresponding texts, defaults to None. Cannot be None if from_file is False. + :type dataset: Union[dict[str, list[str]], None], optional + :param from_file: Whether the LSM is being loaded from a file, defaults to False. Cannot be False is dataset is None. + :type from_file: bool, optional + :raises ValueError: Either you must provide a dataset, or you must load an existing map from an external file + + :Example 1: Classify emails based on subject line + + >>> import PyXA + >>> lsm = PyXA.XALSM({ + >>> # 1 + >>> "spam": ["Deals", "Holiday playbook", "Spend and save. You know the drill.", "Don't miss these holiday deals!", "GOOD NEWS", "you will never have an opportunity of this kind again", "Your Long Overdue Compensation Funds; Totally", "US $25,000,000.00", "goog day", "GOOD DAY, I am Mike Paul I have a", "enron methanol; meter # : 988291 this is a follow up to the note i gave you on monday , 4...", "hpl nom for january 9, see attached file : hplnol 09. xls", "neon retreat ho ho ho, we're around to that most wonderful time of the year", "photoshop, windows, office cheap, main trending abasements, darer prudently fortuitous", "re: indian springs this deal is to book the teco pvr revenue. it is my understanding that...", "noms / actual flow for 2 / we agree", "nominations for oct 21 - 23"], + >>> + >>> # 2 + >>> "kayak": ["Price Alert: Airfare holding steady for your trip", "Prices going down for your Boston to Dublin flight", "Price Increase: Boston to Dublin airfare up $184", "Flight Alert: #37 decrease on your flight to Dublin.", "Flight Alert: It's time to book your flight to Dublin", "Price Alert: Airfare holding steady for your Bangor, ME to...", "Ready to explore the world again?"], + >>> + >>> # 3 + >>> "lenovo": ["Doorbuster deals up to 70% off", "Visionary, On-Demand Content. Lenovo Tech World '22 is starting", "Up to 70% off deals 9 AM", "TGIF! Here's up to 70% off to jumpstart your weekend", "Top picks to refresh your workspace", "This only happens twice a year", "Think about saving on a Think PC", "Deep deals on Summer Clearance", "Save up to 67% + earn rewards", "Unlock up to 61% off Think PCs", "Giveaway alert!", "Annual Sale Sneak Peak Unlocked!"], + >>> + >>> # 4 + >>> "linkedin": ["Here is the latest post trending amongst your coworkers", "Stephen, add Sean Brown to your network", "Share thoughts on LinkedIn", "Top companies are hiring", "Linkedin is better on the app", "Here is the latest post trending amongst your coworkers", "Stephen, add Ronald McDonald to your network", "James Smith shared a post for the first time in a while", "Here is the latest post trending amongst your coworkers", "You appeared in 13 searches this week", "you're on a roll with your career!", "You appeared in 16 searches this week", "18 people notices you", "You appeared in 10 searches this week", "Stephen, add Joe Shmoe to your network", "Your network is talking: The Center for Oceanic Research...", "thanks for being a valued member"] + >>> }) + >>> print(lsm.categorize_query("New! Weekend-only deals")) + >>> print(lsm.categorize_query("Stephen, redeem these three (3) unlocked courses")) + >>> print(lsm.categorize_query("Round Trip From San Francisco to Atlanta")) + [(3, 0.9418474435806274)] + [(4, 0.9366401433944702)] + [(2, 0.9944692850112915)] + + :Example 2: Use the Mail module to automate dataset construction + + >>> import PyXA + >>> app = PyXA.Application("Mail") + >>> junk_subject_lines = app.accounts()[0].mailboxes().by_name("Junk").messages().subject() + >>> other_subject_lines = app.accounts()[0].mailboxes().by_name("INBOX").messages().subject() + >>> + >>> dataset = { + >>> "junk": junk_subject_lines, + >>> "other": other_subject_lines + >>> } + >>> lsm = PyXA.XALSM(dataset) + >>> + >>> query = "Amazon Web Services Billing Statement Available" + >>> category = list(dataset.keys())[lsm.categorize_query(query)[0][0] - 1] + >>> print(query, "- category:", category) + >>> + >>> query = "Complete registration form asap receive your rewards" + >>> category = list(dataset.keys())[lsm.categorize_query(query)[0][0] - 1] + >>> print(query, "- category:", category) + Amazon Web Services Billing Statement Available - category: other + Complete registration form asap receive your rewards - category: junk + + .. versionadded:: 0.1.0 + """ + self.__categories = {} + if dataset is None and not from_file: + raise ValueError("You must either load a map from an external file or provide an initial dataset.") + elif dataset is None: + # Map will be loaded from external file -- empty dataset is temporary + self.__dataset = {} + else: + # Create new map + self.__dataset = dataset + + self.map = LatentSemanticMapping.LSMMapCreate(None, 0) + LatentSemanticMapping.LSMMapStartTraining(self.map) + LatentSemanticMapping.LSMMapSetProperties(self.map, { + LatentSemanticMapping.kLSMSweepCutoffKey: 0, + # LatentSemanticMapping.kLSMPrecisionKey: LatentSemanticMapping.kLSMPrecisionDouble, + LatentSemanticMapping.kLSMAlgorithmKey: LatentSemanticMapping.kLSMAlgorithmSparse, + }) + + for category in dataset: + self.__add_category(category) + + LatentSemanticMapping.LSMMapCompile(self.map) + + def __add_category(self, category: str) -> int: + loc = Foundation.CFLocaleGetSystem() + category_ref = LatentSemanticMapping.LSMMapAddCategory(self.map) + self.__categories[category] = category_ref + self.__categories[category_ref] = category_ref + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, " ".join(self.__dataset[category]), loc, LatentSemanticMapping.kLSMTextPreserveAcronyms) + LatentSemanticMapping.LSMMapAddText(self.map, text_ref, category_ref) + return category_ref + +
[docs] def save(self, file_path: Union[XAPath, str]) -> bool: + """Saves the map to an external file. + + :param file_path: The path to save the map at + :type file_path: Union[XAPath, str] + :return: True if the map was saved successfully + :rtype: bool + + :Example: Create a Reddit post classifier for gaming vs. productivity posts + + >>> import PyXA + >>> lsm = PyXA.XALSM({ + >>> # 1 + >>> "gaming": ["If you could vote on the 2017 Mob Vote again, which mob would you choose this time and why?", "Take your time, you got this", "My parents (late 70s) got me a ps5 controller for Christmas. I do not own a playstation 5...", "I got off the horse by accident right before a cutscene in red dead", "boy gamer", "Minesweeper 99 x 99, 1500 mines. Took me about 2.5 hours to finish, nerve-wracking. No one might care, but just wanted to share this.", "The perfect cosplay doesn’t ex...", "'Play until we lose'", "Can we please boycott Star Wars battlefront 2", "EA removed the refund button on their webpage, and now you have to call them and wait to get a refund.", "Train Simulator is so immersive!", "Been gaming with this dude for 15 years. Since Rainbow Six Vegas on 360. I have some good gaming memories with him. He tried but couldn’t get one. Little did he know I was able to get him one. Looking forward to playing another generation with him.", "EA will no longer have exclusive rights of the Star Wars games", "A ziplining contraption I created with 1000+ command blocks", "The steepest walkable staircase possible in 1.16", "I made a texture pack that gives mobs different facial expressions. Should I keep going?"], + >>> + >>> # 2 + >>> "productivity": ["Looking for an alarm app that plays a really small alarm, doesn’t need to be switched off and doesn’t repeat.", "I want to build a second brain but I'm lost and don't know where to start.", "noise cancelling earplugs", "I have so much to do but I don't know where to start", "How to handle stressful work calls", "time tracking app/platform", "We just need to find ways to cope and keep moving forward.", "Ikigai - A Reason for Being", "Minimalist Productivity Tip: create two users on your computer ➞ One for normal use and leisure ➞ One for business/work only. I have nothing except the essentials logged in on my work user. Not even Messages or YouTube. It completely revolutionized my productivity 💸", "Trick yourself into productivity the same way you trick yourself into procrastination", "I spent 40 hours sifting through research papers to fix my mental clarity, focus, and productivity - I ended up going down a rabbit hole and figuring out it was all tied to sleep, even though I felt I sleep well - here's what I found.", "The Cycle of Procrastination. Always a good reminder", "'Most people underestimate what they can do in a year, and overestimate what they can do in a day' - When you work on getting 1% better each day you won't even recognize yourself in a year."], + >>> }) + >>> lsm.save("/Users/steven/Downloads/gaming-productivity.map") + + .. versionadded:: 0.1.0 + """ + if isinstance(file_path, str): + file_path = XAPath(file_path) + + status = LatentSemanticMapping.LSMMapWriteToURL(self.map, file_path.xa_elem, 0) + if status == 0: + return True + return False
+ +
[docs] def load(file_path: Union[XAPath, str]) -> 'XALSM': + """Loads a map from an external file. + + :param file_path: The file path for load the map from + :type file_path: Union[XAPath, str] + :return: The populated LSM object + :rtype: XALSM + + :Example: Using the gaming vs. productivity Reddit post map + + >>> import PyXA + >>> lsm = PyXA.XALSM.load("/Users/steven/Downloads/gaming-productivity.map") + >>> print(lsm.categorize_query("Hidden survival base on our server")) + >>> print(lsm.categorize_query("Your memory is FAR more powerful than you think… school just never taught us to use it properly.")) + [(1, 0.7313863635063171)] + [(2, 0.9422407150268555)] + + .. versionadded:: 0.1.0 + """ + if isinstance(file_path, str): + file_path = XAPath(file_path) + + new_lsm = XALSM(from_file=True) + new_lsm.map = LatentSemanticMapping.LSMMapCreateFromURL(None, file_path.xa_elem, LatentSemanticMapping.kLSMMapLoadMutable) + new_lsm.__dataset = {i: [] for i in range(LatentSemanticMapping.LSMMapGetCategoryCount(new_lsm.map))} + new_lsm.__categories = {i: i for i in range(LatentSemanticMapping.LSMMapGetCategoryCount(new_lsm.map))} + LatentSemanticMapping.LSMMapCompile(new_lsm.map) + return new_lsm
+ +
[docs] def add_category(self, name: str, initial_data: Union[list[str], None] = None) -> int: + """Adds a new category to the map, optionally filling the category with initial text data. + + :param name: The name of the category + :type name: str + :param initial_data: _description_ + :type initial_data: list[str] + :return: The ID of the new category + :rtype: int + + :Example: Add a category for cleaning-related Reddit posts to the previous example + + >>> import PyXA + >>> lsm = PyXA.XALSM.load("/Users/steven/Downloads/gaming-productivity.map") + >>> lsm.add_category("cleaning", ["Curtains stained from eyelet reaction at dry cleaner", "How do I get these stains out of my pink denim overalls? from a black denim jacket that was drying next to them", "Cleaned my depression room after months 🥵", "Tip: 30 minute soak in Vinegar", "Regular floor squeegee pulled a surprising amount of pet hair out of my carpet!", "Before and after…", "It actually WORKS", "CLR is actually magic. (With some elbow grease)", "It was 100% worth it to scrape out my old moldy caulk and replace it. $5 dollars and a bit of time to make my shower look so much cleaner!", "Thanks to the person who recommended the Clorox Foamer. Before and after pics", "TIL you can dissolve inkstains with milk.", "Fixing cat scratch marks to couch using felting needle: Before and After", "Turns out BKF isn't a meme! Really satisfied with this stuff"]) + >>> print(lsm.categorize_query("Hidden survival base on our server")) + >>> print(lsm.categorize_query("Your memory is FAR more powerful than you think… school just never taught us to use it properly.")) + >>> print(lsm.categorize_query("A carpet rake will change your life.")) + [(1, 0.7474805116653442)] + [(2, 0.7167008519172668)] + [(3, 0.797333300113678)] + + .. versionadded:: 0.1.0 + """ + LatentSemanticMapping.LSMMapStartTraining(self.map) + + if initial_data is None: + initial_data = [] + + if name in self.__dataset: + raise ValueError("The category name must be unique.") + + self.__dataset[name] = initial_data + category_ref = self.__add_category(name) + LatentSemanticMapping.LSMMapCompile(self.map) + return category_ref
+ +
[docs] def add_data(self, data: dict[Union[int, str], list[str]]) -> list[int]: + """Adds the provided data, organized by category, to the active map. + + :param data: A dictionary specifying new or existing categories along with data to input into them + :type data: dict[Union[int, str], list[str]] + :return: A list of newly created category IDs + :rtype: int + + :Example: Classify text by language + + >>> import PyXA + >>> lsm = PyXA.XALSM({}) + >>> lsm.add_data({ + >>> # 1 + >>> "english": ["brilliance outer jacket artist flat mosquito recover restrict official gas ratio publish domestic realize pure offset obstacle thigh favorite demonstration revive nest reader slide pudding symptom ballot auction characteristic complete Mars ridge student explosion dive emphasis the buy perfect motif penny a errand to fur far spirit random integration of with"], + >>> + >>> # 2 + >>> "italian": ["da piazza proposta di legge legare nazionale a volte la salute bar farti farmi il pane aggiunta valore artista chiamata settentrionale scuro buio classe signori investitore in grado di fidanzato tagliare arriva successo altrimenti speciale esattamente definizione sorriso chiamo madre pulire esperto rurale vedo malattia era amici libertà l'account immaginare lingua soldi più perché"], + >>> }) + >>> print(lsm.categorize_query("Here's to the crazy ones")) + >>> print(lsm.categorize_query("Potete parlarmi in italiano")) + [(1, 1.0)] + [(2, 1.0)] + + .. versionadded:: 0.1.0 + """ + category_refs = [] + LatentSemanticMapping.LSMMapStartTraining(self.map) + for category in data: + if category not in self.__dataset: + self.__dataset[category] = data[category] + category_refs.append(self.__add_category(category)) + else: + loc = Foundation.CFLocaleGetSystem() + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, " ".join(data[category]), loc, LatentSemanticMapping.kLSMTextPreserveAcronyms) + LatentSemanticMapping.LSMMapAddText(self.map, text_ref, self.__categories[category]) + LatentSemanticMapping.LSMMapCompile(self.map) + return category_refs
+ +
[docs] def add_text(self, text: str, category: Union[int, str], weight: float = 1): + """Adds the given text to the specified category, applying an optional weight. + + :param text: The text to add to the dataset + :type text: str + :param category: The category to add the text to + :type category: Union[int, str] + :param weight: The weight to assign to the text entry, defaults to 1 + :type weight: float, optional + :raises ValueError: The specified category must be a valid category name or ID + + :Example: + + >>> import PyXA + >>> lsm = PyXA.XALSM({"colors": [], "numbers": ["One", "Two", "Three"]}) + >>> lsm.add_text("red orange yellow green blue purple", "colors") + >>> lsm.add_text("white black grey gray brown pink", 1) + >>> print(lsm.categorize_query("pink")) + + .. versionadded:: 0.1.0 + """ + LatentSemanticMapping.LSMMapStartTraining(self.map) + if category not in self.__dataset and category not in self.__categories: + raise ValueError(f"Invalid category: {category}") + + loc = Foundation.CFLocaleGetSystem() + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, text, loc, LatentSemanticMapping.kLSMTextPreserveAcronyms) + LatentSemanticMapping.LSMMapAddTextWithWeight(self.map, text_ref, self.__categories[category], weight) + LatentSemanticMapping.LSMMapCompile(self.map)
+ +
[docs] def categorize_query(self, query: str, num_results: int = 1) -> list[tuple[int, float]]: + """Categorizes the query based on the current weights in the map. + + :param query: The query to categorize + :type query: str + :param num_results: The number of categorizations to show, defaults to 1 + :type num_results: int, optional + :return: A list of tuples identifying categories and their associated score. A higher score indicates better fit. If not matching categorization is found, the list will be empty. + :rtype: list[tuple[int, float]] + + :Example: + + >>> import PyXA + >>> dataset = { + >>> # 1 + >>> "color": ["red", "orange", "yellow", "green", "emerald", "blue", "purple", "white", "black", "brown", "pink", "grey", "gray"], + >>> + >>> # 2 + >>> "number": ["One Two Three Four Five Six Seven Eight Nine Ten"] + >>> } + >>> lsm = PyXA.XALSM(dataset) + >>> queries = ["emerald green three", "one hundred five", "One o' clock", "sky blue", "ninety nine", "purple pink"] + >>> + >>> for query in queries: + >>> category = "Unknown" + >>> categorization_tuple = lsm.categorize_query(query) + >>> if len(categorization_tuple) > 0: + >>> category = list(dataset.keys())[categorization_tuple[0][0] - 1] + >>> print(query, "is a", category) + emerald green three is a color + one hundred five is a number + One o' clock is a number + sky blue is a color + ninety nine is a number + purple pink is a color + + .. versionadded:: 0.1.0 + """ + loc = Foundation.CFLocaleGetSystem() + text_ref = LatentSemanticMapping.LSMTextCreate(None, self.map) + LatentSemanticMapping.LSMTextAddWords(text_ref, query, loc, 0) + rows = LatentSemanticMapping.LSMResultCreate(None, self.map, text_ref, 10, LatentSemanticMapping.kLSMTextPreserveAcronyms) + + categorization = [] + num_results = min(num_results, LatentSemanticMapping.LSMResultGetCount(rows)) + for i in range(0, num_results): + category_num = LatentSemanticMapping.LSMResultGetCategory(rows, i) + score = LatentSemanticMapping.LSMResultGetScore(rows, i) + categorization.append((category_num, score)) + return categorization
+ + + + +
[docs]class XAColorList(XATextList): + """A wrapper around lists of colors that employs fast enumeration techniques. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAColor, filter)
+ +
[docs]class XAColor(XAObject, XAClipboardCodable): + def __init__(self, *args): + if len(args) == 0: + # No color specified -- default to white + self.xa_elem = XAColor.white_color().xa_elem + elif len(args) == 1 and isinstance(args[0], AppKit.NSColor): + # Initialize copy of non-mutable NSColor object + self.copy_color(args[0]) + elif len(args) == 1 and isinstance(args[0], XAColor): + # Initialize copy of another XAColor object + self.copy_color(args[0].xa_elem) + else: + # Initialize from provided RGBA values + red = args[0] if len(args) >= 0 else 255 + green = args[1] if len(args) >= 1 else 255 + blue = args[2] if len(args) >= 3 else 255 + alpha = args[3] if len(args) == 4 else 1.0 + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red, green, blue, alpha) + +
[docs] def red() -> 'XAColor': + """Initializes and returns a pure red :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(65535, 0, 0)
+ +
[docs] def orange() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (1.0, 0.5, 0.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.orangeColor())
+ +
[docs] def yellow() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (1.0, 1.0, 0.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.yellowColor())
+ +
[docs] def green() -> 'XAColor': + """Initializes and returns a pure green :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 65535, 0)
+ +
[docs] def cyan() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.0, 1.0, 1.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.cyanColor())
+ +
[docs] def blue() -> 'XAColor': + """Initializes and returns a pure blue :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 0, 65535)
+ +
[docs] def magenta() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (1.0, 0.0, 1.0). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.magentaColor())
+ +
[docs] def purple() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.5, 0.0, 0.5). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.purpleColor())
+ +
[docs] def brown() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.6, 0.4, 0.2). + + .. versionadded:: 0.1.0 + """ + return XAColor(AppKit.NSColor.brownColor())
+ +
[docs] def white() -> 'XAColor': + """Initializes and returns a pure white :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(65535, 65535, 65535)
+ +
[docs] def gray() -> 'XAColor': + """Initializes and returns an :class:`XAColor` object whose RGB values are (0.5, 0.5, 0.5). + + .. versionadded:: 0.1.0 + """ + return XAColor(0.5, 0.5, 0.5)
+ +
[docs] def black() -> 'XAColor': + """Initializes and returns a pure black :class:`XAColor` object. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 0, 0)
+ +
[docs] def clear() -> 'XAColor': + """Initializes and returns a an :class:`XAColor` object whose alpha value is 0.0. + + .. versionadded:: 0.1.0 + """ + return XAColor(0, 0, 0, 0)
+ + @property + def red_value(self) -> float: + """The red value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.redComponent() + + @red_value.setter + def red_value(self, red_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red_value, self.green_value, self.blue_value, self.alpha_value) + + @property + def green_value(self) -> float: + """The green value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.greenComponent() + + @green_value.setter + def green_value(self, green_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(self.red_value, green_value, self.blue_value, self.alpha_value) + + @property + def blue_value(self) -> float: + """The blue value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.blueComponent() + + @blue_value.setter + def blue_value(self, blue_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(self.red_value, self.green_value, blue_value, self.alpha_value) + + @property + def alpha_value(self) -> float: + """The alpha value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.alphaComponent() + + @alpha_value.setter + def alpha_value(self, alpha_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(self.red_value, self.green_value, self.blue_value, alpha_value) + + @property + def hue_value(self): + """The hue value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.hueComponent() + + @hue_value.setter + def hue_value(self, hue_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(hue_value, self.saturation_value, self.brightness_value, self.alpha_value) + + @property + def saturation_value(self): + """The staturation value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.saturationComponent() + + @saturation_value.setter + def saturation_value(self, saturation_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(self.hue_value, saturation_value, self.brightness_value, self.alpha_value) + + @property + def brightness_value(self): + """The brightness value of the color on the scale of 0.0 to 1.0. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.brightnessComponent() + + @brightness_value.setter + def brightness_value(self, brightness_value: float): + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(self.hue_value, self.saturation_value, brightness_value, self.alpha_value) + +
[docs] def copy_color(self, color: AppKit.NSColor) -> 'XAColor': + """Initializes a XAColor copy of an NSColor object. + + :param color: The NSColor to copy + :type color: AppKit.NSColor + :return: The newly created XAColor object + :rtype: XAColor + + .. versionadded:: 0.1.0 + """ + self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_( + color.redComponent(), + color.greenComponent(), + color.blueComponent(), + color.alphaComponent() + ) + return self
+ +
[docs] def set_rgba(self, red: float, green: float, blue: float, alpha: float) -> 'XAColor': + """Sets the RGBA values of the color. + + :param red: The red value of the color, from 0.0 to 1.0 + :type red: float + :param green: The green value of the color, from 0.0 to 1.0 + :type green: float + :param blue: The blue value of the color, from 0.0 to 1.0 + :type blue: float + :param alpha: The opacity of the color, from 0.0 to 1.0 + :type alpha: float + :return: The XAColor object + :rtype: XAColor - .. versionadded:: 0.0.8 + .. versionadded:: 0.1.0 """ - image_types = [AppKit.NSPasteboardTypePNG, AppKit.NSPasteboardTypeTIFF, 'public.jpeg', 'com.apple.icns'] - items = [] - for item in self.xa_elem.pasteboardItems(): - for image_type in image_types: - if image_type in item.types(): - img = XAImage(data = item.dataForType_(image_type)) - items.append(img) - return items
+ self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red, green, blue, alpha) + return self
-
[docs] def set_contents(self, content: List[Any]): - """Sets the content of the clipboard +
[docs] def set_hsla(self, hue: float, saturation: float, brightness: float, alpha: float) -> 'XAColor': + """Sets the HSLA values of the color. + + :param hue: The hue value of the color, from 0.0 to 1.0 + :type hue: float + :param saturation: The saturation value of the color, from 0.0 to 1.0 + :type saturation: float + :param brightness: The brightness value of the color, from 0.0 to 1.0 + :type brightness: float + :param alpha: The opacity of the color, from 0.0 to 1.0 + :type alpha: float + :return: The XAColor object + :rtype: XAColor - :param content: A list of the content to add fill the clipboard with. - :type content: List[Any] + .. versionadded:: 0.1.0 + """ + self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(hue, saturation, brightness, alpha) + return self
- .. deprecated:: 0.0.8 - Set the :ivar:`content` property directly instead. - - .. versionadded:: 0.0.5 +
[docs] def mix_with(self, color: 'XAColor', fraction: int = 0.5) -> 'XAColor': + """Blends this color with the specified fraction of another. + + :param color: The color to blend this color with + :type color: XAColor + :param fraction: The fraction of the other color to mix into this color, from 0.0 to 1.0, defaults to 0.5 + :type fraction: int, optional + :return: The resulting color after mixing + :rtype: XAColor + + .. versionadded:: 0.1.0 """ - self.xa_elem.clearContents() - self.xa_elem.writeObjects_(content)
+ new_color = self.xa_elem.blendedColorWithFraction_ofColor_(fraction, color.xa_elem) + return XAColor(new_color.redComponent(), new_color.greenComponent(), new_color.blueComponent(), new_color.alphaComponent())
+
[docs] def brighten(self, fraction: float = 0.5) -> 'XAColor': + """Brightens the color by mixing the specified fraction of the system white color into it. + :param fraction: The amount (fraction) of white to mix into the color, defaults to 0.5 + :type fraction: float, optional + :return: The resulting color after brightening + :rtype: XAColor + .. versionadded:: 0.1.0 + """ + self.xa_elem = self.xa_elem.highlightWithLevel_(fraction) + return self
-
[docs]class XANotification(XAObject): - """A class for managing and interacting with notifications. +
[docs] def darken(self, fraction: float = 0.5) -> 'XAColor': + """Darkens the color by mixing the specified fraction of the system black color into it. - .. versionadded:: 0.0.9 - """ - def __init__(self, text: str, title: Union[str, None] = None, subtitle: Union[str, None] = None, sound_name: Union[str, None] = None): - """Initializes a notification object. + :param fraction: The amount (fraction) of black to mix into the color, defaults to 0.5 + :type fraction: float, optional + :return: The resulting color after darkening + :rtype: XAColor - :param text: The main text of the notification - :type text: str - :param title: The title of the notification, defaults to None - :type title: Union[str, None], optional - :param subtitle: The subtitle of the notification, defaults to None - :type subtitle: Union[str, None], optional - :param sound_name: The sound to play when the notification is displayed, defaults to None - :type sound_name: Union[str, None], optional + .. versionadded:: 0.1.0 + """ + self.xa_elem = self.xa_elem.shadowWithLevel_(fraction) + return self
- .. versionadded:: 0.0.9 +
[docs] def make_swatch(self, width: int = 100, height: int = 100) -> 'XAImage': + """Creates an image swatch of the color with the specified dimensions. + + :param width: The width of the swatch image, in pixels, defaults to 100 + :type width: int, optional + :param height: The height of the swatch image, in pixels, defaults to 100 + :type height: int, optional + :return: The image swatch as an XAImage object + :rtype: XAImage + + :Example: View swatches in Preview + + >>> import PyXA + >>> from time import sleep + >>> + >>> blue = PyXA.XAColor.blue() + >>> red = PyXA.XAColor.red() + >>> + >>> swatches = [ + >>> blue.make_swatch(), + >>> blue.darken(0.5).make_swatch(), + >>> blue.mix_with(red).make_swatch() + >>> ] + >>> + >>> for swatch in swatches: + >>> swatch.show_in_preview() + >>> sleep(0.2) + + .. versionadded:: 0.1.0 """ - self.text = text - self.title = title - self.subtitle = subtitle - self.sound_name = sound_name + img = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(width, height)) + img.lockFocus() + self.xa_elem.drawSwatchInRect_(AppKit.NSMakeRect(0, 0, width, height)) + img.unlockFocus() + return XAImage(img)
-
[docs] def display(self): - """Displays the notification. +
[docs] def get_clipboard_representation(self) -> AppKit.NSColor: + """Gets a clipboard-codable representation of the color. - .. todo:: - - Currently uses :func:`subprocess.Popen`. Should use UserNotifications in the future. + When the clipboard content is set to a color, the raw color data is added to the clipboard. - .. versionadded:: 0.0.9 + :return: The raw color data + :rtype: AppKit.NSColor + + .. versionadded:: 0.1.0 """ - script = AppleScript() - script.add(f"display notification \\\"{self.text}\\\"") + return self.xa_elem
- if self.title is not None: - script.add(f"with title \\\"{self.title}\\\"") + def __repr__(self): + return f"<{str(type(self))}r={str(self.red_value)}, g={self.green_value}, b={self.blue_value}, a={self.alpha_value}>"
+ + + + +# TODO: Init NSLocation object +
[docs]class XALocation(XAObject): + """A location with a latitude and longitude, along with other data. + + .. versionadded:: 0.0.2 + """ + current_location: 'XALocation' #: The current location of the device + + def __init__(self, raw_value: CoreLocation.CLLocation = None, title: str = None, latitude: float = 0, longitude: float = 0, altitude: float = None, radius: int = 0, address: str = None): + self.raw_value = raw_value #: The raw CLLocation object + self.title = title #: The name of the location + self.latitude = latitude #: The latitude of the location + self.longitude = longitude #: The longitude of the location + self.altitude = altitude #: The altitude of the location + self.radius = radius #: The horizontal accuracy of the location measurement + self.address = address #: The addres of the location + + @property + def raw_value(self) -> CoreLocation.CLLocation: + return self.__raw_value + + @raw_value.setter + def raw_value(self, raw_value: CoreLocation.CLLocation): + self.__raw_value = raw_value + if raw_value is not None: + self.latitude = raw_value.coordinate()[0] + self.longitude = raw_value.coordinate()[1] + self.altitude = raw_value.altitude() + self.radius = raw_value.horizontalAccuracy() + + @property + def current_location(self) -> 'XALocation': + self.raw_value = None + self._spawn_thread(self.__get_current_location) + while self.raw_value is None: + time.sleep(0.01) + return self + +
[docs] def show_in_maps(self): + """Shows the location in Maps.app. + + .. versionadded:: 0.0.6 + """ + XAURL(f"maps://?q={self.title},ll={self.latitude},{self.longitude}").open()
+ + def __get_current_location(self): + location_manager = CoreLocation.CLLocationManager.alloc().init() + old_self = self + class CLLocationManagerDelegate(AppKit.NSObject): + def locationManager_didUpdateLocations_(self, manager, locations): + if locations is not None: + old_self.raw_value = locations[0] + AppHelper.stopEventLoop() + + def locationManager_didFailWithError_(self, manager, error): + print(manager, error) + + location_manager.requestWhenInUseAuthorization() + location_manager.setDelegate_(CLLocationManagerDelegate.alloc().init().retain()) + location_manager.requestLocation() + + AppHelper.runConsoleEventLoop() + + def __repr__(self): + return "<" + str(type(self)) + str((self.latitude, self.longitude)) + ">"
- if self.subtitle is not None: - script.add(f"subtitle \\\"{self.subtitle}\\\"") + + + +
[docs]class XAAlertStyle(Enum): + """Options for which alert style an alert should display with. + """ + INFORMATIONAL = AppKit.NSAlertStyleInformational + WARNING = AppKit.NSAlertStyleWarning + CRITICAL = AppKit.NSAlertStyleCritical
+ +
[docs]class XAAlert(XAObject): + """A class for creating and interacting with an alert dialog window. + + .. versionadded:: 0.0.5 + """ + def __init__(self, title: str = "Alert!", message: str = "", style: XAAlertStyle = XAAlertStyle.INFORMATIONAL, buttons = ["Ok", "Cancel"]): + super().__init__() + self.title: str = title + self.message: str = message + self.style: XAAlertStyle = style + self.buttons: list[str] = buttons + +
[docs] def display(self) -> int: + """Displays the alert. + + :return: A number representing the button that the user selected, if any + :rtype: int + + .. versionadded:: 0.0.5 + """ + alert = AppKit.NSAlert.alloc().init() + alert.setMessageText_(self.title) + alert.setInformativeText_(self.message) + alert.setAlertStyle_(self.style.value) + + for button in self.buttons: + alert.addButtonWithTitle_(button) + return alert.runModal()
+ + + + +
[docs]class XAColorPickerStyle(Enum): + """Options for which tab a color picker should display when first opened. + """ + GRAYSCALE = AppKit.NSColorPanelModeGray + RGB_SLIDERS = AppKit.NSColorPanelModeRGB + CMYK_SLIDERS = AppKit.NSColorPanelModeCMYK + HSB_SLIDERS = AppKit.NSColorPanelModeHSB + COLOR_LIST = AppKit.NSColorPanelModeColorList + COLOR_WHEEL = AppKit.NSColorPanelModeWheel + CRAYONS = AppKit.NSColorPanelModeCrayon + IMAGE_PALETTE = AppKit.NSColorPanelModeCustomPalette
+ +
[docs]class XAColorPicker(XAObject): + """A class for creating and interacting with a color picker window. + + .. versionadded:: 0.0.5 + """ + def __init__(self, style: XAColorPickerStyle = XAColorPickerStyle.GRAYSCALE): + super().__init__() + self.style = style + +
[docs] def display(self) -> XAColor: + """Displays the color picker. + + :return: The color that the user selected + :rtype: XAColor + + .. versionadded:: 0.0.5 + """ + panel = AppKit.NSColorPanel.sharedColorPanel() + panel.setMode_(self.style.value) + panel.setShowsAlpha_(True) + + def run_modal(panel): + initial_color = panel.color() + time.sleep(0.5) + while panel.isVisible() and panel.color() == initial_color: + time.sleep(0.01) + AppKit.NSApp.stopModal() + + modal_thread = threading.Thread(target=run_modal, args=(panel, ), name="Run Modal", daemon=True) + modal_thread.start() + + AppKit.NSApp.runModalForWindow_(panel) + return XAColor(panel.color())
+ + + + +
[docs]class XADialog(XAObject): + """A custom dialog window. + + .. versionadded:: 0.0.8 + """ + def __init__(self, text: str = "", title: Union[str, None] = None, buttons: Union[None, list[Union[str, int]]] = None, hidden_answer: bool = False, default_button: Union[str, int, None] = None, cancel_button: Union[str, int, None] = None, icon: Union[Literal["stop", "note", "caution"], None] = None, default_answer: Union[str, int, None] = None): + super().__init__() + self.text: str = text + self.title: str = title + self.buttons: Union[None, list[Union[str, int]]] = buttons or [] + self.hidden_answer: bool = hidden_answer + self.icon: Union[str, None] = icon + self.default_button: Union[str, int, None] = default_button + self.cancel_button: Union[str, int, None] = cancel_button + self.default_answer: Union[str, int, None] = default_answer - if self.sound_name is not None: - script.add(f"sound name \\\"{self.sound_name}\\\"") +
[docs] def display(self) -> Union[str, int, None, list[str]]: + """Displays the dialog, waits for the user to select an option or cancel, then returns the selected button or None if cancelled. - cmd = "osascript -e \"" + " ".join(script.script) + "\"" - subprocess.Popen([cmd], shell=True)
+ :return: The selected button or None if no value was selected + :rtype: Union[str, int, None, list[str]] + .. versionadded:: 0.0.8 + """ + buttons = [x.replace("'", "") for x in self.buttons] + buttons = str(buttons).replace("'", '"') + default_button = str(self.default_button).replace("'", "") + default_button_str = "default button \"" + default_button + "\"" if self.default_button is not None else "" + cancel_button = str(self.cancel_button).replace("'", "") + cancel_button_str = "cancel button \"" + cancel_button + "\"" if self.cancel_button is not None else "" -
[docs]class XAList(XAObject): - """A wrapper around NSArray and NSMutableArray objects enabling fast enumeration and lazy evaluation of Objective-C objects. + icon_str = "with icon " + self.icon + "" if self.icon is not None else "" - .. versionadded:: 0.0.3 - """ - def __init__(self, properties: dict, object_class: type = None, filter: Union[dict, None] = None): - """Creates an efficient wrapper object around a list of scriptable elements. + default_answer = str(self.default_answer).replace("'", '"') + default_answer_str = "default answer \"" + default_answer + "\"" if self.default_answer is not None else "" - :param properties: PyXA properties passed to this object for utility purposes - :type properties: dict - :param object_class: _description_, defaults to None - :type object_class: type, optional - :param filter: A dictionary of properties and values to filter items by, defaults to None - :type filter: Union[dict, None], optional + script = AppleScript(f""" + tell application "Terminal" + display dialog \"{self.text}\" with title \"{self.title}\" buttons {buttons} {default_button_str} {cancel_button_str} {icon_str} {default_answer_str} hidden answer {self.hidden_answer} + end tell + """) - .. versionchanged:: 0.0.8 - The filter property is deprecated and will be removed in a future version. Use the :func:`filter` method instead. + result = script.run()["event"] + if result is not None: + if result.numberOfItems() > 1: + return [result.descriptorAtIndex_(1).stringValue(), result.descriptorAtIndex_(2).stringValue()] + else: + return result.descriptorAtIndex_(1).stringValue()
+ - .. versionadded:: 0.0.3 - """ - super().__init__(properties) - self.xa_ocls = object_class - if filter is not None: - self.xa_elem = XAPredicate().from_dict(filter).evaluate(self.xa_elem) -
[docs] def by_property(self, property: str, value: Any) -> XAObject: - """Retrieves the first element whose property value matches the given value, if one exists. +
[docs]class XAMenu(XAObject): + """A custom list item selection menu. - :param property: The property to match - :type property: str - :param value: The value to match - :type value: Any - :return: The matching element, if one is found - :rtype: XAObject + .. versionadded:: 0.0.8 + """ + def __init__(self, menu_items: list[Any], title: str = "Select Item", prompt: str = "Select an item", default_items: Union[list[str], None] = None, ok_button_name: str = "Okay", cancel_button_name: str = "Cancel", multiple_selections_allowed: bool = False, empty_selection_allowed: bool = False): + super().__init__() + self.menu_items: list[Union[str, int]] = menu_items #: The items the user can choose from + self.title: str = title #: The title of the dialog window + self.prompt: str = prompt #: The prompt to display in the dialog box + self.default_items: list[str] = default_items or [] #: The items to initially select + self.ok_button_name: str = ok_button_name #: The name of the OK button + self.cancel_button_name: str = cancel_button_name #: The name of the Cancel button + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether multiple items can be selected + self.empty_selection_allowed: bool = empty_selection_allowed #: Whether the user can click OK without selecting anything - :Example: +
[docs] def display(self) -> Union[str, int, bool, list[str], list[int]]: + """Displays the menu, waits for the user to select an option or cancel, then returns the selected value or False if cancelled. - >>> import PyXA - >>> app = PyXA.application("Photos") - >>> photo = app.media_items().by_property("id", "CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001") - >>> print(photo) - <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001> + :return: The selected value or False if no value was selected + :rtype: Union[str, int, bool, list[str], list[int]] - .. versionadded:: 0.0.6 + .. versionadded:: 0.0.8 """ - predicate = XAPredicate() - predicate.add_eq_condition(property, value) - ls = predicate.evaluate(self.xa_elem) - obj = ls[0] - return self._new_element(obj, self.xa_ocls)
+ menu_items = [x.replace("'", "") for x in self.menu_items] + menu_items = str(menu_items).replace("'", '"') + default_items = str(self.default_items).replace("'", '"') + script = AppleScript(f""" + tell application "Terminal" + choose from list {menu_items} with title \"{self.title}\" with prompt \"{self.prompt}\" default items {default_items} OK button name \"{self.ok_button_name}\" cancel button name \"{self.cancel_button_name}\" multiple selections allowed {self.multiple_selections_allowed} empty selection allowed {self.empty_selection_allowed} + end tell + """) + result = script.run()["event"] + if result is not None: + if self.multiple_selections_allowed: + values = [] + for x in range(1, result.numberOfItems() + 1): + desc = result.descriptorAtIndex_(x) + values.append(desc.stringValue()) + return values + else: + if result.stringValue() == "false": + return False + return result.stringValue()
-
[docs] def containing(self, property: str, value: str) -> XAObject: - """Retrieves the element whose property value contains the given value, if one exists. - :param property: The property match - :type property: str - :param value: The value to search for - :type value: str - :return: The matching element, if one is found - :rtype: XAObject - .. deprecated:: 0.0.8 - Use :func:`filter` instead. - .. versionadded:: 0.0.6 - """ - predicate = XAPredicate() - predicate.add_contains_condition(property, value) - ls = predicate.evaluate(self.xa_elem) - obj = ls[0] - return self._new_element(obj, self.xa_ocls)
+
[docs]class XAFilePicker(XAObject): + """A file selection window. -
[docs] def filter(self, filter: str, comparison_operation: Union[str, None] = None, value1: Union[Any, None] = None, value2: Union[Any, None] = None) -> 'XAList': - """Filters the list by the given parameters. + .. versionadded:: 0.0.8 + """ + def __init__(self, prompt: str = "Choose File", types: list[str] = None, default_location: Union[str, None] = None, show_invisibles: bool = False, multiple_selections_allowed: bool = False, show_package_contents: bool = False): + super().__init__() + self.prompt: str = prompt #: The prompt to display in the dialog box + self.types: list[str] = types #: The file types/type identifiers to allow for selection + self.default_location: Union[str, None] = default_location #: The default file location + self.show_invisibles: bool = show_invisibles #: Whether invisible files and folders are shown + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether the user can select multiple files + self.show_package_contents: bool = show_package_contents #: Whether to show the contents of packages - The filter may be either a format string, used to create an NSPredicate, or up to 4 arguments specifying the filtered property name, the comparison operation, and up to two values to compare against. +
[docs] def display(self) -> Union[XAPath, None]: + """Displays the file chooser, waits for the user to select a file or cancel, then returns the selected file URL or None if cancelled. - :param filter: A format string or a property name - :type filter: str - :param comparison_operation: The symbol or name of a comparison operation, such as > or <, defaults to None - :type comparison_operation: Union[str, None], optional - :param value1: The first value to compare each list item's property value against, defaults to None - :type value1: Union[Any, None], optional - :param value2: The second value to compare each list item's property value against, defaults to None - :type value2: Union[Any, None], optional - :return: The filter XAList object - :rtype: XAList + :return: The selected file URL or None if no file was selected + :rtype: Union[XAPath, None] - :Example 1: Get the last file sent by you (via this machine) in Messages.app + .. versionadded:: 0.0.8 + """ + types = [x.replace("'", "") for x in self.types] + types = str(types).replace("'", '"') + types_str = "of type " + types if self.types is not None else "" - >>> import PyXA - >>> app = PyXA.application("Messages") - >>> last_file_transfer = app.file_transfers().filter("direction", "==", app.MessageDirection.OUTGOING)[-1] - >>> print(last_file_transfer) - <<class 'PyXA.apps.Messages.XAMessagesFileTransfer'>Test.jpg> + default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" - :Example 2: Get the list of favorite photos/videos from Photos.app + script = AppleScript(f""" + tell application "Terminal" + choose file with prompt \"{self.prompt}\" {types_str}{default_location_str} invisibles {self.show_invisibles} multiple selections allowed {self.multiple_selections_allowed} showing package contents {self.show_package_contents} + end tell + """) + result = script.run()["event"] - >>> import PyXA - >>> app = PyXA.application("Photos") - >>> favorites = app.media_items().filter("favorite", "==", True) - >>> print(favorites) - <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItemList'>['CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001', 'EFEB7F37-8373-4972-8E43-21612F597185/L0/001', ...]> + if result is not None: + if self.multiple_selections_allowed: + values = [] + for x in range(1, result.numberOfItems() + 1): + desc = result.descriptorAtIndex_(x) + values.append(XAPath(desc.fileURLValue())) + return values + else: + return XAPath(result.fileURLValue())
- .. note:: - - For properties that appear to be boolean but fail to return expected filter results, try using the corresponding 0 or 1 value instead. - :Example 3: Provide a custom format string - >>> import PyXA - >>> app = PyXA.application("Photos") - >>> photo = app.media_items().filter("id == 'CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001'")[0] - >>> print(photo) - <<class 'PyXA.apps.PhotosApp.XAPhotosMediaItem'>id=CB24FE9F-E9DC-4A5C-A0B0-CC779B1CEDCE/L0/001> - .. versionadded:: 0.0.8 - """ - if comparison_operation is not None and value1 is not None: - predicate = XAPredicate() - if comparison_operation in ["=", "==", "eq", "EQ", "equals", "EQUALS"]: - predicate.add_eq_condition(filter, value1) - elif comparison_operation in ["!=", "!==", "neq", "NEQ", "not equal to", "NOT EQUAL TO"]: - predicate.add_neq_condition(filter, value1) - elif comparison_operation in [">", "gt", "GT", "greater than", "GREATER THAN"]: - predicate.add_gt_condition(filter, value1) - elif comparison_operation in ["<", "lt", "LT", "less than", "LESS THAN"]: - predicate.add_lt_condition(filter, value1) - elif comparison_operation in [">=", "geq", "GEQ", "greater than or equal to", "GREATER THAN OR EQUAL TO"]: - predicate.add_geq_condition(filter, value1) - elif comparison_operation in ["<=", "leq", "LEQ", "less than or equal to", "LESS THAN OR EQUAL TO"]: - predicate.add_leq_condition(filter, value1) - elif comparison_operation in ["begins with", "beginswith", "BEGINS WITH", "BEGINSWITH"]: - predicate.add_begins_with_condition(filter, value1) - elif comparison_operation in ["contains", "CONTAINS"]: - predicate.add_contains_condition(filter, value1) - elif comparison_operation in ["ends with", "endswith", "ENDS WITH", "ENDSWITH"]: - predicate.add_ends_with_condition(filter, value1) - elif comparison_operation in ["between", "BETWEEN"]: - predicate.add_between_condition(filter, value1, value2) - elif comparison_operation in ["matches", "MATCHES"]: - predicate.add_match_condition(filter, value1) +
[docs]class XAFolderPicker(XAObject): + """A folder selection window. - filtered_list = predicate.evaluate(self.xa_elem) - return self._new_element(filtered_list, self.__class__) - else: - filtered_list = XAPredicate.evaluate_with_format(self.xa_elem, filter) - return self._new_element(filtered_list, self.__class__)
+ .. versionadded:: 0.0.8 + """ + def __init__(self, prompt: str = "Choose Folder", default_location: Union[str, None] = None, show_invisibles: bool = False, multiple_selections_allowed: bool = False, show_package_contents: bool = False): + super().__init__() + self.prompt: str = prompt #: The prompt to display in the dialog box + self.default_location: Union[str, None] = default_location #: The default folder location + self.show_invisibles: bool = show_invisibles #: Whether invisible files and folders are shown + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether the user can select multiple folders + self.show_package_contents: bool = show_package_contents #: Whether to show the contents of packages -
[docs] def at(self, index: int) -> XAObject: - """Retrieves the element at the specified index. +
[docs] def display(self) -> Union[XAPath, None]: + """Displays the folder chooser, waits for the user to select a folder or cancel, then returns the selected folder URL or None if cancelled. - :param index: The index of the desired element - :type index: int - :return: The PyXA-wrapped element object - :rtype: XAObject + :return: The selected folder URL or None if no folder was selected + :rtype: Union[XAPath, None] - .. versionadded:: 0.0.6 + .. versionadded:: 0.0.8 """ - return self._new_element(self.xa_elem[index], self.xa_ocls)
-
[docs] def first(self) -> XAObject: - """Retrieves the first element of the list as a wrapped PyXA object. + default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" - :return: The wrapped object - :rtype: XAObject + script = AppleScript(f""" + tell application "Terminal" + choose folder with prompt \"{self.prompt}\" {default_location_str} invisibles {self.show_invisibles} multiple selections allowed {self.multiple_selections_allowed} showing package contents {self.show_package_contents} + end tell + """) + result = script.run()["event"] + if result is not None: + if self.multiple_selections_allowed: + values = [] + for x in range(1, result.numberOfItems() + 1): + desc = result.descriptorAtIndex_(x) + values.append(XAPath(desc.fileURLValue())) + return values + else: + return XAPath(result.fileURLValue())
- .. versionadded:: 0.0.3 - """ - return self._new_element(self.xa_elem.firstObject(), self.xa_ocls)
-
[docs] def last(self) -> XAObject: - """Retrieves the last element of the list as a wrapped PyXA object. - :return: The wrapped object - :rtype: XAObject - .. versionadded:: 0.0.3 - """ - return self._new_element(self.xa_elem.lastObject(), self.xa_ocls)
+
[docs]class XAApplicationPicker(XAObject): + """An application selection window. -
[docs] def shuffle(self) -> 'XAList': - """Randomizes the order of objects in the list. + .. versionadded:: 0.1.0 + """ + def __init__(self, title: Union[str, None] = None, prompt: Union[str, None] = None, multiple_selections_allowed: bool = False): + super().__init__() + self.title: str = title #: The dialog window title + self.prompt: str = prompt #: The prompt to be displayed in the dialog box + self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether to allow multiple items to be selected - :return: A reference to the shuffled XAList - :rtype: XAList +
[docs] def display(self) -> str: + """Displays the application chooser, waits for the user to select an application or cancel, then returns the selected application's name or None if cancelled. - .. versionadded:: 0.0.3 + :return: The name of the selected application + :rtype: str + + .. versionadded:: 0.0.8 """ - self.xa_elem = self.xa_elem.shuffledArray() - return self
-
[docs] def push(self, element: XAObject): - """Appends the object referenced by the provided PyXA wrapper to the end of the list. + script = AppleScript("tell application \"Terminal\"") + dialog_str = "choose application " + if self.title is not None: + dialog_str += f"with title \"{self.title}\" " + if self.prompt is not None: + dialog_str += f"with prompt \"{self.prompt}\"" + dialog_str += f"multiple selections allowed {self.multiple_selections_allowed} " + script.add(dialog_str) + script.add("end tell") - .. versionadded:: 0.0.3 - """ - self.xa_elem.addObject_(element.xa_elem)
+ return script.run()["string"]
-
[docs] def insert(self, element: XAObject, index: int): - """Inserts the object referenced by the provided PyXA wrapper at the specified index. - .. versionadded:: 0.0.3 - """ - self.xa_elem.insertObject_atIndex_(element.xa_elem, index)
-
[docs] def pop(self, index: int = -1) -> XAObject: - """Removes the object at the specified index from the list and returns it. - .. versionadded:: 0.0.3 - """ - removed = self.xa_elem.lastObject() - self.xa_elem.removeLastObject() - return self._new_element(removed, self.xa_ocls)
+
[docs]class XAFileNameDialog(XAObject): + """A file name input window. - def __getitem__(self, key: Union[int, slice]): - if isinstance(key, slice): - arr = AppKit.NSMutableArray.alloc().initWithArray_([self.xa_elem[index] for index in range(key.start, key.stop, key.step or 1)]) - return self._new_element(arr, self.__class__) - return self._new_element(self.xa_elem[key], self.xa_ocls) + .. versionadded:: 0.0.8 + """ + def __init__(self, prompt: str = "Specify file name and location", default_name: str = "New File", default_location: Union[str, None] = None): + super().__init__() + self.prompt: str = prompt #: The prompt to display in the dialog box + self.default_name: str = default_name #: The default name for the new file + self.default_location: Union[str, None] = default_location #: The default file location - def __len__(self): - if hasattr(self.xa_elem, "count"): - return self.xa_elem.count() - return len(self.xa_elem) +
[docs] def display(self) -> Union[XAPath, None]: + """Displays the file name input window, waits for the user to input a name and location or cancel, then returns the specified file URL or None if cancelled. - def __reversed__(self): - self.xa_elem = self.xa_elem.reverseObjectEnumerator().allObjects() - return self + :return: The specified file URL or None if no file name was inputted + :rtype: Union[XAPath, None] - def __iter__(self): - return (self._new_element(object, self.xa_ocls) for object in self.xa_elem.objectEnumerator()) + .. versionadded:: 0.0.8 + """ - def __repr__(self): - return "<" + str(type(self)) + str(self.xa_elem) + ">"
+ default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" + + script = AppleScript(f""" + tell application "Terminal" + choose file name with prompt \"{self.prompt}\" default name \"{self.default_name}\" {default_location_str} + end tell + """) + result = script.run()["event"] + if result is not None: + return XAPath(result.fileURLValue())
-
[docs]class XAProcess(XAObject): - def __init__(self, properties): - super().__init__(properties) - self.xa_wcls = properties["window_class"] - self.id = self.xa_elem.id() - self.unix_id = self.xa_elem.unixId() +
[docs]class XAMenuBar(XAObject): + def __init__(self): + """Creates a new menu bar object for interacting with the system menu bar. - self.front_window: XAWindow #: The front window of the application process + .. versionadded:: 0.0.9 + """ + self._menus = {} + self._menu_items = {} + self._methods = {} - @property - def front_window(self) -> 'XAWindow': - return self._new_element(self.xa_elem.windows()[0], XAWindow) + detector = self + class MyApplicationAppDelegate(AppKit.NSObject): + start_time = datetime.now() -
[docs] def windows(self, filter: dict = None) -> 'XAWindowList': - return self._new_element(self.xa_elem.windows(), XAWindowList, filter)
+ def applicationDidFinishLaunching_(self, sender): + for item_name, status_item in detector._menus.items(): + menu = status_item.menu() -
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': - return self._new_element(self.xa_elem.menuBars(), XAUIMenuBarList, filter)
+ menuitem = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '') + menu.addItem_(menuitem) + def action_(self, menu_item): + selection = menu_item.title() + for item_name in detector._methods: + if selection == item_name: + detector._methods[item_name]() + app = AppKit.NSApplication.sharedApplication() + app.setDelegate_(MyApplicationAppDelegate.alloc().init().retain()) +
[docs] def add_menu(self, title: str, image: Union['XAImage', None] = None, tool_tip: Union[str, None] = None, img_width: int = 30, img_height: int = 30): + """Adds a new menu to be displayed in the system menu bar. -
[docs]class XAApplication(XAObject, XAClipboardCodable): - """A general application class for both officially scriptable and non-scriptable applications. + :param title: The name of the menu + :type title: str + :param image: The image to display for the menu, defaults to None + :type image: Union[XAImage, None], optional + :param tool_tip: The tooltip to display on hovering over the menu, defaults to None + :type tool_tip: Union[str, None], optional + :param img_width: The width of the image, in pixels, defaults to 30 + :type img_width: int, optional + :param img_height: The height of the image, in pixels, defaults to 30 + :type img_height: int, optional - .. seealso:: :class:`XASBApplication`, :class:`XAWindow` + :Example: - .. versionadded:: 0.0.1 - """ - def __init__(self, properties): - super().__init__(properties) - self.xa_wcls = XAWindow + >>> import PyXA + >>> menu_bar = PyXA.XAMenuBar() + >>> img = PyXA.XAImage("/Users/steven/Downloads/Blackness.jpg") + >>> menu_bar.add_menu("Menu 1", image=img, img_width=100, img_height=100) + >>> menu_bar.display() - predicate = AppKit.NSPredicate.predicateWithFormat_("displayedName == %@", self.xa_elem.localizedName()) - process = self.xa_sevt.processes().filteredArrayUsingPredicate_(predicate)[0] + .. versionadded:: 0.0.9 + """ + status_bar = AppKit.NSStatusBar.systemStatusBar() + status_item = status_bar.statusItemWithLength_(AppKit.NSVariableStatusItemLength).retain() + status_item.setTitle_(title) + + if isinstance(image, XAImage): + img = image.xa_elem.copy() + img.setScalesWhenResized_(True) + img.setSize_((img_width, img_height)) + status_item.button().setImage_(img) - properties = { - "parent": self, - "appspace": self.xa_apsp, - "workspace": self.xa_wksp, - "element": process, - "appref": self.xa_aref, - "system_events": self.xa_sevt, - "window_class": self.xa_wcls - } - self.xa_prcs = XAProcess(properties) + status_item.setHighlightMode_(objc.YES) - self.bundle_identifier: str #: The bundle identifier for the application - self.bundle_url: str #: The file URL of the application bundle - self.executable_url: str #: The file URL of the application's executable - self.frontmost: bool #: Whether the application is the active application - self.launch_date: datetime #: The date and time that the application was launched - self.localized_name: str #: The application's name - self.owns_menu_bar: bool #: Whether the application owns the top menu bar - self.process_identifier: str #: The process identifier for the application instance + if isinstance(tool_tip, str): + status_item.setToolTip_(tool_tip) - @property - def bundle_identifier(self) -> str: - return self.xa_elem.bundleIdentifier() + menu = AppKit.NSMenu.alloc().init() + status_item.setMenu_(menu) - @property - def bundle_url(self) -> str: - return self.xa_elem.bundleURL() + status_item.setEnabled_(objc.YES) + self._menus[title] = status_item
- @property - def executable_url(self) -> str: - return self.xa_elem.executableURL() +
[docs] def add_item(self, menu: str, item_name: str, method: Union[Callable[[], None], None] = None, image: Union['XAImage', None] = None, img_width: int = 20, img_height: int = 20): + """Adds an item to a menu, creating the menu if necessary. - @property - def frontmost(self) -> bool: - return self.xa_elem.isActive() + :param menu: The name of the menu to add an item to, or the name of the menu to create + :type menu: str + :param item_name: The name of the item + :type item_name: str + :param method: The method to associate with the item (the method called when the item is clicked) + :type method: Callable[[], None] + :param image: The image for the item, defaults to None + :type image: Union[XAImage, None], optional + :param img_width: The width of image, in pixels, defaults to 30 + :type img_width: int, optional + :param img_height: The height of the image, in pixels, defaults to 30 + :type img_height: int, optional - @frontmost.setter - def frontmost(self, frontmost: bool): - if frontmost is True: - self.xa_elem.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps) + :Example: - @property - def launch_date(self) -> datetime: - return self.xa_elem.launchDate() + >>> import PyXA + >>> menu_bar = PyXA.XAMenuBar() + >>> + >>> menu_bar.add_menu("Menu 1") + >>> menu_bar.add_item(menu="Menu 1", item_name="Item 1", method=lambda : print("Action 1")) + >>> menu_bar.add_item(menu="Menu 1", item_name="Item 2", method=lambda : print("Action 2")) + >>> + >>> menu_bar.add_item(menu="Menu 2", item_name="Item 1", method=lambda : print("Action 1")) + >>> img = PyXA.XAImage("/Users/exampleUser/Downloads/example.jpg") + >>> menu_bar.add_item("Menu 2", "Item 1", lambda : print("Action 1"), image=img, img_width=100) + >>> menu_bar.display() - @property - def localized_name(self) -> str: - return self.xa_elem.localizedName() + .. versionadded:: 0.0.9 + """ + item = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(item_name, 'action:', '') + + if isinstance(image, XAImage): + img = image.xa_elem.copy() + img.setScalesWhenResized_(True) + img.setSize_((img_width, img_height)) + item.setImage_(img) + + if menu not in self._menus: + self.add_menu(menu) + self._menu_items[item_name] = item + self._menus[menu].menu().addItem_(item) + self._methods[item_name] = method
- @property - def owns_menu_bar(self) -> bool: - return self.xa_elem.ownsMenuBar() +
[docs] def set_image(self, item_name: str, image: 'XAImage', img_width: int = 30, img_height: int = 30): + """Sets the image displayed for a menu or menu item. - @property - def process_identifier(self) -> str: - return self.xa_elem.processIdentifier() + :param item_name: The name of the item to update + :type item_name: str + :param image: The image to display + :type image: XAImage + :param img_width: The width of the image, in pixels, defaults to 30 + :type img_width: int, optional + :param img_height: The height of the image, in pixels, defaults to 30 + :type img_height: int, optional -
[docs] def activate(self) -> 'XAApplication': - """Activates the application, bringing its window(s) to the front and launching the application beforehand if necessary. + :Example: Set Image on State Change - :return: A reference to the PyXA application object. - :rtype: XAApplication + >>> import PyXA + >>> current_state = True # On + >>> img_on = PyXA.XAImage("/Users/exampleUser/Documents/on.jpg") + >>> img_off = PyXA.XAImage("/Users/exampleUser/Documents/off.jpg") + >>> menu_bar = PyXA.XAMenuBar() + >>> menu_bar.add_menu("Status", image=img_on) + >>> + >>> def update_state(): + >>> global current_state + >>> if current_state is True: + >>> # ... (Actions for turning off) + >>> menu_bar.set_text("Turn off", "Turn on") + >>> menu_bar.set_image("Status", img_off) + >>> current_state = False + >>> else: + >>> # ... (Actions for turning on) + >>> menu_bar.set_text("Turn off", "Turn off") + >>> menu_bar.set_image("Status", img_on) + >>> current_state = True - .. seealso:: :func:`terminate`, :func:`unhide`, :func:`focus` + menu_bar.add_item("Status", "Turn off", update_state) + menu_bar.display() - .. versionadded:: 0.0.1 + .. versionadded:: 0.0.9 """ - self.xa_elem.activateWithOptions_(AppKit.NSApplicationActivateIgnoringOtherApps) - return self
+ img = image.xa_elem.copy() + img.setScalesWhenResized_(True) + img.setSize_((img_width, img_height)) + if item_name in self._menus: + self._menus[item_name].button().setImage_(img) + elif item_name in self._methods: + self._menu_items[item_name].setImage_(img)
-
[docs] def terminate(self) -> 'XAApplication': - """Quits the application. Synonymous with quit(). +
[docs] def set_text(self, item_name: str, text: str): + """Sets the text displayed for a menu or menu item. - :return: A reference to the PyXA application object. - :rtype: XAApplication + :param item_name: The name of the item to update + :type item_name: str + :param text: The new text to display + :type text: str - :Example: + :Example: Random Emoji Ticker >>> import PyXA - >>> safari = PyXA.application("Safari") - >>> safari.terminate() - - .. seealso:: :func:`quit`, :func:`activate` + >>> import random + >>> import threading + >>> + >>> menu_bar = PyXA.XAMenuBar() + >>> menu_bar.add_menu("Emoji") + >>> + >>> emojis = ["😀", "😍", "🙂", "😎", "🤩", "🤯", "😭", "😱", "😴", "🤒", "😈", "🤠"] + >>> + >>> def update_display(): + >>> while True: + >>> new_emoji = random.choice(emojis) + >>> menu_bar.set_text("Emoji", new_emoji) + >>> sleep(0.25) + >>> + >>> emoji_ticker = threading.Thread(target=update_display) + >>> emoji_ticker.start() + >>> menu_bar.display() - .. versionadded:: 0.0.1 + .. versionadded:: 0.0.9 """ - self.xa_elem.terminate() - return self
- -
[docs] def quit(self) -> 'XAApplication': - """Quits the application. Synonymous with terminate(). + if item_name in self._menus: + self._menus[item_name].setTitle_(text) + elif item_name in self._methods: + self._menu_items[item_name].setTitle_(text) + self._methods[text] = self._methods[item_name]
- :return: A reference to the PyXA application object. - :rtype: XAApplication +
[docs] def display(self): + """Displays the custom menus on the menu bar. :Example: >>> import PyXA - >>> safari = PyXA.application("Safari") - >>> safari.quit() - - .. seealso:: :func:`terminate`, :func:`activate` + >>> mbar = PyXA.XAMenuBar() + >>> mbar.add_menu("🔥") + >>> mbar.display() - .. versionadded:: 0.0.1 + .. versionadded:: 0.0.9 """ - self.xa_elem.terminate() - return self
+ try: + AppHelper.runEventLoop(installInterrupt=True) + except Exception as e: + print(e)
-
[docs] def hide(self) -> 'XAApplication': - """Hides all windows of the application. - :return: A reference to the PyXA application object. - :rtype: XAApplication - :Example: - >>> import PyXA - >>> safari = PyXA.application("Safari") - >>> safari.hide() +############################# +### System / Image Events ### +############################# +# ? Move into separate XAFileSystemBase.py file? +
[docs]class XAEventsApplication(XACanOpenPath): + """A base class for the System and Image events applications. - .. seealso:: :func:`unhide` + .. versionadded:: 0.1.0 + """ +
[docs] class Format(Enum): + """Disk format options. + """ + APPLE_PHOTO = OSType("dfph") + APPLESHARE = OSType("dfas") + AUDIO = OSType("dfau") + HIGH_SIERRA = OSType("dfhs") + ISO_9660 = OSType("fd96") + MACOS_EXTENDED = OSType("dfh+") + MACOS = OSType("dfhf") + MSDOS = OSType("dfms") + NFS = OSType("dfnf") + PRODOS = OSType("dfpr") + QUICKTAKE = OSType("dfqt") + UDF = OSType("dfud") + UFS = OSType("dfuf") + UNKNOWN = OSType("df$$") + WEBDAV = OSType("dfwd")
+ +
[docs]class XADiskItemList(XAList): + """A wrapper around lists of disk items that employs fast enumeration techniques. + + All properties of disk items can be called as methods on the wrapped list, returning a list containing each item's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, object_class = None): + if object_class is None: + object_class = XADiskItem + super().__init__(properties, object_class, filter) - .. versionadded:: 0.0.1 +
[docs] def busy_status(self) -> list['bool']: + """Retrieves the busy status of each disk item in the list. + + .. versionadded:: 0.1.0 """ - self.xa_elem.hide() - return self
+ return list(self.xa_elem.arrayByApplyingSelector_("busyStatus"))
-
[docs] def unhide(self) -> 'XAApplication': - """Unhides (reveals) all windows of the application, but does not does not activate them. +
[docs] def container(self) -> 'XADiskItemList': + """Retrieves the containing folder or disk of each disk item in the list. - :return: A reference to the PyXA application object. - :rtype: XAApplication + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("container") + return self._new_element(ls, XADiskItemList)
- :Example: +
[docs] def creation_date(self) -> list['datetime']: + """Retrieves the creation date of each disk item in the list. - >>> import PyXA - >>> safari = PyXA.application("Safari") - >>> safari.unhide() + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("creationDate"))
- .. seealso:: :func:`hide` +
[docs] def displayed_name(self) -> list['str']: + """Retrieves the displayed name of each disk item in the list. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - self.xa_elem.unhide() - return self
+ return list(self.xa_elem.arrayByApplyingSelector_("displayedName"))
-
[docs] def focus(self) -> 'XAApplication': - """Hides the windows of all applications except this one. +
[docs] def id(self) -> list['str']: + """Retrieves the unique ID of each disk item in the list. - :return: A reference to the PyXA application object. - :rtype: XAApplication + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
- :Example: +
[docs] def modification_date(self) -> list['datetime']: + """Retrieves the last modified date of each disk item in the list. - >>> import PyXA - >>> safari = PyXA.application("Safari") - >>> safari.focus() + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modificationDate"))
- .. seealso:: :func:`unfocus` +
[docs] def name(self) -> list['str']: + """Retrieves the name of each disk item in the list. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - for app in self.xa_wksp.runningApplications(): - if app.localizedName() != self.xa_elem.localizedName(): - app.hide() - else: - app.unhide() - return self
+ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def unfocus(self) -> 'XAApplication': - """Unhides (reveals) the windows of all other applications, but does not activate them. +
[docs] def name_extension(self) -> list['str']: + """Retrieves the name extension of each disk item in the list. - :return: A reference to the PyXA application object. - :rtype: XAApplication + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("nameExtension"))
- :Example: +
[docs] def package_folder(self) -> list['bool']: + """Retrieves the package folder status of each disk item in the list. - >>> import PyXA - >>> safari = PyXA.application("Safari") - >>> safari.unfocus() + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("packageFolder"))
- .. seealso:: :func:`focus` +
[docs] def path(self) -> list['XAPath']: + """Retrieves the file system path of each disk item in the list. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - for app in self.xa_wksp.runningApplications(): - app.unhide() - return self
- - def _get_processes(self, processes): - for process in self.xa_sevt.processes(): - processes.append(process) + ls = self.xa_elem.arrayByApplyingSelector_("path") + return [XAPath(x) for x in ls]
-
[docs] def windows(self, filter: dict = None) -> List['XAWindow']: - return self.xa_prcs.windows(filter)
+
[docs] def physical_size(self) -> list['int']: + """Retrieves the actual disk space used by each disk item in the list. - @property - def front_window(self) -> 'XAWindow': - return self.xa_prcs.front_window + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("physicalSize"))
-
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': - return self._new_element(self.xa_prcs.xa_elem.menuBars(), XAUIMenuBarList, filter)
+
[docs] def posix_path(self) -> list[XAPath]: + """Retrieves the POSIX file system path of each disk item in the list. -
[docs] def get_clipboard_representation(self) -> List[Union[str, AppKit.NSURL, AppKit.NSImage]]: - """Gets a clipboard-codable representation of the application. + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("POSIXPath") + return [XAPath(x) for x in ls]
- When the clipboard content is set to an application, three items are placed on the clipboard: - 1. The application's name - 2. The URL to the application bundle - 3. The application icon +
[docs] def size(self) -> list['int']: + """Retrieves the logical size of each disk item in the list. - After copying an application to the clipboard, pasting will have the following effects: - - In Finder: Paste a copy of the application bundle in the current directory - - In Terminal: Paste the name of the application followed by the path to the application - - In iWork: Paste the application name - - In Safari: Paste the application name - - In Notes: Attach a copy of the application bundle to the active note - The pasted content may different for other applications. + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("size"))
- :return: The clipboard-codable representation - :rtype: List[Union[str, AppKit.NSURL, AppKit.NSImage]] +
[docs] def url(self) -> list['XAURL']: + """Retrieves the URL of each disk item in the list. - .. versionadded:: 0.0.8 + .. versionadded:: 0.1.0 """ - return [self.xa_elem.localizedName(), self.xa_elem.bundleURL(), self.xa_elem.icon()]
- + ls = self.xa_elem.arrayByApplyingSelector_("URL") + return [XAURL(x) for x in ls]
+
[docs] def visible(self) -> list['bool']: + """Retrieves the visible status of each item in the list. + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("visible"))
-
[docs]class XASound(XAObject, XAClipboardCodable): - """A wrapper class for NSSound objects and associated methods. +
[docs] def volume(self) -> list['str']: + """Retrieves the volume on which each item in the list resides. - .. versionadded:: 0.0.1 - """ - def __init__(self, sound_file: Union[str, AppKit.NSURL]): - if isinstance(sound_file, str): - if "/" in sound_file: - sound_file = XAPath(sound_file) - else: - sound_file = XAPath("/System/Library/Sounds/" + sound_file + ".aiff") - self.file = sound_file - self.xa_elem = AppKit.NSSound.alloc() - self.xa_elem.initWithContentsOfURL_byReference_(sound_file.xa_elem, True) + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("volume"))
-
[docs] def play(self) -> 'XASound': - """Plays the sound from the beginning. +
[docs] def by_busy_status(self, busy_status: bool) -> 'XADiskItem': + """Retrieves item whose busy status matches the given boolean value. - :return: A reference to this sound object. - :rtype: XASound + .. versionadded:: 0.1.0 + """ + return self.by_property("busyStatus", busy_status)
- :Example: +
[docs] def by_container(self, container: 'XADiskItem') -> 'XADiskItem': + """Retrieves item whose container matches the given disk item. - >>> import PyXA - >>> glass_sound = PyXA.sound("Glass") - >>> glass_sound.play() + .. versionadded:: 0.1.0 + """ + return self.by_property("container", container.xa_elem)
- .. seealso:: :func:`pause`, :func:`stop` +
[docs] def by_creation_date(self, creation_date: datetime) -> 'XADiskItem': + """Retrieves item whose creation date matches the given date. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - self.xa_elem.stop() - self.xa_elem.play() - time.sleep(self.xa_elem.duration()) - return self
+ return self.by_property("creationDate", creation_date)
-
[docs] def pause(self) -> 'XASound': - """Pauses the sound. +
[docs] def by_displayed_name(self, displayed_name: str) -> 'XADiskItem': + """Retrieves item whose displayed name matches the given name. - :return: A reference to this sound object. - :rtype: XASound + .. versionadded:: 0.1.0 + """ + return self.by_property("displayedName", displayed_name)
- :Example: +
[docs] def by_id(self, id: str) -> 'XADiskItem': + """Retrieves item whose ID matches the given ID. - >>> import PyXA - >>> glass_sound = PyXA.sound("Glass") - >>> glass_sound.pause() + .. versionadded:: 0.1.0 + """ + return self.by_property("id", id)
- .. seealso:: :func:`resume`, :func:`stop` +
[docs] def by_modification_date(self, modification_date: datetime) -> 'XADiskItem': + """Retrieves item whose date matches the given date. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - self.xa_elem.pause() - return self
+ return self.by_property("modificationDate", modification_date)
-
[docs] def resume(self) -> 'XASound': - """Plays the sound starting from the time it was last paused at. +
[docs] def by_name(self, name: str) -> 'XADiskItem': + """Retrieves item whose name matches the given name. - :return: A reference to this sound object. - :rtype: XASound + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
- :Example: +
[docs] def by_name_extension(self, name_extension: str) -> 'XADiskItem': + """Retrieves item whose name extension matches the given extension. - >>> import PyXA - >>> glass_sound = PyXA.sound("Glass") - >>> glass_sound.resume() + .. versionadded:: 0.1.0 + """ + return self.by_property("nameExtension", name_extension)
- .. seealso:: :func:`pause`, :func:`play` +
[docs] def by_package_folder(self, package_folder: bool) -> 'XADiskItem': + """Retrieves item whose package folder status matches the given boolean value. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - self.xa_elem.resume() - return self
+ return self.by_property("packageFolder", package_folder)
-
[docs] def stop(self) -> 'XASound': - """Stops playback of the sound and rewinds it to the beginning. +
[docs] def by_path(self, path: Union[XAPath, str]) -> 'XADiskItem': + """Retrieves item whose path matches the given path. - :return: A reference to this sound object. - :rtype: XASound + .. versionadded:: 0.1.0 + """ + if isinstance(path, XAPath): + path = path.path + return self.by_property("path", path)
- :Example: +
[docs] def by_physical_size(self, physical_size: int) -> 'XADiskItem': + """Retrieves item whose physical size matches the given size. - >>> import PyXA - >>> glass_sound = PyXA.sound("Glass") - >>> glass_sound.stop() + .. versionadded:: 0.1.0 + """ + return self.by_property("physicalSize", physical_size)
- .. seealso:: :func:`pause`, :func:`play` +
[docs] def by_posix_path(self, posix_path: Union[XAPath, str]) -> 'XADiskItem': + """Retrieves item whose POSIX path matches the given POSIX path. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - self.xa_elem.stop() - return self
+ if isinstance(posix_path, XAPath): + posix_path = posix_path.path + return self.by_property("POSIXPath", posix_path)
-
[docs] def set_volume(self, volume: int) -> 'XASound': - """Sets the volume of the sound. +
[docs] def by_size(self, size: int) -> 'XADiskItem': + """Retrieves item whose size matches the given size. - :param volume: The desired volume of the sound in the range [0.0, 1.0]. - :type volume: int - :return: A reference to this sound object. - :rtype: XASound + .. versionadded:: 0.1.0 + """ + return self.by_property("size", size)
- :Example: +
[docs] def by_url(self, url: XAURL) -> 'XADiskItem': + """Retrieves the item whose URL matches the given URL. - >>> import PyXA - >>> glass_sound = PyXA.sound("Glass") - >>> glass_sound.set_volume(1.0) + .. versionadded:: 0.1.0 + """ + return self.by_property("URL", url.xa_elem)
- .. seealso:: :func:`volume` +
[docs] def by_visible(self, visible: bool) -> 'XADiskItem': + """Retrieves the item whose visible status matches the given boolean value. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - self.xa_elem.setVolume_(volume) - return self
+ return self.by_property("visible", visible)
-
[docs] def volume(self) -> float: - """Returns the current volume of the sound. +
[docs] def by_volume(self, volume: str) -> 'XADiskItem': + """Retrieves the item whose volume matches the given volume. - :return: The volume level of the sound. - :rtype: int + .. versionadded:: 0.1.0 + """ + return self.by_property("volume", volume)
- :Example: + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
- >>> import PyXA - >>> glass_sound = PyXA.sound("Glass") - >>> print(glass_sound.volume()) - 1.0 +
[docs]class XADiskItem(XAObject, XAPathLike): + """An item stored in the file system. - .. seealso:: :func:`set_volume` + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) - .. versionadded:: 0.0.1 + @property + def busy_status(self) -> 'bool': + """Whether the disk item is busy. + + .. versionadded:: 0.1.0 """ - return self.xa_elem.volume()
+ return self.xa_elem.busyStatus() -
[docs] def loop(self, times: int) -> 'XASound': - """Plays the sound the specified number of times. + @property + def container(self) -> 'XADiskItem': + """The folder or disk which has this disk item as an element. - :param times: The number of times to loop the sound. - :type times: int - :return: A reference to this sound object. - :rtype: XASound + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.container(), XADiskItem) - :Example: + @property + def creation_date(self) -> 'datetime': + """The date on which the disk item was created. - >>> import PyXA - >>> glass_sound = PyXA.sound("Glass") - >>> glass_sound.loop(10) + .. versionadded:: 0.1.0 + """ + return self.xa_elem.creationDate() - .. versionadded:: 0.0.1 + @property + def displayed_name(self) -> 'str': + """The name of the disk item as displayed in the User Interface. + + .. versionadded:: 0.1.0 """ - self.xa_elem.setLoops_(times) - self.xa_elem.play() - time.sleep(self.xa_elem.duration() * times) - self.xa_elem.stop() - self.xa_elem.setLoops_(0) - return self
+ return self.xa_elem.displayedName() -
[docs] def get_clipboard_representation(self) -> List[Union[AppKit.NSSound, AppKit.NSURL, str]]: - """Gets a clipboard-codable representation of the sound. + @property + def id(self) -> 'str': + """The unique ID of the disk item. - When the clipboard content is set to a sound, the raw sound data, the associated file URL, and the path string of the file are added to the clipboard. + .. versionadded:: 0.1.0 + """ + return self.xa_elem.id() - :return: The clipboard-codable form of the sound - :rtype: Any + @property + def modification_date(self) -> 'datetime': + """The date on which the disk item was last modified. - .. versionadded:: 0.0.8 + .. versionadded:: 0.1.0 """ - return [self.xa_elem, self.file.xa_elem, self.file.xa_elem.path()]
+ return self.xa_elem.modificationDate() + @property + def name(self) -> 'str': + """The name of the disk item. + .. versionadded:: 0.1.0 + """ + return self.xa_elem.name() + @property + def name_extension(self) -> 'str': + """The extension portion of the name. -
[docs]class XACommandDetector(XAObject): - """A command-based query detector. + .. versionadded:: 0.1.0 + """ + return self.xa_elem.nameExtension() - .. versionadded:: 0.0.9 - """ - def __init__(self, command_function_map: Union[Dict[str, Callable[[], Any]], None] = None): - """Creates a command detector object. + @property + def package_folder(self) -> 'bool': + """Whether the disk item is a package. - :param command_function_map: A dictionary mapping command strings to function objects - :type command_function_map: Dict[str, Callable[[], Any]] + .. versionadded:: 0.1.0 + """ + return self.xa_elem.packageFolder() - .. versionadded:: 0.0.9 + @property + def path(self) -> 'XAPath': + """The file system path of the disk item. + + .. versionadded:: 0.1.0 """ - self.command_function_map = command_function_map or {} #: The dictionary of commands and corresponding functions to run upon detection + return XAPath(self.xa_elem.path()) -
[docs] def on_detect(self, command: str, function: Callable[[], Any]): - """Adds or replaces a command to listen for upon calling :func:`listen`, and associates the given function with that command. + @property + def physical_size(self) -> 'int': + """The actual space used by the disk item on disk. - :param command: The command to listen for - :type command: str - :param function: The function to call when the command is heard - :type function: Callable[[], Any] + .. versionadded:: 0.1.0 + """ + return self.xa_elem.physicalSize() - :Example: + @property + def posix_path(self) -> XAPath: + """The POSIX file system path of the disk item. - >>> detector = PyXA.XACommandDetector() - >>> detector.on_detect("go to google", PyXA.XAURL("http://google.com").open) - >>> detector.listen() + .. versionadded:: 0.1.0 + """ + return XAPath(self.xa_elem.POSIXPath()) - .. versionadded:: 0.0.9 + @property + def size(self) -> 'int': + """The logical size of the disk item. + + .. versionadded:: 0.1.0 """ - self.command_function_map[command] = function
+ return self.xa_elem.size() -
[docs] def listen(self) -> Any: - """Begins listening for the specified commands. + @property + def url(self) -> 'XAURL': + """The URL of the disk item. - :return: The execution return value of the corresponding command function - :rtype: Any + .. versionadded:: 0.1.0 + """ + return XAURL(self.xa_elem.URL()) - :Example: + @property + def visible(self) -> 'bool': + """Whether the disk item is visible. - >>> import PyXA - >>> PyXA.speak("What app do you want to open?") - >>> PyXA.XACommandDetector({ - >>> "safari": PyXA.application("Safari").activate, - >>> "messages": PyXA.application("Messages").activate, - >>> "shortcuts": PyXA.application("Shortcuts").activate, - >>> "mail": PyXA.application("Mail").activate, - >>> "calendar": PyXA.application("Calendar").activate, - >>> "notes": PyXA.application("Notes").activate, - >>> "music": PyXA.application("Music").activate, - >>> "tv": PyXA.application("TV").activate, - >>> "pages": PyXA.application("Pages").activate, - >>> "numbers": PyXA.application("Numbers").activate, - >>> "keynote": PyXA.application("Keynote").activate, - >>> }).listen() + .. versionadded:: 0.1.0 + """ + return self.xa_elem.visible() - .. versionadded:: 0.0.9 + @property + def volume(self) -> 'str': + """The volume on which the disk item resides. + + .. versionadded:: 0.1.0 """ - command_function_map = self.command_function_map - return_value = None - class NSSpeechRecognizerDelegate(AppKit.NSObject): - def speechRecognizer_didRecognizeCommand_(self, recognizer, cmd): - return_value = command_function_map[cmd]() - AppHelper.stopEventLoop() + return self.xa_elem.volume() - recognizer = AppKit.NSSpeechRecognizer.alloc().init() - recognizer.setCommands_(list(command_function_map.keys())) - recognizer.setBlocksOtherRecognizers_(True) - recognizer.setDelegate_(NSSpeechRecognizerDelegate.alloc().init().retain()) - recognizer.startListening() - AppHelper.runConsoleEventLoop() +
[docs] def get_path_representation(self) -> XAPath: + return self.posix_path
- return return_value
+ def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
-
[docs]class XASpeechRecognizer(XAObject): - """A rule-based query detector. +
[docs]class XAAliasList(XADiskItemList): + """A wrapper around lists of aliases that employs fast enumeration techniques. - .. versionadded:: 0.0.9 - """ - def __init__(self, finish_conditions: Union[None, Dict[Callable[[str], bool], Callable[[str], bool]]] = None): - """Creates a speech recognizer object. + All properties of aliases can be called as methods on the wrapped list, returning a list containing each alias' value for the property. - By default, with no other rules specified, the Speech Recognizer will timeout after 10 seconds once :func:`listen` is called. + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAAlias) - :param finish_conditions: A dictionary of rules and associated methods to call when a rule evaluates to true, defaults to None - :type finish_conditions: Union[None, Dict[Callable[[str], bool], Callable[[str], bool]]], optional +
[docs] def creator_type(self) -> list['str']: + """Retrieves the OSType identifying the application that created each alias in the list - .. versionadded:: 0.0.9 + .. versionadded:: 0.1.0 """ - default_conditions = { - lambda x: self.time_elapsed > timedelta(seconds = 10): lambda x: self.spoken_query, - } - self.finish_conditions: Callable[[str], bool] = finish_conditions or default_conditions #: A dictionary of rules and associated methods to call when a rule evaluates to true - self.spoken_query: str = "" #: The recognized spoken input - self.start_time: datetime #: The time that the Speech Recognizer begins listening - self.time_elapsed: timedelta #: The amount of time passed since the start time + return list(self.xa_elem.arrayByApplyingSelector_("creatorType"))
- def __prepare(self): - # Request microphone access if we don't already have it - Speech.SFSpeechRecognizer.requestAuthorization_(None) +
[docs] def default_application(self) -> 'XADiskItemList': + """Retrieves the applications that will launch if each alias in the list is opened. - # Set up audio session - self.audio_session = AVFoundation.AVAudioSession.sharedInstance() - self.audio_session.setCategory_mode_options_error_(AVFoundation.AVAudioSessionCategoryRecord, AVFoundation.AVAudioSessionModeMeasurement, AVFoundation.AVAudioSessionCategoryOptionDuckOthers, None) - self.audio_session.setActive_withOptions_error_(True, AVFoundation.AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation, None) + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("defaultApplication") + return self._new_element(ls, XADiskItemList)
- # Set up recognition request - self.recognizer = Speech.SFSpeechRecognizer.alloc().init() - self.recognition_request = Speech.SFSpeechAudioBufferRecognitionRequest.alloc().init() - self.recognition_request.setShouldReportPartialResults_(True) +
[docs] def file_type(self) -> list['str']: + """Retrieves the OSType identifying the type of data contained in each alias in the list. - # Set up audio engine - self.audio_engine = AVFoundation.AVAudioEngine.alloc().init() - self.input_node = self.audio_engine.inputNode() - recording_format = self.input_node.outputFormatForBus_(0) - self.input_node.installTapOnBus_bufferSize_format_block_(0, 1024, recording_format, - lambda buffer, _when: self.recognition_request.appendAudioPCMBuffer_(buffer)) - self.audio_engine.prepare() - self.audio_engine.startAndReturnError_(None) + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("fileType"))
-
[docs] def on_detect(self, rule: Callable[[str], bool], method: Callable[[str], bool]): - """Sets the given rule to call the specified method if a spoken query passes the rule. +
[docs] def kind(self) -> list['str']: + """Retrieves the kind of each alias in the list. - :param rule: A function that takes the spoken query as a parameter and returns a boolean value depending on whether the query passes a desired rule - :type rule: Callable[[str], bool] - :param method: A function that takes the spoken query as a parameter and acts on it - :type method: Callable[[str], bool] + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("kind"))
- .. versionadded:: 0.0.9 +
[docs] def product_version(self) -> list['str']: + """Retrieves the product version of each alias in the list. + + .. versionadded:: 0.1.0 """ - self.finish_conditions[rule] = method
+ return list(self.xa_elem.arrayByApplyingSelector_("productVersion"))
-
[docs] def listen(self) -> Any: - """Begins listening for a query until a rule returns True. +
[docs] def short_version(self) -> list['str']: + """Retrieves the short version of the application bundle referenced by each alias in the list. - :return: The value returned by the method invoked upon matching some rule - :rtype: Any + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("shortVersion"))
- .. versionadded:: 0.0.9 +
[docs] def stationery(self) -> list['bool']: + """Retrieves the stationery status of each alias in the list. + + .. versionadded:: 0.1.0 """ - self.start_time = datetime.now() - self.time_elapsed = None - self.__prepare() + return list(self.xa_elem.arrayByApplyingSelector_("stationery"))
- old_self = self - def detect_speech(transcription, error): - if error is not None: - print("Failed to detect speech. Error: ", error) - else: - old_self.spoken_query = transcription.bestTranscription().formattedString() - print(old_self.spoken_query) +
[docs] def type_identifier(self) -> list['str']: + """Retrieves the type identifier of each alias in the list. - recognition_task = self.recognizer.recognitionTaskWithRequest_resultHandler_(self.recognition_request, detect_speech) - while self.spoken_query == "" or not any(x(self.spoken_query) for x in self.finish_conditions): - self.time_elapsed = datetime.now() - self.start_time - AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.5)) + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("typeIdentifier"))
- self.audio_engine.stop() - for rule, method in self.finish_conditions.items(): - if rule(self.spoken_query): - return method(self.spoken_query)
+
[docs] def version(self) -> list['str']: + """Retrieves the version of the application bundle referenced by each alias in the list. + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("version"))
+
[docs] def by_creator_type(self, creator_type: str) -> 'XAAlias': + """Retrieves the alias whose creator type matches the given creator type. + .. versionadded:: 0.1.0 + """ + return self.by_property("creatorType", creator_type)
-
[docs]class XASpotlight(XAObject): - """A Spotlight query for files on the disk. +
[docs] def by_default_application(self, default_application: 'XADiskItem') -> 'XAAlias': + """Retrieves the alias whose default application matches the given application. - .. versionadded:: 0.0.9 - """ - def __init__(self, *query: List[Any]): - self.query: List[Any] = query #: The query terms to search - self.timeout: int = 10 #: The amount of time in seconds to timeout the search after - self.predicate: Union[str, XAPredicate] = None #: The predicate to filter search results by - self.results: List[XAPath] #: The results of the search - self.__results = None + .. versionadded:: 0.1.0 + """ + return self.by_property("defaultApplication", default_application.xa_elem)
- self.query_object = AppKit.NSMetadataQuery.alloc().init() - nc = AppKit.NSNotificationCenter.defaultCenter() - nc.addObserver_selector_name_object_(self, '_queryNotification:', None, self.query_object) +
[docs] def by_file_type(self, file_type: str) -> 'XAAlias': + """Retrieves the alias whose file type matches the given file type. - @property - def results(self) -> List['XAPath']: - if len(self.query) == 0 and self.predicate is None: - return [] - self.run() - total_time = 0 - while self.__results is None and total_time < self.timeout: - AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.01)) - total_time += 0.01 - if self.__results is None: - return [] - return self.__results + .. versionadded:: 0.1.0 + """ + return self.by_property("fileType", file_type)
-
[docs] def run(self): - """Runs the search. +
[docs] def by_kind(self, kind: str) -> 'XAAlias': + """Retrieves the alias whose kind matches the given kind. - :Example: + .. versionadded:: 0.1.0 + """ + return self.by_property("kind", kind)
- >>> import PyXA - >>> from datetime import date, datetime, time - >>> date1 = datetime.combine(date(2022, 5, 17), time(0, 0, 0)) - >>> date2 = datetime.combine(date(2022, 5, 18), time(0, 0, 0)) - >>> search = PyXA.XASpotlight(date1, date2) - >>> print(search.results) - [<<class 'PyXA.XABase.XAPath'>file:///Users/exampleUser/Downloads/>, <<class 'PyXA.XABase.XAPath'>file:///Users/exampleUser/Downloads/Example.txt>, ...] +
[docs] def by_product_version(self, product_version: str) -> 'XAAlias': + """Retrieves the alias whose product version matches the given version. - .. versionadded:: 0.0.9 + .. versionadded:: 0.1.0 """ - if self.predicate is not None: - # Search with custom predicate - if isinstance(self.predicate, XAPredicate): - self.predicate = self.predicate.get_clipboard_representation() - self.__search_with_predicate(self.predicate) - elif len(self.query) == 1 and isinstance(self.query[0], datetime): - # Search date + or - 24 hours - self.__search_by_date(self.query) - elif len(self.query) == 2 and isinstance(self.query[0], datetime) and isinstance(self.query[1], datetime): - # Search date range - self.__search_by_date_range(self.query[0], self.query[1]) - elif all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query): - # Search matching multiple strings - self.__search_by_strs(self.query) - elif isinstance(self.query[0], datetime) and all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query[1:]): - # Search by date and string - self.__search_by_date_strings(self.query[0], self.query[1:]) - elif isinstance(self.query[0], datetime) and isinstance(self.query[1], datetime) and all(isinstance(x, str) or isinstance(x, int) or isinstance(x, float) for x in self.query[2:]): - # Search by date range and string - self.__search_by_date_range_strings(self.query[0], self.query[1], self.query[2:]) + return self.by_property("productVersion", product_version)
- AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.01))
+
[docs] def by_short_version(self, short_version: str) -> 'XAAlias': + """Retrieves the alias whose short version matches the given text. -
[docs] def show_in_finder(self): - """Shows the search in Finder. This might not reveal the same search results. + .. versionadded:: 0.1.0 + """ + return self.by_property("shortVersion", short_version)
- .. versionadded:: 0.0.9 +
[docs] def by_stationery(self, stationery: bool) -> 'XAAlias': + """Retrieves the alias whose stationery status matches the given boolean value. + + .. versionadded:: 0.1.0 """ - AppKit.NSWorkspace.sharedWorkspace().showSearchResultsForQueryString_(str(self.query))
+ return self.by_property("stationery", stationery)
- def __search_by_strs(self, terms: Tuple[str]): - expanded_terms = [[x]*3 for x in terms] - expanded_terms = [x for sublist in expanded_terms for x in sublist] - format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) - self.__search_with_predicate(format[:-5], *expanded_terms) +
[docs] def by_type_identifier(self, type_identifier: str) -> 'XAAlias': + """Retrieves the alias whose type identifier matches the given type identifier. - def __search_by_date(self, date: datetime): - self.__search_with_predicate(f"((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@))", *[date - timedelta(hours=12), date + timedelta(hours=12)]*5) + .. versionadded:: 0.1.0 + """ + return self.by_property("typeIdentifier", type_identifier)
- def __search_by_date_range(self, date1: datetime, date2: datetime): - self.__search_with_predicate(f"((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@))", *[date1, date2]*5) +
[docs] def by_version(self, version: str) -> 'XAAlias': + """Retrieves the alias whose version matches the given version. - def __search_by_date_strings(self, date: datetime, terms: Tuple[str]): - expanded_terms = [[x]*3 for x in terms] - expanded_terms = [x for sublist in expanded_terms for x in sublist] - format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) - format += "(((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@)))" - self.__search_with_predicate(format, *expanded_terms, *[date - timedelta(hours=12), date + timedelta(hours=12)]*5) + .. versionadded:: 0.1.0 + """ + return self.by_property("version", version)
- def __search_by_date_range_strings(self, date1: datetime, date2: datetime, terms: Tuple[str]): - expanded_terms = [[x]*3 for x in terms] - expanded_terms = [x for sublist in expanded_terms for x in sublist] - format = "((kMDItemDisplayName CONTAINS %@) OR (kMDItemTextContent CONTAINS %@) OR (kMDItemFSName CONTAINS %@)) AND " * len(terms) - format += "(((kMDItemContentCreationDate > %@) AND (kMDItemContentCreationDate < %@)) OR ((kMDItemContentModificationDate > %@) AND (kMDItemContentModificationDate < %@)) OR ((kMDItemFSCreationDate > %@) AND (kMDItemFSCreationDate < %@)) OR ((kMDItemFSContentChangeDate > %@) AND (kMDItemFSContentChangeDate < %@)) OR ((kMDItemDateAdded > %@) AND (kMDItemDateAdded < %@)))" - self.__search_with_predicate(format, *expanded_terms, *[date1, date2]*5) +
[docs]class XAAlias(XADiskItem): + """An alias in the file system. - def __search_with_predicate(self, predicate_format: str, *args: List[Any]): - predicate = AppKit.NSPredicate.predicateWithFormat_(predicate_format, *args) - self.query_object.setPredicate_(predicate) - self.query_object.startQuery() + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) - def _queryNotification_(self, notification): - if notification.name() == AppKit.NSMetadataQueryDidFinishGatheringNotification: - self.query_object.stopQuery() - results = notification.object().results() - self.__results = [XAPath(x.valueForAttribute_(AppKit.NSMetadataItemPathKey)) for x in results]
+ @property + def creator_type(self) -> 'str': + """The OSType identifying the application that created the alias. + .. versionadded:: 0.1.0 + """ + return self.xa_elem.creatorType() + @property + def default_application(self) -> 'XADiskItem': + """The application that will launch if the alias is opened. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.defaultApplication(), XADiskItem) -
[docs]class XAURL(XAObject, XAClipboardCodable): - """A URL using any scheme recognized by the system. This can be a file URL. + @property + def file_type(self) -> 'str': + """The OSType identifying the type of data contained in the alias. - .. versionadded:: 0.0.5 - """ - def __init__(self, url: Union[str, AppKit.NSURL]): - super().__init__() - self.parameters: str #: The query parameters of the URL - self.scheme: str #: The URI scheme of the URL - self.fragment: str #: The fragment identifier following a # symbol in the URL - self.port: int #: The port that the URL points to - self.html: element.tag #: The html of the URL - self.title: str #: The title of the URL - self.soup: BeautifulSoup = None #: The bs4 object for the URL, starts as None until a bs4-related action is made - self.url: str = url #: The string form of the URL - if isinstance(url, str): - url = url.replace(" ", "%20") - url = AppKit.NSURL.alloc().initWithString_(url) - self.xa_elem = url + .. versionadded:: 0.1.0 + """ + return self.xa_elem.fileType() @property - def base_url(self) -> str: - return self.xa_elem.host() + def kind(self) -> 'str': + """The kind of alias, as shown in Finder. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.kind() @property - def parameters(self) -> str: - return self.xa_elem.query() + def product_version(self) -> 'str': + """The version of the product (visible at the top of the "Get Info" window). + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.productVersion() @property - def scheme(self) -> str: - return self.xa_elem.scheme() + def short_version(self) -> 'str': + """The short version of the application bundle referenced by the alias. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.shortVersion() @property - def fragment(self) -> str: - return self.xa_elem.fragment() + def stationery(self) -> 'bool': + """Whether the alias is a stationery pad. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.stationery() @property - def html(self) -> element.Tag: - if self.soup is None: - self.__get_soup() - return self.soup.html + def type_identifier(self) -> 'str': + """The type identifier of the alias. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.typeIdentifier() @property - def title(self) -> str: - if self.soup is None: - self.__get_soup() - return self.soup.title.text + def version(self) -> 'str': + """The version of the application bundle referenced by the alias (visible at the bottom of the "Get Info" window). - def __get_soup(self): - req = requests.get(str(self.xa_elem)) - self.soup = BeautifulSoup(req.text, "html.parser") + .. versionadded:: 0.1.0 + """ + return self.xa_elem.version() -
[docs] def open(self): - """Opens the URL in the appropriate default application. +
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.5 + .. versionadded:: 0.1.0 """ - AppKit.NSWorkspace.sharedWorkspace().openURL_(self.xa_elem)
+ self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
-
[docs] def extract_text(self) -> List[str]: - """Extracts the visible text from the webpage that the URL points to. +
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. - :return: The list of extracted lines of text - :rtype: List[str] + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
- .. versionadded:: 0.0.8 +
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 """ - if self.soup is None: - self.__get_soup() - return self.soup.get_text().splitlines()
+ self._new_element(self.xa_elem.files(), XAFileList, filter)
-
[docs] def extract_images(self) -> List['XAImage']: - """Extracts all images from HTML of the webpage that the URL points to. +
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. - :return: The list of extracted images - :rtype: List[XAImage] + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.filePackages(), XAFilePackageList, filter)
- .. versionadded:: 0.0.8 +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 """ - data = AppKit.NSData.alloc().initWithContentsOfURL_(AppKit.NSURL.URLWithString_(str(self.xa_elem))) - image = AppKit.NSImage.alloc().initWithData_(data) + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
- if image is not None: - image_object = XAImage(image, name = self.xa_elem.pathComponents()[-1]) - return [image_object] - else: - if self.soup is None: - self.__get_soup() - images = self.soup.findAll("img") - image_objects = [] - for image in images: - image_src = image["src"] - if image_src.startswith("/"): - image_src = str(self) + str(image["src"]) - data = AppKit.NSData.alloc().initWithContentsOfURL_(AppKit.NSURL.URLWithString_(image_src)) - image = AppKit.NSImage.alloc().initWithData_(data) - if image is not None: - image_object = XAImage(image, name = XAURL(image_src).xa_elem.pathComponents()[-1]) - image_objects.append(image_object) - return image_objects
+
[docs]class XADiskList(XADiskItemList): + """A wrapper around lists of disks that employs fast enumeration techniques. -
[docs] def get_clipboard_representation(self) -> List[Union[AppKit.NSURL, str]]: - """Gets a clipboard-codable representation of the URL. + All properties of disks can be called as methods on the wrapped list, returning a list containing each disk's value for the property. - When the clipboard content is set to a URL, the raw URL data and the string representation of the URL are added to the clipboard. + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XADisk) - :return: The clipboard-codable form of the URL - :rtype: Any +
[docs] def capacity(self) -> list['float']: + """Retrieves the total number of bytes (free or used) on each disk in the list. - .. versionadded:: 0.0.8 + .. versionadded:: 0.1.0 """ - return [self.xa_elem, str(self.xa_elem)]
+ return list(self.xa_elem.arrayByApplyingSelector_("capacity"))
- def __repr__(self): - return "<" + str(type(self)) + str(self.xa_elem) + ">"
+
[docs] def ejectable(self) -> list['bool']: + """Retrieves the ejectable status of each disk in the list. + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("ejectable"))
+
[docs] def format(self) -> list['XAEventsApplication.Format']: + """Retrieves the file system format of each disk in the list. + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("format") + return [XAEventsApplication.Format(OSType(x.stringValue())) for x in ls]
-
[docs]class XAPath(XAObject, XAClipboardCodable): - """A path to a file on the disk. +
[docs] def free_space(self) -> list['float']: + """Retrieves the number of free bytes left on each disk in the list. - .. versionadded:: 0.0.5 - """ - def __init__(self, path: Union[str, AppKit.NSURL]): - super().__init__() - if isinstance(path, str): - path = AppKit.NSURL.alloc().initFileURLWithPath_(path) - self.xa_elem = path - self.path = path.path() #: The path string without the file:// prefix - self.xa_wksp = AppKit.NSWorkspace.sharedWorkspace() + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("freeSpace"))
-
[docs] def open(self): - """Opens the file in its default application. +
[docs] def ignore_privileges(self) -> list['bool']: + """Retrieves the ignore privileges status for each disk in the list. - .. versionadded: 0.0.5 + .. versionadded:: 0.1.0 """ - self.xa_wksp.openURL_(self.xa_elem)
+ return list(self.xa_elem.arrayByApplyingSelector_("ignorePrivileges"))
-
[docs] def show_in_finder(self): - """Opens a Finder window showing the folder containing this path, with the associated file selected. Synonymous with :func:`select`. +
[docs] def local_volume(self) -> list['bool']: + """Retrieves the local volume status for each disk in the list. - .. versionadded: 0.0.9 + .. versionadded:: 0.1.0 """ - self.select()
+ return list(self.xa_elem.arrayByApplyingSelector_("localVolume"))
-
[docs] def select(self): - """Opens a Finder window showing the folder containing this path, with the associated file selected. Synonymous with :func:`show_in_finder`. +
[docs] def server(self) -> list['str']: + """Retrieves the server on which each disk in the list resides, AFP volumes only. - .. versionadded: 0.0.5 + .. versionadded:: 0.1.0 """ - self.xa_wksp.activateFileViewerSelectingURLs_([self.xa_elem])
+ return list(self.xa_elem.arrayByApplyingSelector_("server"))
-
[docs] def get_clipboard_representation(self) -> List[Union[AppKit.NSURL, str]]: - """Gets a clipboard-codable representation of the path. +
[docs] def startup(self) -> list['bool']: + """Retrieves the startup disk status of each disk in the list. - When the clipboard content is set to a path, the raw file URL data and the string representation of the path are added to the clipboard. + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("startup"))
- :return: The clipboard-codable form of the path - :rtype: Any +
[docs] def zone(self) -> list['str']: + """Retrieves the zone in which each disk's server resides, AFP volumes only. - .. versionadded:: 0.0.8 + .. versionadded:: 0.1.0 """ - return [self.xa_elem, self.xa_elem.path()]
+ return list(self.xa_elem.arrayByApplyingSelector_("zone"))
- def __repr__(self): - return "<" + str(type(self)) + str(self.xa_elem) + ">"
+
[docs] def by_capacity(self, capacity: float) -> 'XADisk': + """Retrieves the disk whose capacity matches the given capacity. + .. versionadded:: 0.1.0 + """ + return self.by_property("capacity", capacity)
+
[docs] def by_ejectable(self, ejectable: bool) -> 'XADisk': + """Retrieves the disk whose ejectable status matches the given boolean value. + .. versionadded:: 0.1.0 + """ + return self.by_property("ejectable", ejectable)
-
[docs]class XAPredicate(XAObject, XAClipboardCodable): - """A predicate used to filter arrays. +
[docs] def by_format(self, format: 'XAEventsApplication.Format') -> 'XADisk': + """Retrieves the disk whose format matches the given format. - .. versionadded:: 0.0.4 - """ - def __init__(self): - self.keys: List[str] = [] - self.operators: List[str] = [] - self.values: List[str] = [] + .. versionadded:: 0.1.0 + """ + return self.by_property("format", format.value)
-
[docs] def from_dict(self, ref_dict: dict) -> 'XAPredicate': - """Populates the XAPredicate object from the supplied dictionary. +
[docs] def by_free_space(self, free_space: float) -> 'XADisk': + """Retrieves the disk whose free space matches the given amount. - The predicate will use == for all comparisons. + .. versionadded:: 0.1.0 + """ + return self.by_property("freeSpace", free_space)
- :param ref_dict: A specification of key, value pairs - :type ref_dict: dict - :return: The populated predicate object - :rtype: XAPredicate +
[docs] def by_ignore_privileges(self, ignore_privileges: bool) -> 'XADisk': + """Retrieves the disk whose ignore privileges status matches the given boolean value. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - for key, value in ref_dict.items(): - self.keys.append(key) - self.operators.append("==") - self.values.append(value) - return self
+ return self.by_property("ignorePrivileges", ignore_privileges)
-
[docs] def from_args(self, *args) -> 'XAPredicate': - """Populates the XAPredicate object from the supplied key, value argument pairs. +
[docs] def by_local_volume(self, local_volume: bool) -> 'XADisk': + """Retrieves the disk whose local volume status matches the given boolean value. - The number of keys and values must be equal. The predicate will use == for all comparisons. + .. versionadded:: 0.1.0 + """ + return self.by_property("localVolume", local_volume)
- :raises InvalidPredicateError: Raised when the number of keys does not match the number of values - :return: The populated predicate object - :rtype: XAPredicate +
[docs] def by_server(self, server: str) -> 'XADisk': + """Retrieves the disk whose server matches the given server. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - arg_num = len(args) - if arg_num % 2 != 0: - raise InvalidPredicateError("The number of keys and values must be equal; the number of arguments must be an even number.") - - for index, value in enumerate(args): - if index % 2 == 0: - self.keys.append(value) - self.operators.append("==") - self.values.append(args[index + 1]) - return self
+ return self.by_property("server", server)
-
[docs] def evaluate(self, target: AppKit.NSArray) -> AppKit.NSArray: - """Evaluates the predicate on the given array. +
[docs] def by_startup(self, startup: bool) -> 'XADisk': + """Retrieves the disk whose startup status matches the given boolean value. - :param target: The array to evaluate against the predicate - :type target: AppKit.NSArray - :return: The filtered array - :rtype: AppKit.NSArray + .. versionadded:: 0.1.0 + """ + return self.by_property("startup", startup)
- .. versionadded:: 0.0.4 +
[docs] def by_zone(self, zone: str) -> 'XADisk': + """Retrieves the disk whose zone matches the given zone. + + .. versionadded:: 0.1.0 """ - placeholders = ["%@"] * len(self.values) - expressions = [" ".join(expr) for expr in zip(self.keys, self.operators, placeholders)] - format = "( " + " ) && ( ".join(expressions) + " )" - predicate = AppKit.NSPredicate.predicateWithFormat_(format, *self.values) - return target.filteredArrayUsingPredicate_(predicate)
+ return self.by_property("zone", zone)
-
[docs] def evaluate_with_format(target: AppKit.NSArray, fmt: str) -> AppKit.NSArray: - """Evaluates the specified array against a predicate with the given format. +
[docs]class XADisk(XADiskItem): + """A disk in the file system. - :param target: The array to filter - :type target: AppKit.NSArray - :param fmt: The format string for the predicate - :type fmt: str - :return: The filtered array - :rtype: AppKit.NSArray + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) - .. versionadded:: 0.0.4 + @property + def capacity(self) -> 'float': + """The total number of bytes (free or used) on the disk. + + .. versionadded:: 0.1.0 """ - predicate = AppKit.NSPredicate.predicateWithFormat_(fmt) - return target.filteredArrayUsingPredicate_(predicate)
+ return self.xa_elem.capacity() -
[docs] def evaluate_with_dict(target: AppKit.NSArray, properties_dict: dict) -> AppKit.NSArray: - """Evaluates the specified array against a predicate constructed from the supplied dictionary. + @property + def ejectable(self) -> 'bool': + """Whether the media can be ejected (floppies, CD's, and so on). - The predicate will use == for all comparisons. + .. versionadded:: 0.1.0 + """ + return self.xa_elem.ejectable() - :param target: The array to filter - :type target: AppKit.NSArray - :param properties_dict: The specification of key, value pairs - :type properties_dict: dict - :return: The filtered array - :rtype: AppKit.NSArray + @property + def format(self) -> 'XAEventsApplication.Format': + """The file system format of the disk. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - fmt = "" - for key, value in properties_dict.items(): - if isinstance(value, str): - value = "'" + value + "'" - fmt += f"( {key} == {value} ) &&" - predicate = AppKit.NSPredicate.predicateWithFormat_(fmt[:-3]) - return target.filteredArrayUsingPredicate_(predicate)
+ return XAEventsApplication.Format(self.xa_elem.format()) - # EQUAL -
[docs] def add_eq_condition(self, property: str, value: Any): - """Appends an `==` condition to the end of the predicate format. + @property + def free_space(self) -> 'float': + """The number of free bytes left on the disk. - The added condition will have the form `property == value`. + .. versionadded:: 0.1.0 + """ + return self.xa_elem.freeSpace() - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def ignore_privileges(self) -> 'bool': + """Whether to ignore permissions on this disk. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("==") - self.values.append(value)
+ return self.xa_elem.ignorePrivileges() -
[docs] def insert_eq_condition(self, index: int, property: str, value: Any): - """Inserts an `==` condition to the predicate format at the desired location, specified by index. + @property + def local_volume(self) -> 'bool': + """Whether the media is a local volume (as opposed to a file server). - The added condition will have the form `property == value`. + .. versionadded:: 0.1.0 + """ + return self.xa_elem.localVolume() - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def server(self) -> 'str': + """The server on which the disk resides, AFP volumes only. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "==") - self.values.insert(index, value)
+ return self.xa_elem.server() + + @property + def startup(self) -> 'bool': + """Whether this disk is the boot disk. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.startup() + + @property + def zone(self) -> 'str': + """The zone in which the disk's server resides, AFP volumes only. + + .. versionadded:: 0.1.0 + """ + return self.xa_elem.zone() + +
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
- # NOT EQUAL -
[docs] def add_neq_condition(self, property: str, value: Any): - """Appends a `!=` condition to the end of the predicate format. +
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. - The added condition will have the form `property != value`. + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
- :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any +
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("!=") - self.values.append(value)
+ self._new_element(self.xa_elem.files(), XAFileList, filter)
-
[docs] def insert_neq_condition(self, index: int, property: str, value: Any): - """Inserts a `!=` condition to the predicate format at the desired location, specified by index. +
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. - The added condition will have the form `property != value`. + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.fileOackages(), XAFilePackageList, filter)
- :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "!=") - self.values.insert(index, value)
+ self._new_element(self.xa_elem.folders(), XAFolderList, filter)
- # GREATER THAN OR EQUAL -
[docs] def add_geq_condition(self, property: str, value: Any): - """Appends a `>=` condition to the end of the predicate format. - The added condition will have the form `property >= value`. - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any - .. versionadded:: 0.0.4 - """ - self.keys.append(property) - self.operators.append(">=") - self.values.append(value)
+
[docs]class XADomainList(XAList): + """A wrapper around lists of domains that employs fast enumeration techniques. -
[docs] def insert_geq_condition(self, index: int, property: str, value: Any): - """Inserts a `>=` condition to the predicate format at the desired location, specified by index. + All properties of domains can be called as methods on the wrapped list, returning a list containing each domain's value for the property. - The added condition will have the form `property >= value`. + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XADomain, filter) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any +
[docs] def id(self) -> list['str']: + """Retrieves the unique identifier of each domain in the list - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, ">=") - self.values.insert(index, value)
+ return list(self.xa_elem.arrayByApplyingSelector_("id"))
- # LESS THAN OR EQUAL -
[docs] def add_leq_condition(self, property: str, value: Any): - """Appends a `<=` condition to the end of the predicate format. +
[docs] def name(self) -> list['str']: + """Retrieves the name of each domain in the list. - The added condition will have the form `property <= value`. + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
- :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any +
[docs] def by_id(self, id: str) -> 'XADomain': + """Retrieves the domain whose ID matches the given ID. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("<=") - self.values.append(value)
+ return self.by_property("id", id)
-
[docs] def insert_leq_condition(self, index: int, property: str, value: Any): - """Inserts a `<=` condition to the predicate format at the desired location, specified by index. +
[docs] def by_name(self, name: str) -> 'XADomain': + """Retrieves the domain whose name matches the given name. - The added condition will have the form `property <= value`. + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
- :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
- .. versionadded:: 0.0.4 - """ - self.keys.insert(index, property) - self.operators.insert(index, "<=") - self.values.insert(index, value)
+
[docs]class XADomain(XAObject): + """A domain in the file system. - # GREATER THAN -
[docs] def add_gt_condition(self, property: str, value: Any): - """Appends a `>` condition to the end of the predicate format. + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) - The added condition will have the form `property > value`. + @property + def application_support_folder(self) -> 'XAFolder': + """The Application Support folder. - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.applicationSupportFolder(), XAFolder) - .. versionadded:: 0.0.4 + @property + def applications_folder(self) -> 'XAFolder': + """The Applications folder. + + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append(">") - self.values.append(value)
+ return self._new_element(self.xa_elem.applicationsFolder(), XAFolder) -
[docs] def insert_gt_condition(self, index: int, property: str, value: Any): - """Inserts a `>` condition to the predicate format at the desired location, specified by index. + @property + def desktop_pictures_folder(self) -> 'XAFolder': + """The Desktop Pictures folder. - The added condition will have the form `property > value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.desktopPicturesFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def folder_action_scripts_folder(self) -> 'XAFolder': + """The Folder Action Scripts folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, ">") - self.values.insert(index, value)
+ return self._new_element(self.xa_elem.folderActionScriptsFolder(), XAFolder) - # LESS THAN -
[docs] def add_lt_condition(self, property: str, value: Any): - """Appends a `<` condition to the end of the predicate format. + @property + def fonts_folder(self) -> 'XAFolder': + """The Fonts folder. - The added condition will have the form `property < value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.fontsFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def id(self) -> 'str': + """The unique identifier of the domain. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("<") - self.values.append(value)
+ return self.xa_elem.id() -
[docs] def insert_lt_condition(self, index: int, property: str, value: Any): - """Inserts a `<` condition to the predicate format at the desired location, specified by index. + @property + def library_folder(self) -> 'XAFolder': + """The Library folder. - The added condition will have the form `property < value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.libraryFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def name(self) -> 'str': + """The name of the domain. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "<") - self.values.insert(index, value)
+ return self.xa_elem.name() - # BETWEEN -
[docs] def add_between_condition(self, property: str, value1: Any, value2: Any): - """Appends a `BETWEEN` condition to the end of the predicate format. + @property + def preferences_folder(self) -> 'XAFolder': + """The Preferences folder. - The added condition will have the form `property BETWEEN [value1, value2]`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.preferencesFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def scripting_additions_folder(self) -> 'XAFolder': + """The Scripting Additions folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("BETWEEN") - self.values.append([value1, value2])
+ return self._new_element(self.xa_elem.scriptingAdditionsFolder(), XAFolder) -
[docs] def insert_between_condition(self, index: int, property: str, value1: Any, value2: Any): - """Inserts a `BETWEEN` condition to the predicate format at the desired location, specified by index. + @property + def scripts_folder(self) -> 'XAFolder': + """The Scripts folder. - The added condition will have the form `property BETWEEN [value1, value2]`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.scriptsFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def shared_documents_folder(self) -> 'XAFolder': + """The Shared Documents folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "BETWEEN") - self.values.insert(index, [value1, value2])
+ return self._new_element(self.xa_elem.sharedDocumentsFolder(), XAFolder) - # BEGINSWITH -
[docs] def add_begins_with_condition(self, property: str, value: Any): - """Appends a `BEGINSWITH` condition to the end of the predicate format. + @property + def speakable_items_folder(self) -> 'XAFolder': + """The Speakable Items folder. - The added condition will have the form `property BEGINSWITH value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.speakableItemsFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def utilities_folder(self) -> 'XAFolder': + """The Utilities folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("BEGINSWITH") - self.values.append(value)
+ return self._new_element(self.xa_elem.utilitiesFolder(), XAFolder) -
[docs] def insert_begins_with_condition(self, index: int, property: str, value: Any): - """Inserts a `BEGINSWITH` condition to the predicate format at the desired location, specified by index. + @property + def workflows_folder(self) -> 'XAFolder': + """The Automator Workflows folder. - The added condition will have the form `property BEGINSWITH value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.workflowsFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "BEGINSWITH") - self.values.insert(index, value)
+ self._new_element(self.xa_elem.folders(), XAFolderList, filter)
- # ENDSWITH -
[docs] def add_ends_with_condition(self, property: str, value: Any): - """Appends a `ENDSWITH` condition to the end of the predicate format. + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
- The added condition will have the form `property ENDSWITH value`. - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any - .. versionadded:: 0.0.4 - """ - self.keys.append(property) - self.operators.append("ENDSWITH") - self.values.append(value)
-
[docs] def insert_ends_with_condition(self, index: int, property: str, value: Any): - """Inserts a `ENDSWITH` condition to the predicate format at the desired location, specified by index. +
[docs]class XAClassicDomainObject(XADomain): + """The Classic domain in the file system. - The added condition will have the form `property ENDSWITH value`. + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def apple_menu_folder(self) -> 'XAFolder': + """The Apple Menu Items folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "ENDSWITH") - self.values.insert(index, value)
+ return self._new_element(self.xa_elem.appleMenuFolder(), XAFolder) - # CONTAINS -
[docs] def add_contains_condition(self, property: str, value: Any): - """Appends a `CONTAINS` condition to the end of the predicate format. + @property + def control_panels_folder(self) -> 'XAFolder': + """The Control Panels folder. - The added condition will have the form `property CONTAINS value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.controlPanelsFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def control_strip_modules_folder(self) -> 'XAFolder': + """The Control Strip Modules folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("CONTAINS") - self.values.append(value)
+ return self._new_element(self.xa_elem.controlStripModulesFolder(), XAFolder) -
[docs] def insert_contains_condition(self, index: int, property: str, value: Any): - """Inserts a `CONTAINS` condition to the predicate format at the desired location, specified by index. + @property + def desktop_folder(self) -> 'XAFolder': + """The Classic Desktop folder. - The added condition will have the form `property CONTAINS value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.desktopFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def extensions_folder(self) -> 'XAFolder': + """The Extensions folder. + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.extensionsFolder(), XAFolder) + + @property + def fonts_folder(self) -> 'XAFolder': + """The Fonts folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "CONTAINS") - self.values.insert(index, value)
+ return self._new_element(self.xa_elem.fontsFolder(), XAFolder) - # MATCHES -
[docs] def add_match_condition(self, property: str, value: Any): - """Appends a `MATCHES` condition to the end of the predicate format. + @property + def launcher_items_folder(self) -> 'XAFolder': + """The Launcher Items folder. - The added condition will have the form `property MATCHES value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.launcherItemsFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def preferences_folder(self) -> 'XAFolder': + """The Classic Preferences folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.append(property) - self.operators.append("MATCHES") - self.values.append(value)
+ return self._new_element(self.xa_elem.preferencesFolder(), XAFolder) -
[docs] def insert_match_condition(self, index: int, property: str, value: Any): - """Inserts a `MATCHES` condition to the predicate format at the desired location, specified by index. + @property + def shutdown_folder(self) -> 'XAFolder': + """The Shutdown Items folder. - The added condition will have the form `property MATCHES value`. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.shutdownFolder(), XAFolder) - :param property: A property of an object to check the condition against - :type property: str - :param value: The target value of the condition - :type value: Any + @property + def startup_items_folder(self) -> 'XAFolder': + """The StartupItems folder. - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.keys.insert(index, property) - self.operators.insert(index, "MATCHES") - self.values.insert(index, value)
+ return self._new_element(self.xa_elem.startupItemsFolder(), XAFolder) -
[docs] def get_clipboard_representation(self) -> str: - """Gets a clipboard-codable representation of the predicate. + @property + def system_folder(self) -> 'XAFolder': + """The System folder. - When a predicate is copied to the clipboard, the string representation of the predicate is added to the clipboard. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.systemFolder(), XAFolder) - :return: The string representation of the predicate - :rtype: str +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.8 + .. versionadded:: 0.1.0 """ - placeholders = ["%@"] * len(self.values) - expressions = [" ".join(expr) for expr in zip(self.keys, self.operators, placeholders)] - format = "( " + " ) && ( ".join(expressions) + " )" - predicate = AppKit.NSPredicate.predicateWithFormat_(format, *self.values) - return predicate.predicateFormat()
+ self._new_element(self.xa_elem.folders(), XAFolderList, filter)
-
[docs]class XAUIElementList(XAList): - """A wrapper around a list of UI elements. +
[docs]class XAFileList(XADiskItemList): + """A wrapper around lists of files that employs fast enumeration techniques. - All properties of UI elements can be accessed via methods on this list, returning a list of the method's return value for each element in the list. + All properties of files can be called as methods on the wrapped list, returning a list containing each file's value for the property. - .. versionadded:: 0.0.5 + .. versionadded:: 0.1.0 """ - def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_type = None): - if obj_type is None: - obj_type = XAUIElement - super().__init__(properties, obj_type, filter) + def __init__(self, properties: dict, filter: Union[dict, None] = None, object_class = None): + if object_class is None: + object_class = XAFile + super().__init__(properties, filter, object_class) -
[docs] def properties(self) -> List[dict]: - return list(self.xa_elem.arrayByApplyingSelector_("properties"))
+
[docs] def creator_type(self) -> list['str']: + """Retrieves the OSType identifying the application that created each file in the list. -
[docs] def accessibility_description(self) -> List[str]: - return list(self.xa_elem.arrayByApplyingSelector_("accessibilityDescription"))
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("creatorType"))
-
[docs] def enabled(self) -> List[bool]: - return list(self.xa_elem.arrayByApplyingSelector_("enabled"))
+
[docs] def default_application(self) -> 'XADiskItemList': + """Retrieves the applications that will launch if each file in the list is opened. -
[docs] def entire_contents(self) -> List[List[Any]]: - return list(self.xa_elem.arrayByApplyingSelector_("entireContents"))
+ .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("defaultApplication") + return self._new_element(ls, XADiskItemList)
-
[docs] def focused(self) -> List[bool]: - return list(self.xa_elem.arrayByApplyingSelector_("focused"))
+
[docs] def file_type(self) -> list['str']: + """Retrieves the OSType identifying the type of data contained in each file in the list. -
[docs] def name(self) -> List[str]: - return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("fileType"))
-
[docs] def title(self) -> List[str]: - return list(self.xa_elem.arrayByApplyingSelector_("title"))
+
[docs] def kind(self) -> list['str']: + """Retrieves the kind of each file in the list. -
[docs] def position(self) -> List[Tuple[Tuple[int, int], Tuple[int, int]]]: - return list(self.xa_elem.arrayByApplyingSelector_("position"))
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("kind"))
-
[docs] def size(self) -> List[Tuple[int, int]]: - return list(self.xa_elem.arrayByApplyingSelector_("size"))
+
[docs] def product_version(self) -> list['str']: + """Retrieves the product version of each file in the list. -
[docs] def maximum_value(self) -> List[Any]: - return list(self.xa_elem.arrayByApplyingSelector_("maximumValue"))
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("productVersion"))
-
[docs] def minimum_value(self) -> List[Any]: - return list(self.xa_elem.arrayByApplyingSelector_("minimumValue"))
+
[docs] def short_version(self) -> list['str']: + """Retrieves the short version of each file in the list. -
[docs] def value(self) -> List[Any]: - return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("shortVersion"))
-
[docs] def role(self) -> List[str]: - return list(self.xa_elem.arrayByApplyingSelector_("role"))
+
[docs] def stationery(self) -> list['bool']: + """Retrieves the stationery status of each file in the list. -
[docs] def role_description(self) -> List[str]: - return list(self.xa_elem.arrayByApplyingSelector_("roleDescription"))
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("stationery"))
-
[docs] def subrole(self) -> List[str]: - return list(self.xa_elem.arrayByApplyingSelector_("subrole"))
+
[docs] def type_identifier(self) -> list['str']: + """Retrieves the type identifier of each file in the list. -
[docs] def selected(self) -> List[bool]: - return list(self.xa_elem.arrayByApplyingSelector_("selected"))
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("typeIdentifier"))
-
[docs] def by_properties(self, properties: dict) -> 'XAUIElement': - return self.by_property("properties", properties)
+
[docs] def version(self) -> list['str']: + """Retrieves the version of each file in the list. -
[docs] def by_accessibility_description(self, accessibility_description: str) -> 'XAUIElement': - return self.by_property("accessibilityDescription", accessibility_description)
+ .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("version"))
-
[docs] def by_entire_contents(self, entire_contents: List[Any]) -> 'XAUIElement': - return self.by_property("entireContents", entire_contents)
+
[docs] def by_creator_type(self, creator_type: str) -> 'XAFile': + """Retrieves the file whose creator type matches the given creator type. -
[docs] def by_focused(self, focused: bool) -> 'XAUIElement': - return self.by_property("focused", focused)
+ .. versionadded:: 0.1.0 + """ + return self.by_property("creatorType", creator_type)
-
[docs] def by_name(self, name: str) -> 'XAUIElement': - return self.by_property("name", name)
+
[docs] def by_default_application(self, default_application: 'XADiskItem') -> 'XAFile': + """Retrieves the file whose default application matches the given application. -
[docs] def by_title(self, title: str) -> 'XAUIElement': - return self.by_property("title", title)
+ .. versionadded:: 0.1.0 + """ + return self.by_property("defaultApplication", default_application.xa_elem)
-
[docs] def by_position(self, position: Tuple[Tuple[int, int], Tuple[int, int]]) -> 'XAUIElement': - return self.by_property("position", position)
+
[docs] def by_file_type(self, file_type: str) -> 'XAFile': + """Retrieves the file whose file type matches the given file type. -
[docs] def by_size(self, size: Tuple[int, int]) -> 'XAUIElement': - return self.by_property("size", size)
+ .. versionadded:: 0.1.0 + """ + return self.by_property("fileType", file_type)
-
[docs] def by_maximum_value(self, maximum_value: Any) -> 'XAUIElement': - return self.by_property("maximumValue", maximum_value)
+
[docs] def by_kind(self, kind: str) -> 'XAFile': + """Retrieves the file whose kind matches the given kind. -
[docs] def by_minimum_value(self, minimum_value: Any) -> 'XAUIElement': - return self.by_property("minimumValue", minimum_value)
+ .. versionadded:: 0.1.0 + """ + return self.by_property("kind", kind)
-
[docs] def by_value(self, value: Any) -> 'XAUIElement': - return self.by_property("value", value)
+
[docs] def by_product_version(self, product_version: str) -> 'XAFile': + """Retrieves the file whose product version matches the given version. -
[docs] def by_role(self, role: str) -> 'XAUIElement': - return self.by_property("role", role)
+ .. versionadded:: 0.1.0 + """ + return self.by_property("productVersion", product_version)
-
[docs] def by_role_description(self, role_description: str) -> 'XAUIElement': - return self.by_property("roleDescription", role_description)
+
[docs] def by_short_version(self, short_version: str) -> 'XAFile': + """Retrieves the file whose short version matches the given text. -
[docs] def by_subrole(self, subrole: str) -> 'XAUIElement': - return self.by_property("subrole", subrole)
+ .. versionadded:: 0.1.0 + """ + return self.by_property("shortVersion", short_version)
-
[docs] def by_selected(self, selected: bool) -> 'XAUIElement': - return self.by_property("selected", selected)
+
[docs] def by_stationery(self, stationery: bool) -> 'XAFile': + """Retrieves the file whose stationery status matches the given boolean value. -
[docs]class XAUIElement(XAObject): + .. versionadded:: 0.1.0 + """ + return self.by_property("stationery", stationery)
+ +
[docs] def by_type_identifier(self, type_identifier: str) -> 'XAFile': + """Retrieves the file whose type identifier matches the given type identifier. + + .. versionadded:: 0.1.0 + """ + return self.by_property("typeIdentifier", type_identifier)
+ +
[docs] def by_version(self, version: str) -> 'XAFile': + """Retrieves the file whose version matches the given version. + + .. versionadded:: 0.1.0 + """ + return self.by_property("version", version)
+ +
[docs]class XAFile(XADiskItem): + """A file in the file system. + + .. versionadded:: 0.1.0 + """ def __init__(self, properties): super().__init__(properties) - self.properties: dict #: All properties of the UI element - self.accessibility_description: str #: The accessibility description of the element - self.enabled: bool #: Whether the UI element is currently enabled - self.entire_contents: Any #: The entire contents of the element - self.focused: bool #: Whether the window is the currently element - self.name: str #: The name of the element - self.title: str #: The title of the element (often the same as its name) - self.position: Tuple[int, int] #: The position of the top left corner of the element - self.size: Tuple[int, int] #: The width and height of the element, in pixels - self.maximum_value: Any #: The maximum value that the element can have - self.minimum_value: Any #: The minimum value that the element can have - self.value: Any #: The current value of the element - self.role: str #: The element's role - self.role_description: str #: The description of the element's role - self.subrole: str #: The subrole of the UI element - self.selected: bool #: Whether the element is currently selected - @property - def properties(self) -> dict: - return self.xa_elem.properties() + def creator_type(self) -> 'str': + """The OSType identifying the application that created the file. - @property - def accessibility_description(self) -> str: - return self.xa_elem.accessibilityDescription() + .. versionadded:: 0.1.0 + """ + return self.xa_elem.creatorType() @property - def enabled(self) -> bool: - return self.xa_elem.enabled() + def default_application(self) -> 'XADiskItem': + """The application that will launch if the file is opened. - @property - def entire_contents(self) -> List[XAObject]: - ls = self.xa_elem.entireContents() - return [self._new_element(x, XAUIElement) for x in ls] + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.defaultApplication(), XADiskItem) - @property - def focused(self) -> bool: - return self.xa_elem.focused() + @default_application.setter + def default_application(self, default_application: XADiskItem): + self.set_property('defaultApplication', default_application.xa_elem) @property - def name(self) -> str: - return self.xa_elem.name() + def file_type(self) -> 'str': + """The OSType identifying the type of data contained in the file. - @property - def title(self) -> str: - return self.xa_elem.title() + .. versionadded:: 0.1.0 + """ + return self.xa_elem.fileType() @property - def position(self) -> Tuple[int, int]: - return self.xa_elem.position() + def kind(self) -> 'str': + """The kind of file, as shown in Finder. - @property - def size(self) -> Tuple[int, int]: - return self.xa_elem.size() + .. versionadded:: 0.1.0 + """ + return self.xa_elem.kind() @property - def maximum_value(self) -> Any: - return self.xa_elem.maximumValue() + def product_version(self) -> 'str': + """The version of the product (visible at the top of the "Get Info" window). - @property - def minimum_value(self) -> Any: - return self.xa_elem.minimumValue() + .. versionadded:: 0.1.0 + """ + return self.xa_elem.productVersion() @property - def value(self) -> Any: - return self.xa_elem.value() + def short_version(self) -> 'str': + """The short version of the file. - @property - def role(self) -> str: - return self.xa_elem.role() + .. versionadded:: 0.1.0 + """ + return self.xa_elem.shortVersion() @property - def role_description(self) -> str: - return self.xa_elem.roleDescription() + def stationery(self) -> 'bool': + """Whether the file is a stationery pad. - @property - def subrole(self) -> str: - return self.xa_elem.subrole() + .. versionadded:: 0.1.0 + """ + return self.xa_elem.stationery() @property - def selected(self) -> bool: - return self.xa_elem.selected() + def type_identifier(self) -> 'str': + """The type identifier of the file. -
[docs] def ui_elements(self, filter: dict = None) -> 'XAUIElementList': - return self._new_element(self.xa_elem.UIElements(), XAUIElementList, filter)
+ .. versionadded:: 0.1.0 + """ + return self.xa_elem.typeIdentifier() -
[docs] def windows(self, filter: dict = None) -> 'XAWindowList': - return self._new_element(self.xa_elem.windows(), XAWindowList, filter)
+ @property + def version(self) -> 'str': + """The version of the file (visible at the bottom of the "Get Info" window). -
[docs] def menu_bars(self, filter: dict = None) -> 'XAUIMenuBarList': - return self._new_element(self.xa_elem.menuBars(), XAUIMenuBarList, filter)
+ .. versionadded:: 0.1.0 + """ + return self.xa_elem.version()
-
[docs] def menu_bar_items(self, filter: dict = None) -> 'XAUIMenuBarItemList': - return self._new_element(self.xa_elem.menuBarItems(), XAUIMenuBarItemList, filter)
-
[docs] def menus(self, filter: dict = None) -> 'XAUIMenuList': - return self._new_element(self.xa_elem.menus(), XAUIMenuList, filter)
-
[docs] def menu_items(self, filter: dict = None) -> 'XAUIMenuItemList': - return self._new_element(self.xa_elem.menuItems(), XAUIMenuItemList, filter)
-
[docs] def splitters(self, filter: dict = None) -> 'XAUISplitterList': - return self._new_element(self.xa_elem.splitters(), XAUISplitterList, filter)
+
[docs]class XAFilePackageList(XAFileList): + """A wrapper around lists of file packages that employs fast enumeration techniques. -
[docs] def toolbars(self, filter: dict = None) -> 'XAUIToolbarList': - return self._new_element(self.xa_elem.toolbars(), XAUIToolbarList, filter)
+ All properties of file packages can be called as methods on the wrapped list, returning a list containing each package's value for the property. -
[docs] def tab_groups(self, filter: dict = None) -> 'XAUITabGroupList': - return self._new_element(self.xa_elem.tabGroups(), XAUITabGroupList, filter)
+ .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAFilePackage)
-
[docs] def scroll_areas(self, filter: dict = None) -> 'XAUIScrollAreaList': - return self._new_element(self.xa_elem.scrollAreas(), XAUIScrollAreaList, filter)
+
[docs]class XAFilePackage(XAFile): + """A file package in the file system. -
[docs] def groups(self, filter: dict = None) -> 'XAUIGroupList': - return self._new_element(self.xa_elem.groups(), XAUIGroupList, filter)
+ .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) -
[docs] def buttons(self, filter: dict = None) -> 'XAButtonList': - return self._new_element(self.xa_elem.buttons(), XAButtonList, filter)
+
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. -
[docs] def radio_buttons(self, filter: dict = None) -> 'XAUIRadioButtonList': - return self._new_element(self.xa_elem.radioButtons(), XAUIRadioButtonList, filter)
+ .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
-
[docs] def actions(self, filter: dict = None) -> 'XAUIActionList': - return self._new_element(self.xa_elem.actions(), XAUIActionList, filter)
+
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. -
[docs] def text_fields(self, filter: dict = None) -> 'XAUITextfieldList': - return self._new_element(self.xa_elem.textFields(), XAUITextfieldList, filter)
+ .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
-
[docs] def static_texts(self, filter: dict = None) -> 'XAUIStaticTextList': - return self._new_element(self.xa_elem.staticTexts(), XAUIStaticTextList, filter)
+
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.files(), XAFileList, filter)
+
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.filePackages(), XAFilePackageList, filter)
-
[docs]class XAWindowList(XAUIElementList): - """A wrapper around a list of windows. +
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAWindow) + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
-
[docs] def collapse(self): - """Collapses all windows in the list. - .. versionadded:: 0.0.5 - """ - for window in self: - window.collapse()
-
[docs] def uncollapse(self): - """Uncollapses all windows in the list. - .. versionadded:: 0.0.6 - """ - for window in self: - window.uncollapse()
+
[docs]class XAFolderList(XADiskItemList): + """A wrapper around lists of folders that employs fast enumeration techniques. -
[docs] def close(self): - """Closes all windows in the list.add() - - .. versionadded:: 0.0.6 - """ - for window in self: - window.close()
+ All properties of folders can be called as methods on the wrapped list, returning a list containing each folder's value for the property. -
[docs]class XAWindow(XAUIElement): - """A general window class for windows of both officially scriptable and non-scriptable applications. + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAFolder)
- .. seealso:: :class:`XAApplication` +
[docs]class XAFolder(XADiskItem): + """A folder in the file system. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ def __init__(self, properties): super().__init__(properties) -
[docs] def close(self) -> 'XAWindow': - """Collapses (minimizes) the window. - - :return: A reference to the now-collapsed window object. - :rtype: XAWindow +
[docs] def aliases(self, filter: Union[dict, None] = None) -> 'XAAliasList': + """Returns a list of aliases, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - close_button = self.button({"subrole": "AXCloseButton"}) - close_button.click() - return self
- -
[docs] def collapse(self) -> 'XAWindow': - """Collapses (minimizes) the window. + self._new_element(self.xa_elem.aliases(), XAAliasList, filter)
- :return: A reference to the now-collapsed window object. - :rtype: XAWindow +
[docs] def disk_items(self, filter: Union[dict, None] = None) -> 'XADiskItemList': + """Returns a list of disk items, as PyXA objects, matching the given filter. - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - if hasattr(self.xa_elem.properties(), "miniaturized"): - self.xa_elem.setValue_forKey_(True, "miniaturized") - else: - close_button = self.buttons({"subrole": "AXMinimizeButton"})[0] - close_button.click() - return self
+ self._new_element(self.xa_elem.diskItems(), XADiskItemList, filter)
-
[docs] def uncollapse(self) -> 'XAWindow': - """Uncollapses (unminimizes/expands) the window. +
[docs] def files(self, filter: Union[dict, None] = None) -> 'XAFileList': + """Returns a list of files, as PyXA objects, matching the given filter. - :return: A reference to the uncollapsed window object. - :rtype: XAWindow + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.files(), XAFileList, filter)
- .. versionadded:: 0.0.1 +
[docs] def file_packages(self, filter: Union[dict, None] = None) -> 'XAFilePackageList': + """Returns a list of file packages, as PyXA objects, matching the given filter. + + .. versionadded:: 0.1.0 """ - ls = self.xa_sevt.applicationProcesses() - dock_process = XAPredicate.evaluate_with_format(ls, "name == 'Dock'")[0] + self._new_element(self.xa_elem.filePackages(), XAFilePackageList, filter)
- ls = dock_process.lists()[0].UIElements() - name = self.xa_prnt.xa_prnt.xa_elem.localizedName() - app_icon = XAPredicate.evaluate_with_format(ls, f"name == '{name}'")[0] - app_icon.actions()[0].perform() - return self
+
[docs] def folders(self, filter: Union[dict, None] = None) -> 'XAFolderList': + """Returns a list of folders, as PyXA objects, matching the given filter. + :param filter: A dictionary specifying property-value pairs that all returned folders will have, or None + :type filter: Union[dict, None] + :return: The list of folders + :rtype: XAFolderList + .. versionadded:: 0.1.0 + """ + self._new_element(self.xa_elem.folders(), XAFolderList, filter)
-
[docs]class XAUIMenuBarList(XAUIElementList): - """A wrapper around a list of menu bars. - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIMenuBar)
-
[docs]class XAUIMenuBar(XAUIElement): - """A menubar UI element. - - .. versionadded:: 0.0.2 +
[docs]class XALocalDomainObject(XADomain): + """The local domain in the file system. + + .. versionadded:: 0.1.0 """ def __init__(self, properties): super().__init__(properties)
@@ -2740,18 +7549,10 @@

Source code for PyXA.XABase

 
 
 
-
[docs]class XAUIMenuBarItemList(XAUIElementList): - """A wrapper around a list of menu bar items. - - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIMenuBarItem)
+
[docs]class XANetworkDomainObject(XADomain): + """The network domain in the file system. -
[docs]class XAUIMenuBarItem(XAUIElement): - """A menubar item UI element. - - .. versionadded:: 0.0.2 + .. versionadded:: 0.1.0 """ def __init__(self, properties): super().__init__(properties)
@@ -2759,18 +7560,10 @@

Source code for PyXA.XABase

 
 
 
-
[docs]class XAUIMenuList(XAUIElementList): - """A wrapper around a list of menus. - - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIMenu)
+
[docs]class XASystemDomainObject(XADomain): + """The system domain in the file system. -
[docs]class XAUIMenu(XAUIElement): - """A menu UI element. - - .. versionadded:: 0.0.2 + .. versionadded:: 0.1.0 """ def __init__(self, properties): super().__init__(properties)
@@ -2778,1439 +7571,2745 @@

Source code for PyXA.XABase

 
 
 
-
[docs]class XAUIMenuItemList(XAUIElementList): - """A wrapper around a list of menu items. - - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIMenuItem)
+
[docs]class XAUserDomainObject(XADomain): + """The user domain in the file system. -
[docs]class XAUIMenuItem(XAUIElement): - """A menu item UI element. - - .. versionadded:: 0.0.2 + .. versionadded:: 0.1.0 """ def __init__(self, properties): super().__init__(properties) -
[docs] def click(self) -> 'XAUIMenuItem': - """Clicks the menu item. Synonymous with :func:`press`. + @property + def desktop_folder(self) -> 'XAFolder': + """The user's Desktop folder - :return: The menu item object. - :rtype: XAUIMenuItem + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.desktopFolder(), XAFolder) - .. versionadded:: 0.0.2 + @property + def documents_folder(self) -> 'XAFolder': + """The user's Documents folder + + .. versionadded:: 0.1.0 """ - self.actions({"name": "AXPress"})[0].perform() - return self
+ return self._new_element(self.xa_elem.documentsFolder(), XAFolder) -
[docs] def press(self) -> 'XAUIMenuItem': - """Clicks the menu item. Synonymous with :func:`click`. + @property + def downloads_folder(self) -> 'XAFolder': + """The user's Downloads folder - :return: The menu item object. - :rtype: XAUIMenuItem + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.downloadsFolder(), XAFolder) - .. versionadded:: 0.0.2 + @property + def favorites_folder(self) -> 'XAFolder': + """The user's Favorites folder + + .. versionadded:: 0.1.0 """ - self.actions({"name": "AXPress"})[0].perform() - return self
+ return self._new_element(self.xa_elem.favoritesFolder(), XAFolder) + @property + def home_folder(self) -> 'XAFolder': + """The user's Home folder + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.homeFolder(), XAFolder) + @property + def movies_folder(self) -> 'XAFolder': + """The user's Movies folder -
[docs]class XAUISplitterList(XAUIElementList): - """A wrapper around a list of splitters. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.moviesFolder(), XAFolder) - .. versionadded:: 0.0.8 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUISplitter)
+ @property + def music_folder(self) -> 'XAFolder': + """The user's Music folder -
[docs]class XAUISplitter(XAUIElement): - """A splitter UI element. - - .. versionadded:: 0.0.8 - """ - def __init__(self, properties): - super().__init__(properties)
+ .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.musicFolder(), XAFolder) + @property + def pictures_folder(self) -> 'XAFolder': + """The user's Pictures folder + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.picturesFolder(), XAFolder) + @property + def public_folder(self) -> 'XAFolder': + """The user's Public folder -
[docs]class XAUIToolbarList(XAUIElementList): - """A wrapper around a list of toolbars. + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.publicFolder(), XAFolder) - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIToolbar)
+ @property + def sites_folder(self) -> 'XAFolder': + """The user's Sites folder -
[docs]class XAUIToolbar(XAUIElement): - """A toolbar UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties)
+ .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.sitesFolder(), XAFolder) + @property + def temporary_items_folder(self) -> 'XAFolder': + """The Temporary Items folder + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.temporaryItemsFolder(), XAFolder)
-
[docs]class XAUIGroupList(XAUIElementList): - """A wrapper around a list of UI element groups. - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIGroup)
-
[docs]class XAUIGroup(XAUIElement): - """A group of UI element. - - .. versionadded:: 0.0.2 +############# +### Media ### +############# +
[docs]class XAImageList(XAList, XAClipboardCodable): + """A wrapper around lists of images that employs fast enumeration techniques. + + .. versionadded:: 0.0.3 """ - def __init__(self, properties): - super().__init__(properties)
+ def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XAImage + super().__init__(properties, obj_class, filter) + self.modified = False #: Whether the list of images has been modified since it was initialized + def __partial_init(self): + images = [None] * self.xa_elem.count() + def init_images(ref, index, stop): + if isinstance(ref, str): + ref = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(ref).xa_elem) + elif isinstance(ref, ScriptingBridge.SBObject): + ref = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(ref.imageFile().POSIXPath()).xa_elem) + elif isinstance(ref, XAObject): + ref = AppKit.NSImage.alloc().initWithContentsOfURL_(ref.image_file.posix_path.xa_elem) + images[index] = ref -
[docs]class XAUITabGroupList(XAUIElementList): - """A wrapper around a list of UI element tab groups. + self.xa_elem.enumerateObjectsUsingBlock_(init_images) + return AppKit.NSMutableArray.alloc().initWithArray_(images) - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUITabGroup)
+ def __apply_filter(self, filter_block, *args): + images = self.__partial_init() -
[docs]class XAUITabGroup(XAUIElement): - """A tab group UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties)
+ filtered_images = [None] * images.count() + def filter_image(image, index, *args): + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = filter_block(image, *args) + filter.setValue_forKey_(img, "inputImage") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + filtered_images[index] = result + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(filter_image, [image, index, *args]) -
[docs]class XAUIScrollAreaList(XAUIElementList): - """A wrapper around a list of scroll areas. + while any([t.is_alive() for t in threads]): + time.sleep(0.01) - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIScrollArea)
+ self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(filtered_images) + return self -
[docs]class XAUIScrollArea(XAUIElement): - """A scroll area UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties)
+
[docs] def file(self) -> list[XAPath]: + return [x.file for x in self]
+ +
[docs] def horizontal_stitch(self) -> 'XAImage': + """Horizontally stacks each image in the list. + + The first image in the list is placed at the left side of the resulting image. + + :return: The resulting image after stitching + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + return XAImage.horizontal_stitch(self)
+ +
[docs] def vertical_stitch(self) -> 'XAImage': + """Vertically stacks each image in the list. + The first image in the list is placed at the bottom side of the resulting image. + :return: The resulting image after stitching + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + return XAImage.vertical_stitch(self)
+
[docs] def additive_composition(self) -> 'XAImage': + """Creates a composition image by adding the color values of each image in the list. -
[docs]class XAButtonList(XAUIElementList): - """A wrapper around lists of buttons that employs fast enumeration techniques. + :param images: The images to add together + :type images: list[XAImage] + :return: The resulting image composition + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image_data = [None] * self.xa_elem.count() + for index, image in enumerate(self.xa_elem): + if isinstance(image, str): + image = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(image).xa_elem) + image_data[index] = Quartz.CIImage.imageWithData_(image.TIFFRepresentation()) + + current_composition = None + while len(image_data) > 1: + img1 = image_data.pop(0) + img2 = image_data.pop(0) + composition_filter = Quartz.CIFilter.filterWithName_("CIAdditionCompositing") + composition_filter.setDefaults() + composition_filter.setValue_forKey_(img1, "inputImage") + composition_filter.setValue_forKey_(img2, "inputBackgroundImage") + current_composition = composition_filter.outputImage() + image_data.insert(0, current_composition) + + composition_rep = AppKit.NSCIImageRep.imageRepWithCIImage_(current_composition) + composition = AppKit.NSImage.alloc().initWithSize_(composition_rep.size()) + composition.addRepresentation_(composition_rep) + return XAImage(composition)
- .. versionadded:: 0.0.4 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAButton)
+
[docs] def subtractive_composition(self) -> 'XAImage': + """Creates a composition image by subtracting the color values of each image in the list successively. -
[docs]class XAButton(XAUIElement): - """A button UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties) + :param images: The images to create the composition from + :type images: list[XAImage] + :return: The resulting image composition + :rtype: XAImage -
[docs] def click(self) -> 'XAButton': - """Clicks the button. Synonymous with :func:`press`. + .. versionadded:: 0.1.0 + """ + image_data = [None] * self.xa_elem.count() + for index, image in enumerate(self.xa_elem): + if isinstance(image, str): + image = AppKit.NSImage.alloc().initWithContentsOfURL_(XAPath(image).xa_elem) + image_data[index] = Quartz.CIImage.imageWithData_(image.TIFFRepresentation()) - :return: The button object - :rtype: XAButton + current_composition = None + while len(image_data) > 1: + img1 = image_data.pop(0) + img2 = image_data.pop(0) + composition_filter = Quartz.CIFilter.filterWithName_("CISubtractBlendMode") + composition_filter.setDefaults() + composition_filter.setValue_forKey_(img1, "inputImage") + composition_filter.setValue_forKey_(img2, "inputBackgroundImage") + current_composition = composition_filter.outputImage() + image_data.insert(0, current_composition) + + composition_rep = AppKit.NSCIImageRep.imageRepWithCIImage_(current_composition) + composition = AppKit.NSImage.alloc().initWithSize_(composition_rep.size()) + composition.addRepresentation_(composition_rep) + return XAImage(composition)
- .. versionadded:: 0.0.2 +
[docs] def edges(self, intensity: float = 1.0) -> 'XAImageList': + """Detects the edges in each image of the list and highlights them colorfully, blackening other areas of the images. + + :param intensity: The degree to which edges are highlighted. Higher is brighter. Defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 """ - self.actions({"name": "AXPress"})[0].perform() - return self
+ def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIEdges") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter -
[docs] def press(self) -> 'XAButton': - """Clicks the button. Synonymous with :func:`click`. + return self.__apply_filter(filter_block, intensity)
- :return: The button object - :rtype: XAButton +
[docs] def gaussian_blur(self, intensity: float = 10) -> 'XAImageList': + """Blurs each image in the list using a Gaussian filter. - .. versionadded:: 0.0.2 + :param intensity: The strength of the blur effect, defaults to 10 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIGaussianBlur") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputRadius") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def reduce_noise(self, noise_level: float = 0.02, sharpness: float = 0.4) -> 'XAImageList': + """Reduces noise in each image of the list by sharpening areas with a luminance delta below the specified noise level threshold. + + :param noise_level: The threshold for luminance changes in an area below which will be considered noise, defaults to 0.02 + :type noise_level: float + :param sharpness: The sharpness of the resulting images, defaults to 0.4 + :type sharpness: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, noise_level, sharpness): + filter = Quartz.CIFilter.filterWithName_("CINoiseReduction") + filter.setDefaults() + filter.setValue_forKey_(noise_level, "inputNoiseLevel") + filter.setValue_forKey_(sharpness, "inputSharpness") + return filter + + return self.__apply_filter(filter_block, noise_level, sharpness)
+ +
[docs] def pixellate(self, pixel_size: float = 8.0) -> 'XAImageList': + """Pixellates each image in the list. + + :param pixel_size: The size of the pixels, defaults to 8.0 + :type pixel_size: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 """ - self.actions({"name": "AXPress"})[0].perform() - return self
+ def filter_block(image, pixel_size): + filter = Quartz.CIFilter.filterWithName_("CIPixellate") + filter.setDefaults() + filter.setValue_forKey_(pixel_size, "inputScale") + return filter -
[docs] def option_click(self) -> 'XAButton': - """Option-Clicks the button. + return self.__apply_filter(filter_block, pixel_size)
- :return: The button object - :rtype: XAButton +
[docs] def outline(self, threshold: float = 0.1) -> 'XAImageList': + """Outlines detected edges within each image of the list in black, leaving the rest transparent. - .. versionadded:: 0.0.2 + :param threshold: The threshold to use when separating edge and non-edge pixels. Larger values produce thinner edge lines. Defaults to 0.1 + :type threshold: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 """ - self.actions({"name": "AXZoomWindow"})[0].perform() - return self
+ def filter_block(image, threshold): + filter = Quartz.CIFilter.filterWithName_("CILineOverlay") + filter.setDefaults() + filter.setValue_forKey_(threshold, "inputThreshold") + return filter -
[docs] def show_menu(self) -> 'XAButton': - """Right clicks the button, invoking a menu. + return self.__apply_filter(filter_block, threshold)
- :return: The button object - :rtype: XAButton +
[docs] def invert(self) -> 'XAImageList': + """Inverts the colors of each image in the list. - .. versionadded:: 0.0.2 + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 """ - self.actions({"name": "AXShowMenu"})[0].perform() - return self
+ def filter_block(image): + filter = Quartz.CIFilter.filterWithName_("CIColorInvert") + filter.setDefaults() + return filter + return self.__apply_filter(filter_block)
+ +
[docs] def sepia(self, intensity: float = 1.0) -> 'XAImageList': + """Applies a sepia filter to each image in the list; maps all colors of the images to shades of brown. + :param intensity: The opacity of the sepia effect. A value of 0 will have no impact on the image. Defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CISepiaTone") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter -
[docs]class XAUIRadioButtonList(XAUIElementList): - """A wrapper around lists of radio buttons that employs fast enumeration techniques. + return self.__apply_filter(filter_block, intensity)
- .. versionadded:: 0.0.4 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIRadioButton)
+
[docs] def vignette(self, intensity: float = 1.0) -> 'XAImageList': + """Applies vignette shading to the corners of each image in the list. -
[docs]class XAUIRadioButton(XAUIElement): - """A radio button UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties)
+ :param intensity: The intensity of the vignette effect, defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIVignette") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter + + return self.__apply_filter(filter_block, intensity)
+ +
[docs] def depth_of_field(self, focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] = None, intensity: float = 10.0, focal_region_saturation: float = 1.5) -> 'XAImageList': + """Applies a depth of field filter to each image in the list, simulating a tilt & shift effect. + :param focal_region: Two points defining a line within each image to focus the effect around (pixels around the line will be in focus), or None to use the center third of the image, defaults to None + :type focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] + :param intensity: Controls the amount of distance around the focal region to keep in focus. Higher values decrease the distance before the out-of-focus effect starts. Defaults to 10.0 + :type intensity: float + :param focal_region_saturation: Adjusts the saturation of the focial region. Higher values increase saturation. Defaults to 1.5 (1.5x default saturation) + :type focal_region_saturation: float + :return: The resulting images after applying the filter + :rtype: XAImageList + .. versionadded:: 0.1.0 + """ + def filter_block(image, focal_region, intensity, focal_region_saturation): + if focal_region is None: + center_top = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 3) + center_bottom = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 3 * 2) + focal_region = (center_top, center_bottom) + else: + point1 = Quartz.CIVector.vectorWithX_Y_(focal_region[0]) + point2 = Quartz.CIVector.vectorWithX_Y_(focal_region[1]) + focal_region = (point1, point2) + filter = Quartz.CIFilter.filterWithName_("CIDepthOfField") + filter.setDefaults() + filter.setValue_forKey_(focal_region[0], "inputPoint0") + filter.setValue_forKey_(focal_region[1], "inputPoint1") + filter.setValue_forKey_(intensity, "inputRadius") + filter.setValue_forKey_(focal_region_saturation, "inputSaturation") + return filter -
[docs]class XAUIActionList(XAUIElementList): - """A wrapper around a list of UI element actions. + return self.__apply_filter(filter_block, focal_region, intensity, focal_region_saturation)
- .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIAction)
+
[docs] def crystallize(self, crystal_size: float = 20.0) -> 'XAImageList': + """Applies a crystallization filter to each image in the list. Creates polygon-shaped color blocks by aggregating pixel values. -
[docs]class XAUIAction(XAUIElement): - """An action associated with a UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties) + :param crystal_size: The radius of the crystals, defaults to 20.0 + :type crystal_size: float + :return: The resulting images after applying the filter + :rtype: XAImageList -
[docs] def perform(self): - """Executes the action. - - .. versionadded:: 0.0.2 + .. versionadded:: 0.1.0 """ - self.xa_elem.perform()
+ def filter_block(image, crystal_size): + filter = Quartz.CIFilter.filterWithName_("CICrystallize") + filter.setDefaults() + filter.setValue_forKey_(crystal_size, "inputRadius") + return filter + return self.__apply_filter(filter_block, crystal_size)
+
[docs] def comic(self) -> 'XAImageList': + """Applies a comic filter to each image in the list. Outlines edges and applies a color halftone effect. + :return: The resulting images after applying the filter + :rtype: XAImageList -
[docs]class XAUITextfieldList(XAUIElementList): - """A wrapper around a list of textfields. - - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUITextfield)
+ .. versionadded:: 0.1.0 + """ + def filter_block(image): + filter = Quartz.CIFilter.filterWithName_("CIComicEffect") + filter.setDefaults() + return filter -
[docs]class XAUITextfield(XAUIElement): - """A textfield UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties)
+ return self.__apply_filter(filter_block)
+
[docs] def pointillize(self, point_size: float = 20.0) -> 'XAImageList': + """Applies a pointillization filter to each image in the list. + :param crystal_size: The radius of the points, defaults to 20.0 + :type crystal_size: float + :return: The resulting images after applying the filter + :rtype: XAImageList + .. versionadded:: 0.1.0 + """ + def filter_block(image, point_size): + filter = Quartz.CIFilter.filterWithName_("CIPointillize") + filter.setDefaults() + filter.setValue_forKey_(point_size, "inputRadius") + return filter -
[docs]class XAUIStaticTextList(XAUIElementList): - """A wrapper around a list of static text elements. + return self.__apply_filter(filter_block, point_size)
- .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAUIStaticText)
+
[docs] def bloom(self, intensity: float = 0.5) -> 'XAImageList': + """Applies a bloom effect to each image in the list. Softens edges and adds a glow. -
[docs]class XAUIStaticText(XAUIElement): - """A static text UI element. - - .. versionadded:: 0.0.2 - """ - def __init__(self, properties): - super().__init__(properties)
+ :param intensity: The strength of the softening and glow effects, defaults to 0.5 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList + .. versionadded:: 0.1.0 + """ + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIBloom") + filter.setDefaults() + filter.setValue_forKey_(intensity, "inputIntensity") + return filter + return self.__apply_filter(filter_block, intensity)
+
[docs] def monochrome(self, color: XAColor, intensity: float = 1.0) -> 'XAImageList': + """Remaps the colors of each image in the list to shades of the specified color. -
[docs]class XATextDocument(XAObject): - """A class for managing and interacting with text documents. + :param color: The color of map each images colors to + :type color: XAColor + :param intensity: The strength of recoloring effect. Higher values map colors to darker shades of the provided color. Defaults to 1.0 + :type intensity: float + :return: The resulting images after applying the filter + :rtype: XAImageList - .. versionadded:: 0.0.1 - """ - def __init__(self, properties): - super().__init__(properties) - self.text: XAText #: The text of the document. + .. versionadded:: 0.1.0 + """ + ci_color = Quartz.CIColor.alloc().initWithColor_(color.xa_elem) - @property - def text(self) -> 'XAText': - return self._new_element(self.xa_elem.text(), XAText) + def filter_block(image, intensity): + filter = Quartz.CIFilter.filterWithName_("CIColorMonochrome") + filter.setDefaults() + filter.setValue_forKey_(ci_color, "inputColor") + filter.setValue_forKey_(intensity, "inputIntensity") + return filter -
[docs] def set_text(self, new_text: str) -> 'XATextDocument': - """Sets the text of the document. + return self.__apply_filter(filter_block, intensity)
- :param new_text: The new text of the document - :type new_text: str - :return: A reference to the document object. - :rtype: XATextDocument +
[docs] def bump(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, curvature: float = 0.5) -> 'XAImageList': + """Adds a concave (inward) or convex (outward) bump to each image in the list at the specified location within each image. - .. seealso:: :func:`prepend`, :func:`append` + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The radius of the bump in pixels, defaults to 300.0 + :type radius: float + :param curvature: Controls the direction and intensity of the bump's curvature. Positive values create convex bumps while negative values create concave bumps. Defaults to 0.5 + :type curvature: float + :return: The resulting images after applying the distortion + :rtype: XAImageList - .. versionadded:: 0.0.1 + .. versionadded:: 0.1.0 """ - self.set_property("text", new_text) - return self
+ images = self.__partial_init() -
[docs] def prepend(self, text: str) -> 'XATextDocument': - """Inserts the provided text at the beginning of the document. + bumped_images = [None] * images.count() + def bump_image(image, index, center, radius, curvature): + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = Quartz.CIFilter.filterWithName_("CIBumpDistortion") + filter.setDefaults() + filter.setValue_forKey_(img, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(curvature, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + bumped_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(bump_image, [image, index, center, radius, curvature]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) - :param text: The text to insert. - :type text: str - :return: A reference to the document object. - :rtype: XATextDocument + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(bumped_images) + return self
- .. seealso:: :func:`append`, :func:`set_text` +
[docs] def pinch(self, center: Union[tuple[int, int], None] = None, intensity: float = 0.5) -> 'XAImageList': + """Adds an inward pinch distortion to each image in the list at the specified location within each image. - .. versionadded:: 0.0.1 + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param intensity: Controls the scale of the pinch effect. Higher values stretch pixels away from the specified center to a greater degree. Defaults to 0.5 + :type intensity: float + :return: The resulting images after applying the distortion + :rtype: XAImageList + + .. versionadded:: 0.1.0 """ - old_text = self.text - self.set_property("text", text + old_text) - return self
+ images = self.__partial_init() -
[docs] def append(self, text: str) -> 'XATextDocument': - """Appends the provided text to the end of the document. + pinched_images = [None] * images.count() + def pinch_image(image, index, center, intensity): + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = Quartz.CIFilter.filterWithName_("CIPinchDistortion") + filter.setDefaults() + filter.setValue_forKey_(img, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(intensity, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + pinched_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(pinch_image, [image, index, center, intensity]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) - :param text: The text to append. - :type text: str - :return: A reference to the document object. - :rtype: XATextDocument + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(pinched_images) + return self
- .. seealso:: :func:`prepend`, :func:`set_text` +
[docs] def twirl(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, angle: float = 3.14) -> 'XAImageList': + """Adds a twirl distortion to each image in the list by rotating pixels around the specified location within each image. - .. versionadded:: 0.0.1 + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The pixel radius around the centerpoint that defines the area to apply the effect to, defaults to 300.0 + :type radius: float + :param angle: The angle of the twirl in radians, defaults to 3.14 + :type angle: float + :return: The resulting images after applying the distortion + :rtype: XAImageList + + .. versionadded:: 0.1.0 """ - old_text = self.text - self.set_property("text", old_text + text) + images = self.__partial_init() + + twirled_images = [None] * images.count() + def twirl_image(image, index, center, radius, angle): + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(image.size().width / 2, image.size().height / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + img = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + filter = Quartz.CIFilter.filterWithName_("CITwirlDistortion") + filter.setDefaults() + filter.setValue_forKey_(img, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(angle, "inputAngle") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + + # Crop the result to the original image size + cropped = uncropped.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + twirled_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(twirl_image, [image, index, center, radius, angle]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(twirled_images) return self
-
[docs] def reverse(self) -> 'XATextDocument': - """Reverses the text of the document. +
[docs] def auto_enhance(self, correct_red_eye: bool = False, crop_to_features: bool = False, correct_rotation: bool = False) -> 'XAImageList': + """Attempts to enhance each image in the list by applying suggested filters. - :return: A reference to the document object. - :rtype: XATextDocument + :param correct_red_eye: Whether to attempt red eye removal, defaults to False + :type correct_red_eye: bool, optional + :param crop_to_features: Whether to crop the images to focus on their main features, defaults to False + :type crop_to_features: bool, optional + :param correct_rotation: Whether attempt perspective correction by rotating the images, defaults to False + :type correct_rotation: bool, optional + :return: The list of enhanced images + :rtype: XAImageList - .. versionadded:: 0.0.4 + .. versionadded:: 0.1.0 """ - self.set_property("text", reversed(self.text)) + images = self.__partial_init() + + enhanced_images = [None] * images.count() + def enhance_image(image, index): + ci_image = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + + options = { + Quartz.kCIImageAutoAdjustRedEye: correct_red_eye, + Quartz.kCIImageAutoAdjustCrop: crop_to_features, + Quartz.kCIImageAutoAdjustLevel: correct_rotation + } + + enhancements = ci_image.autoAdjustmentFiltersWithOptions_(options) + for filter in enhancements: + filter.setValue_forKey_(ci_image, "inputImage") + ci_image = filter.outputImage() + + # Crop the result to the original image size + cropped = ci_image.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, image.size().width * 2, image.size().height * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + enhanced_images[index] = result + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(enhance_image, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(enhanced_images) return self
-
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': - return self.text.paragraphs(filter)
+
[docs] def flip_horizontally(self) -> 'XAImageList': + """Flips each image in the list horizontally. -
[docs] def sentences(self, filter: dict = None) -> 'XASentenceList': - return self.text.sentences(filter)
+ :return: The list of flipped images + :rtype: XAImageList -
[docs] def words(self, filter: dict = None) -> 'XAWordList': - return self.text.words(filter)
+ .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + flipped_images = [None] * images.count() + def flip_image(image, index): + flipped_image = AppKit.NSImage.alloc().initWithSize_(image.size()) + imageBounds = AppKit.NSMakeRect(0, 0, image.size().width, image.size().height) -
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': - return self.text.characters(filter)
+ transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(image.size().width, 0) + transform.scaleXBy_yBy_(-1, 1) -
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': - return self.text.attribute_runs(filter)
+ flipped_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + flipped_images[index] = flipped_image -
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': - return self.text.attachments(filter)
+ threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(flip_image, [image, index]) + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(flipped_images) + return self
+
[docs] def flip_vertically(self) -> 'XAImageList': + """Flips each image in the list vertically. -
[docs]class XATextList(XAList): - """A wrapper around lists of text objects that employs fast enumeration techniques. + :return: The list of flipped images + :rtype: XAImageList - .. versionadded:: 0.0.4 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): - if obj_class is None: - obj_class = XAText - super().__init__(properties, obj_class, filter) + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + flipped_images = [None] * images.count() + def flip_image(image, index): + flipped_image = AppKit.NSImage.alloc().initWithSize_(image.size()) + imageBounds = AppKit.NSMakeRect(0, 0, image.size().width, image.size().height) -
[docs] def paragraphs(self, filter: dict = None) -> List['XAParagraphList']: - ls = self.xa_elem.arrayByApplyingSelector_("paragraphs") - return [self._new_element(x, XAParagraphList) for x in ls]
+ transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(0, image.size().height) + transform.scaleXBy_yBy_(1, -1) -
[docs] def sentences(self, filter: dict = None) -> List['XASentenceList']: - return [x.sentences() for x in self]
+ flipped_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + flipped_images[index] = flipped_image -
[docs] def words(self, filter: dict = None) -> List['XAWordList']: - ls = self.xa_elem.arrayByApplyingSelector_("words") - return [self._new_element(x, XAWordList) for x in ls]
+ threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(flip_image, [image, index]) -
[docs] def characters(self, filter: dict = None) -> List['XACharacterList']: - ls = self.xa_elem.arrayByApplyingSelector_("characters") - return [self._new_element(x, XACharacterList) for x in ls]
+ while any([t.is_alive() for t in threads]): + time.sleep(0.01) -
[docs] def attribute_runs(self, filter: dict = None) -> List['XAAttributeRunList']: - ls = self.xa_elem.arrayByApplyingSelector_("attributeRuns") - return [self._new_element(x, XAAttributeRunList) for x in ls]
+ self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(flipped_images) + return self
-
[docs] def attachments(self, filter: dict = None) -> List['XAAttachmentList']: - ls = self.xa_elem.arrayByApplyingSelector_("attachments") - return [self._new_element(x, XAAttachmentList) for x in ls]
+
[docs] def rotate(self, degrees: float) -> 'XAImageList': + """Rotates each image in the list by the specified amount of degrees. - def __repr__(self): - return "<" + str(type(self)) + str(self.xa_elem.get()) + ">"
+ :param degrees: The number of degrees to rotate the images by + :type degrees: float + :return: The list of rotated images + :rtype: XAImageList -
[docs]class XAText(XAObject): - """A class for managing and interacting with the text of documents. + .. versionadded:: 0.1.0 + """ + sinDegrees = abs(math.sin(degrees * math.pi / 180.0)) + cosDegrees = abs(math.cos(degrees * math.pi / 180.0)) - .. versionadded:: 0.0.1 - """ - def __init__(self, properties): - super().__init__(properties) + images = self.__partial_init() + + rotated_images = [None] * images.count() + def rotate_image(image, index): + new_size = Quartz.CGSizeMake(image.size().height * sinDegrees + image.size().width * cosDegrees, image.size().width * sinDegrees + image.size().height * cosDegrees) + rotated_image = AppKit.NSImage.alloc().initWithSize_(new_size) - self.text: str #: The plaint ext contents of the rich text - self.color: XAColor #: The color of the first character - self.font: str #: The name of the font of the first character - self.size: int #: The size in points of the first character + imageBounds = Quartz.CGRectMake((new_size.width - image.size().width) / 2, (new_size.height - image.size().height) / 2, image.size().width, image.size().height) - @property - def text(self) -> str: - return self.xa_elem.text() + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(new_size.width / 2, new_size.height / 2) + transform.rotateByDegrees_(degrees) + transform.translateXBy_yBy_(-new_size.width / 2, -new_size.height / 2) - @text.setter - def text(self, text: str): - self.set_property("text", text) + rotated_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + rotated_image.unlockFocus() - @property - def color(self) -> 'XAColor': - return XAColor(self.xa_elem.color()) + rotated_images[index] = rotated_image - @color.setter - def color(self, color: 'XAColor'): - self.set_property("color", color.xa_elem) + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(rotate_image, [image, index]) - @property - def font(self) -> str: - return self.xa_elem.font() + while any([t.is_alive() for t in threads]): + time.sleep(0.01) - @font.setter - def font(self, font: str): - self.set_property("font", font) + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(rotated_images) + return self
- @property - def size(self) -> int: - return self.xa_elem.size() +
[docs] def crop(self, size: tuple[int, int], corner: Union[tuple[int, int], None] = None) -> 'XAImageList': + """Crops each image in the list to the specified dimensions. - @size.setter - def size(self, size: int): - self.set_property("size", size) + :param size: The dimensions to crop each image to + :type size: tuple[int, int] + :param corner: The bottom-left location to crom each image from, or None to use (0, 0), defaults to None + :type corner: Union[tuple[int, int], None] + :return: The list of cropped images + :rtype: XAImageList -
[docs] def paragraphs(self, filter: dict = None) -> 'XAParagraphList': - return self._new_element(self.xa_elem.paragraphs(), XAParagraphList, filter)
+ .. versionadded:: 0.1.0 + """ + if corner is None: + # No corner provided -- use (0,0) by default + corner = (0, 0) -
[docs] def sentences(self, filter: dict = None) -> List[str]: - raw_string = self.xa_elem.get() - sentences = [] - tokenizer = AppKit.NLTokenizer.alloc().initWithUnit_(AppKit.kCFStringTokenizerUnitSentence) - tokenizer.setString_(raw_string) - for char_range in tokenizer.tokensForRange_((0, len(raw_string))): - start = char_range.rangeValue().location - end = start + char_range.rangeValue().length - sentences.append(raw_string[start:end]) - # TODO: Only use Python/ObjC methods, not ScriptingBridge, to handle this -> 0.0.7 - # ls = AppKit.NSArray.alloc().initWithArray_(sentences) - # return self._new_element(sentences, XASentenceList, filter) - return sentences
+ images = self.__partial_init() + + cropped_images = [None] * images.count() + def crop_image(image, index): + cropped_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(size[0], size[1])) + imageBounds = AppKit.NSMakeRect(corner[0], corner[1], image.size().width, image.size().height) -
[docs] def words(self, filter: dict = None) -> 'XAWordList': - return self._new_element(self.xa_elem.words(), XAWordList, filter)
+ cropped_image.lockFocus() + image.drawInRect_(imageBounds) + cropped_image.unlockFocus() + cropped_images[index] = cropped_image -
[docs] def characters(self, filter: dict = None) -> 'XACharacterList': - return self._new_element(self.xa_elem.characters().get(), XACharacterList, filter)
+ threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(crop_image, [image, index]) -
[docs] def attribute_runs(self, filter: dict = None) -> 'XAAttributeRunList': - return self._new_element(self.xa_elem.attributeRuns(), XAAttributeRunList, filter)
+ while any([t.is_alive() for t in threads]): + time.sleep(0.01) -
[docs] def attachments(self, filter: dict = None) -> 'XAAttachmentList': - return self._new_element(self.xa_elem.attachments(), XAAttachmentList, filter)
+ self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(cropped_images) + return self
- def __len__(self): - return len(self.xa_elem.get()) +
[docs] def scale(self, scale_factor_x: float, scale_factor_y: Union[float, None] = None) -> 'XAImageList': + """Scales each image in the list by the specified horizontal and vertical factors. - def __str__(self): - if isinstance(self.xa_elem, str): - return self.xa_elem - return str(self.xa_elem.get()) + :param scale_factor_x: The factor by which to scale each image in the X dimension + :type scale_factor_x: float + :param scale_factor_y: The factor by which to scale each image in the Y dimension, or None to match the horizontal factor, defaults to None + :type scale_factor_y: Union[float, None] + :return: The list of scaled images + :rtype: XAImageList - def __repr__(self): - if isinstance(self.xa_elem, str): - return self.xa_elem - return str(self.xa_elem.get())
+ .. versionadded:: 0.1.0 + """ + if scale_factor_y is None: + scale_factor_y = scale_factor_x + images = self.__partial_init() + + scaled_images = [None] * self.xa_elem.count() + def scale_image(image, index): + scaled_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(image.size().width * scale_factor_x, image.size().height * scale_factor_y)) + imageBounds = AppKit.NSMakeRect(0, 0, image.size().width, image.size().height) + transform = AppKit.NSAffineTransform.alloc().init() + transform.scaleXBy_yBy_(scale_factor_x, scale_factor_y) + scaled_image.lockFocus() + transform.concat() + image.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + scaled_image.unlockFocus() + scaled_images[index] = scaled_image -
[docs]class XAParagraphList(XATextList): - """A wrapper around lists of paragraphs that employs fast enumeration techniques. + threads = [None] * self.xa_elem.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(scale_image, [image, index]) - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAParagraph)
+ while any([t.is_alive() for t in threads]): + time.sleep(0.01) -
[docs]class XAParagraph(XAText): - """A class for managing and interacting with paragraphs in text documents. + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(scaled_images) + return self
- .. versionadded:: 0.0.1 - """ - def __init__(self, properties): - super().__init__(properties)
+
[docs] def pad(self, horizontal_border_width: int = 50, vertical_border_width: int = 50, pad_color: Union[XAColor, None] = None) -> 'XAImageList': + """Pads each image in the list with the specified color; add a border around each image in the list with the specified vertical and horizontal width. + + :param horizontal_border_width: The border width, in pixels, in the x-dimension, defaults to 50 + :type horizontal_border_width: int + :param vertical_border_width: The border width, in pixels, in the y-dimension, defaults to 50 + :type vertical_border_width: int + :param pad_color: The color of the border, or None for a white border, defaults to None + :type pad_color: Union[XAColor, None] + :return: The list of padded images + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if pad_color is None: + # No color provided -- use white by default + pad_color = XAColor.white() + + images = self.__partial_init() + + padded_images = [None] * images.count() + def pad_image(image, index): + new_width = image.size().width + horizontal_border_width * 2 + new_height = image.size().height + vertical_border_width * 2 + color_swatch = pad_color.make_swatch(new_width, new_height) + + color_swatch.xa_elem.lockFocus() + bounds = AppKit.NSMakeRect(horizontal_border_width, vertical_border_width, image.size().width, image.size().height) + image.drawInRect_(bounds) + color_swatch.xa_elem.unlockFocus() + padded_images[index] = color_swatch.xa_elem + + threads = [None] * images.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(pad_image, [image, index]) + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(padded_images) + return self
-# class XASentenceList(XATextList): -# """A wrapper around lists of sentences that employs fast enumeration techniques. +
[docs] def overlay_image(self, image: 'XAImage', location: Union[tuple[int, int], None] = None, size: Union[tuple[int, int], None] = None) -> 'XAImageList': + """Overlays an image on top of each image in the list, at the specified location, with the specified size. -# .. versionadded:: 0.0.5 -# """ -# def __init__(self, properties: dict, filter: Union[dict, None] = None): -# super().__init__(properties, XASentence, filter) + :param image: The image to overlay on top of each image in the list + :type image: XAImage + :param location: The bottom-left point of the overlaid image in the results, or None to use the bottom-left point of each background image, defaults to None + :type location: Union[tuple[int, int], None] + :param size: The width and height of the overlaid image, or None to use the overlaid's images existing width and height, or (-1, -1) to use the dimensions of each background images, defaults to None + :type size: Union[tuple[int, int], None] + :return: The list of images with the specified image overlaid on top of them + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use the bottom-left point of the background image by default + location = (0, 0) + + images = self.__partial_init() + overlayed_images = [None] * images.count() + def overlay_image(img, index, image, size, location): + if size is None: + # No dimensions provided -- use size of overlay image by default + size = image.size + elif size == (-1, -1): + # Use remaining width/height of background image + size = (img.size().width - location[0], img.size().height - location[1]) + elif size[0] == -1: + # Use remaining width of background image + provided height + size = (img.size().width - location[0], size[1]) + elif size[1] == -1: + # Use remaining height of background image + provided width + size = (size[1], img.size().width - location[1]) + + img.lockFocus() + bounds = AppKit.NSMakeRect(location[0], location[1], size[0], size[1]) + image.xa_elem.drawInRect_(bounds) + img.unlockFocus() + overlayed_images[index] = img + + threads = [None] * images.count() + for index, img in enumerate(images): + threads[index] = self._spawn_thread(overlay_image, [img, index, image, size, location]) -# class XASentence(XAText): -# """A class for managing and interacting with sentences in text documents. + while any([t.is_alive() for t in threads]): + time.sleep(0.01) -# .. versionadded:: 0.0.1 -# """ -# def __init__(self, properties): -# super().__init__(properties) + self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(overlayed_images) + return self
-# def words(self, filter: dict = None) -> 'XAWordList': -# ls = AppKit.NSArray.alloc().initWithArray_(self.xa_elem.split(" ")) -# return self._new_element(ls, XAWordList, filter) +
[docs] def overlay_text(self, text: str, location: Union[tuple[int, int], None] = None, font_size: float = 12, font_color: Union[XAColor, None] = None) -> 'XAImageList': + """Overlays text of the specified size and color at the provided location within each image of the list. + :param text: The text to overlay onto each image of the list + :type text: str + :param location: The bottom-left point of the start of the text, or None to use (5, 5), defaults to None + :type location: Union[tuple[int, int], None] + :param font_size: The font size, in pixels, of the text, defaults to 12 + :type font_size: float + :param font_color: The color of the text, or None to use black, defaults to None + :type font_color: XAColor + :return: The list of images with the specified text overlaid on top of them + :rtype: XAImageList + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use (5, 5) by default + location = (5, 5) + + if font_color is None: + # No color provided -- use black by default + font_color = XAColor.black() + + font = AppKit.NSFont.userFontOfSize_(font_size) + images = self.__partial_init() + overlayed_images = [None] * self.xa_elem.count() + def overlay_text(image, index): + textRect = Quartz.CGRectMake(location[0], 0, image.size().width - location[0], location[1]) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: font_color.xa_elem + } + image.lockFocus() + AppKit.NSString.alloc().initWithString_(text).drawInRect_withAttributes_(textRect, attributes) + image.unlockFocus() + overlayed_images[index] = image + threads = [None] * self.xa_elem.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(overlay_text, [image, index]) -
[docs]class XAWordList(XATextList): - """A wrapper around lists of words that employs fast enumeration techniques. + while any([t.is_alive() for t in threads]): + time.sleep(0.01) - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAWord)
+ self.modified = True + self.xa_elem = AppKit.NSMutableArray.alloc().initWithArray_(overlayed_images) + return self
-
[docs]class XAWord(XAText): - """A class for managing and interacting with words in text documents. +
[docs] def extract_text(self) -> list[str]: + """Extracts and returns a list of all visible text in each image of the list. - .. versionadded:: 0.0.1 - """ - def __init__(self, properties): - super().__init__(properties)
+ :return: The array of extracted text strings + :rtype: list[str] + :Example: + >>> import PyXA + >>> test = PyXA.XAImage("/Users/ExampleUser/Downloads/Example.jpg") + >>> print(test.extract_text()) + ["HERE'S TO THE", 'CRAZY ONES', 'the MISFITS the REBELS', 'THE TROUBLEMAKERS', ...] + + .. versionadded:: 0.1.0 + """ + images = self.__partial_init() + + extracted_strings = [None] * self.xa_elem.count() + def get_text(image, index): + # Prepare CGImage + ci_image = Quartz.CIImage.imageWithCGImage_(image.CGImage()) + context = Quartz.CIContext.alloc().initWithOptions_(None) + img = context.createCGImage_fromRect_(ci_image, ci_image.extent()) + + # Handle request completion + image_strings = [] + def recognize_text_handler(request, error): + observations = request.results() + for observation in observations: + recognized_strings = observation.topCandidates_(1)[0].string() + image_strings.append(recognized_strings) + + # Perform request and return extracted text + request = Vision.VNRecognizeTextRequest.alloc().initWithCompletionHandler_(recognize_text_handler) + request_handler = Vision.VNImageRequestHandler.alloc().initWithCGImage_options_(img, None) + request_handler.performRequests_error_([request], None) + extracted_strings[index] = image_strings + + threads = [None] * self.xa_elem.count() + for index, image in enumerate(images): + threads[index] = self._spawn_thread(get_text, [image, index]) + + while any([t.is_alive() for t in threads]): + time.sleep(0.01) + return extracted_strings
-
[docs]class XACharacterList(XATextList): - """A wrapper around lists of characters that employs fast enumeration techniques. +
[docs] def show_in_preview(self): + """Opens each image in the list in Preview. - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XACharacter)
+ .. versionadded:: 0.1.0 + """ + for image in self: + image.show_in_preview()
+ +
[docs] def save(self, file_paths: list[Union[XAPath, str]]): + """Saves each image to a file on the disk. + + :param file_path: The path at which to save the image file. Any existing file at that location will be overwritten, defaults to None + :type file_path: Union[XAPath, str, None] + + .. versionadded:: 0.1.0 + """ + for index, image in enumerate(self): + path = None + if len(file_paths) > index: + path = file_paths[index] + image.save(path)
+ +
[docs] def get_clipboard_representation(self) -> list[AppKit.NSImage]: + """Gets a clipboard-codable representation of each image in the list. + + When the clipboard content is set to a list of image, the raw data of each image is added to the clipboard. You can then + + :return: A list of media item file URLs + :rtype: list[NSURL] + + .. versionadded:: 0.0.8 + """ + data = [] + for image in self.__partial_init(): + if image.TIFFRepresentation(): + data.append(image) + return data
-
[docs]class XACharacter(XAText): - """A class for managing and interacting with characters in text documents. +
[docs]class XAImage(XAObject, XAClipboardCodable): + """A wrapper around NSImage with specialized automation methods. - .. versionadded:: 0.0.1 + .. versionadded:: 0.0.2 """ - def __init__(self, properties): - super().__init__(properties)
+ def __init__(self, image_reference: Union[str, XAPath, AppKit.NSURL, AppKit.NSImage, None] = None, data: Union[AppKit.NSData, None] = None): + self.size: tuple[int, int] #: The dimensions of the image + self.file: Union[XAPath, None] = None #: The path to the image file, if one exists + self.data: str #: The TIFF representation of the image + self.modified: bool = False #: Whether the image data has been modified since the object was originally created + self.xa_elem = None + self.__vibrance = None + self.__gamma = None + self.__tint = None + self.__temperature = None + self.__white_point = None + self.__highlight = None + self.__shadow = None -
[docs]class XAAttributeRunList(XATextList): - """A wrapper around lists of attribute runs that employs fast enumeration techniques. + if data is not None: + # Deprecated as of 0.1.0 -- Pass data as the image_reference instead + logging.warning("Setting the data parameter when initalizing an XAImage is deprecated functionality and will be removed in a future release") + self.xa_elem = AppKit.NSImage.alloc().initWithData_(data) + else: + self.file = image_reference + match image_reference: + case None: + logging.debug("Image ref is none -- use empty NSImage") + self.xa_elem = AppKit.NSImage.alloc().init() + + case {"element": str(ref)}: + logging.debug("Image ref is string from XAList --> Reinit XAImage with string") + self.file = ref + self.xa_elem = XAImage(ref).xa_elem + + case {"element": XAImage() as image}: + logging.debug("Image ref is XAImage from XAList --> Set xa_elem to that image's xa_elem") + self.file = image.file + self.xa_elem = image.xa_elem + + case {"element": AppKit.NSImage() as image}: + logging.debug("Image ref is NSImage from XAList --> Set xa_elem to that image") + self.xa_elem = image + + case str() as ref if "://" in ref: + logging.debug("Image ref is web/file URL string --> Init NSImage with URL") + url = XAURL(ref).xa_elem + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(url) + + case str() as ref if os.path.exists(ref) or os.path.exists(os.getcwd() + "/" + ref): + logging.debug("Image ref is file path string --> Init NSImage with path URL") + path = XAPath(ref).xa_elem + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(path) + + case XAPath() as path: + logging.debug("Image ref is file path object --> Init NSImage with path URL") + self.file = path.path + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(path.xa_elem) + + case XAURL() as url: + logging.debug("Image ref is web/file URL object --> Init NSImage with URL") + self.file = url.url + self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(url.xa_elem) + + case str() as raw_string: + logging.debug("Image ref is raw string --> Make image from string") + font = AppKit.NSFont.monospacedSystemFontOfSize_weight_(15, AppKit.NSFontWeightMedium) + text = AppKit.NSString.alloc().initWithString_(raw_string) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: XAColor.black().xa_elem + } + text_size = text.sizeWithAttributes_(attributes) + + # Make a white background to overlay the text on + swatch = XAColor.white().make_swatch(text_size.width + 20, text_size.height + 20) + text_rect = AppKit.NSMakeRect(10, 10, text_size.width, text_size.height) + + # Overlay the text + swatch.xa_elem.lockFocus() + text.drawInRect_withAttributes_(text_rect, attributes) + swatch.xa_elem.unlockFocus() + self.xa_elem = swatch.xa_elem + + case XAImage() as image: + self.file = image.file + self.xa_elem = image.xa_elem + + case XAObject(): + logging.debug("Image ref is XAObject --> Obtain proper ref via XAImageLike protocol") + self.xa_elem = XAImage(image_reference.get_image_representation()).xa_elem + + case AppKit.NSData() as data: + logging.debug("Image ref is NSData --> Init NSImage with data") + self.xa_elem = AppKit.NSImage.alloc().initWithData_(data) + + case AppKit.NSImage() as image: + logging.debug("Image ref is NSImage --> Set xa_elem to that image") + self.xa_elem = image + + case _: + logging.debug(f"Image ref is of unaccounted for type {type(image_reference)} --> Raise TypeError") + raise TypeError(f"Error: Cannot initialize XAImage using {type(image_reference)} type.") + + def __update_image(self, modified_image: Quartz.CIImage) -> 'XAImage': + # Crop the result to the original image size + cropped = modified_image.imageByCroppingToRect_(Quartz.CGRectMake(0, 0, self.size[0] * 2, self.size[1] * 2)) + + # Convert back to NSImage + rep = AppKit.NSCIImageRep.imageRepWithCIImage_(cropped) + result = AppKit.NSImage.alloc().initWithSize_(rep.size()) + result.addRepresentation_(rep) + + # Update internal data + self.xa_elem = result + self.modified = True + return self - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAAttributeRun)
+ @property + def size(self) -> tuple[int, int]: + """The dimensions of the image, in pixels. -
[docs]class XAAttributeRun(XAText): - """A class for managing and interacting with attribute runs in text documents. + .. versionadded:: 0.1.0 + """ + return tuple(self.xa_elem.size()) - .. versionadded:: 0.0.1 - """ - def __init__(self, properties): - super().__init__(properties)
+ @property + def data(self) -> AppKit.NSData: + return self.xa_elem.TIFFRepresentation() + @property + def has_alpha_channel(self) -> bool: + """Whether the image has an alpha channel or not. + .. versionadded:: 0.1.0 + """ + reps = self.xa_elem.representations() + if len(reps) > 0: + return reps[0].hasAlpha() + # TODO: Make sure this is never a false negative + return False + @property + def is_opaque(self) -> bool: + """Whether the image contains transparent pixels or not. -
[docs]class XAAttachmentList(XATextList): - """A wrapper around lists of text attachments that employs fast enumeration techniques. + .. versionadded:: 0.1.0 + """ + reps = self.xa_elem.representations() + if len(reps) > 0: + return reps[0].isOpaque() + # TODO: Make sure this is never a false negative + return False - .. versionadded:: 0.0.5 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, filter, XAAttachment)
+ @property + def color_space_name(self) -> Union[str, None]: + """The name of the color space that the image currently uses. -
[docs]class XAAttachment(XAObject): - """A class for managing and interacting with attachments in text documents. + .. versionadded:: 0.1.0 + """ + reps = self.xa_elem.representations() + if len(reps) > 0: + return reps[0].colorSpaceName() + # TODO: Make sure this is never a false negative + return None - .. versionadded:: 0.0.1 - """ - def __init__(self, properties): - super().__init__(properties)
+ @property + def gamma(self) -> float: + """The gamma value for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__gamma is not None: + return self.__gamma + return -1 + + @gamma.setter + def gamma(self, gamma: float): + self.__gamma = gamma + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIGammaAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(gamma, "inputPower") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + + @property + def vibrance(self) -> Union[float, None]: + """The vibrance value for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__vibrance is not None: + return self.__vibrance + return -1 + + @vibrance.setter + def vibrance(self, vibrance: float = 1): + self.__vibrance = vibrance + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIVibrance") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(vibrance, "inputAmount") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped) + @property + def tint(self) -> Union[float, None]: + """The tint setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__tint is not None: + return self.__tint + return -1 + + @tint.setter + def tint(self, tint: float): + # -100 to 100 + temp_and_tint = Quartz.CIVector.vectorWithX_Y_(6500, tint) + self.__tint = tint + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CITemperatureAndTint") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(temp_and_tint, "inputTargetNeutral") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + @property + def temperature(self) -> Union[float, None]: + """The temperature setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__temperature is not None: + return self.__temperature + return -1 + + @temperature.setter + def temperature(self, temperature: float): + # 2000 to inf + temp_and_tint = Quartz.CIVector.vectorWithX_Y_(temperature, 0) + self.__temperature = temperature + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CITemperatureAndTint") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(temp_and_tint, "inputTargetNeutral") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) -
[docs]class XAColorList(XATextList): - """A wrapper around lists of colors that employs fast enumeration techniques. + @property + def white_point(self) -> Union['XAColor', None]: + """The white point setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__white_point is not None: + return self.__white_point + return -1 + + @white_point.setter + def white_point(self, white_point: XAColor): + self.__white_point = white_point + ci_white_point = Quartz.CIColor.alloc().initWithColor_(white_point.xa_elem) + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIWhitePointAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(ci_white_point, "inputColor") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) - .. versionadded:: 0.0.6 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, XAColor, filter)
+ @property + def highlight(self) -> float: + """The highlight setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__highlight is not None: + return self.__highlight + return -1 + + @highlight.setter + def highlight(self, highlight: float): + self.__highlight = highlight + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIHighlightShadowAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(highlight, "inputHighlightAmount") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) -
[docs]class XAColor(XAObject): - def __init__(self, *args): - if len(args) == 1: - self.copy_color(args[0]) + @property + def shadow(self) -> float: + """The shadow setting for the image, once it has been manually set. Otherwise, the value is None. + + .. versionadded:: 0.1.0 + """ + if self.__shadow is not None: + return self.__shadow + return -1 + + @shadow.setter + def shadow(self, shadow: float): + self.__shadow = shadow + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIHighlightShadowAdjust") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(self.__highlight or 1, "inputHighlightAmount") + filter.setValue_forKey_(shadow, "inputShadowAmount") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + self.__update_image(uncropped) + +
[docs] def open(*images: Union[str, XAPath, list[Union[str, XAPath]]]) -> Union['XAImage', XAImageList]: + """Initializes one or more images from files. + + :param images: The image(s) to open + :type images: Union[str, XAPath, list[Union[str, XAPath]]] + :return: The newly created image object, or a list of image objects + :rtype: Union[XAImage, XAImageList] + + .. versionadded:: 0.1.0 + """ + if len(images) == 1: + images = images[0] + + if isinstance(images, list) or isinstance(images, tuple): + return XAImageList({"element": images}) else: - red = args[0] if len(args) >= 0 else 255 - green = args[1] if len(args) >= 1 else 255 - blue = args[2] if len(args) >= 3 else 255 - alpha = args[3] if len(args) == 4 else 1.0 - self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red, green, blue, alpha) + return XAImage(images)
-
[docs] def copy_color(self, color: AppKit.NSColor) -> 'XAColor': - self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_( - color.redComponent(), - color.greenComponent(), - color.blueComponent(), - color.alphaComponent() - ) - return self
+
[docs] @staticmethod + def image_from_text(text: str, font_size: int = 15, font_name: str = "Menlo", font_color: XAColor = XAColor.black(), background_color: XAColor = XAColor.white(), inset: int = 10) -> 'XAImage': + """Initializes an image of the provided text overlaid on the specified background color. + + :param text: The text to create an image of + :type text: str + :param font_size: The font size of the text, defaults to 15 + :type font_size: int, optional + :param font_name: The color of the text, defaults to XAColor.black() + :type font_name: str, optional + :param font_color: The name of the font to use for the text, defaults to ".SF NS Mono Light Medium" + :type font_color: XAColor, optional + :param background_color: The color to overlay the text on top of, defaults to XAColor.white() + :type background_color: XAColor, optional + :param inset: The width of the space between the text and the edge of the background color in the resulting image, defaults to 10 + :type inset: int, optional + :return: XAImage + :rtype: The resulting image object + + .. versionadded:: 0.1.0 + """ + font = AppKit.NSFont.fontWithName_size_(font_name, font_size) + print(font.displayName()) + text = AppKit.NSString.alloc().initWithString_(text) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: font_color.xa_elem + } + text_size = text.sizeWithAttributes_(attributes) + + # Make a white background to overlay the text on + swatch = background_color.make_swatch(text_size.width + inset * 2, text_size.height + inset * 2) + text_rect = AppKit.NSMakeRect(inset, inset, text_size.width, text_size.height) + + # Overlay the text + swatch.xa_elem.lockFocus() + text.drawInRect_withAttributes_(text_rect, attributes) + swatch.xa_elem.unlockFocus() + return swatch
-
[docs] def set_rgba(self, red, green, blue, alpha): - self.xa_elem = AppKit.NSCalibratedRGBColor.alloc().initWithRed_green_blue_alpha_(red, green, blue, alpha) +
[docs] def edges(self, intensity: float = 1.0) -> 'XAImage': + """Detects the edges in the image and highlights them colorfully, blackening other areas of the image. + + :param intensity: The degree to which edges are highlighted. Higher is brighter. Defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIEdges") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def gaussian_blur(self, intensity: float = 10) -> 'XAImage': + """Blurs the image using a Gaussian filter. + + :param intensity: The strength of the blur effect, defaults to 10 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIGaussianBlur") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputRadius") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def reduce_noise(self, noise_level: float = 0.02, sharpness: float = 0.4) -> 'XAImage': + """Reduces noise in the image by sharpening areas with a luminance delta below the specified noise level threshold. + + :param noise_level: The threshold for luminance changes in an area below which will be considered noise, defaults to 0.02 + :type noise_level: float + :param sharpness: The sharpness of the resulting image, defaults to 0.4 + :type sharpness: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CINoiseReduction") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(noise_level, "inputNoiseLevel") + filter.setValue_forKey_(sharpness, "inputSharpness") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def pixellate(self, pixel_size: float = 8.0) -> 'XAImage': + """Pixellates the image. + + :param pixel_size: The size of the pixels, defaults to 8.0 + :type pixel_size: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIPixellate") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(pixel_size, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def outline(self, threshold: float = 0.1) -> 'XAImage': + """Outlines detected edges within the image in black, leaving the rest transparent. + + :param threshold: The threshold to use when separating edge and non-edge pixels. Larger values produce thinner edge lines. Defaults to 0.1 + :type threshold: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CILineOverlay") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(threshold, "inputThreshold") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def invert(self) -> 'XAImage': + """Inverts the color of the image. + + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIColorInvert") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def sepia(self, intensity: float = 1.0) -> 'XAImage': + """Applies a sepia filter to the image; maps all colors of the image to shades of brown. + + :param intensity: The opacity of the sepia effect. A value of 0 will have no impact on the image. Defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CISepiaTone") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def vignette(self, intensity: float = 1.0) -> 'XAImage': + """Applies vignette shading to the corners of the image. + + :param intensity: The intensity of the vignette effect, defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIVignette") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def depth_of_field(self, focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] = None, intensity: float = 10.0, focal_region_saturation: float = 1.5) -> 'XAImage': + """Applies a depth of field filter to the image, simulating a tilt & shift effect. + + :param focal_region: Two points defining a line within the image to focus the effect around (pixels around the line will be in focus), or None to use the center third of the image, defaults to None + :type focal_region: Union[tuple[tuple[int, int], tuple[int, int]], None] + :param intensity: Controls the amount of distance around the focal region to keep in focus. Higher values decrease the distance before the out-of-focus effect starts. Defaults to 10.0 + :type intensity: float + :param focal_region_saturation: Adjusts the saturation of the focial region. Higher values increase saturation. Defaults to 1.5 (1.5x default saturation) + :type focal_region_saturation: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if focal_region is None: + center_top = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 3) + center_bottom = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 3 * 2) + focal_region = (center_top, center_bottom) + else: + point1 = Quartz.CIVector.vectorWithX_Y_(focal_region[0]) + point2 = Quartz.CIVector.vectorWithX_Y_(focal_region[1]) + focal_region = (point1, point2) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIDepthOfField") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(focal_region[0], "inputPoint0") + filter.setValue_forKey_(focal_region[1], "inputPoint1") + filter.setValue_forKey_(intensity, "inputRadius") + filter.setValue_forKey_(focal_region_saturation, "inputSaturation") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def crystallize(self, crystal_size: float = 20.0) -> 'XAImage': + """Applies a crystallization filter to the image. Creates polygon-shaped color blocks by aggregating pixel values. + + :param crystal_size: The radius of the crystals, defaults to 20.0 + :type crystal_size: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CICrystallize") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(crystal_size, "inputRadius") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def comic(self) -> 'XAImage': + """Applies a comic filter to the image. Outlines edges and applies a color halftone effect. + + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIComicEffect") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def pointillize(self, point_size: float = 20.0) -> 'XAImage': + """Applies a pointillization filter to the image. + + :param crystal_size: The radius of the points, defaults to 20.0 + :type crystal_size: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIPointillize") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(point_size, "inputRadius") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def bloom(self, intensity: float = 0.5) -> 'XAImage': + """Applies a bloom effect to the image. Softens edges and adds a glow. + + :param intensity: The strength of the softening and glow effects, defaults to 0.5 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIBloom") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def monochrome(self, color: XAColor, intensity: float = 1.0) -> 'XAImage': + """Remaps the colors of the image to shades of the specified color. + + :param color: The color of map the image's colors to + :type color: XAColor + :param intensity: The strength of recoloring effect. Higher values map colors to darker shades of the provided color. Defaults to 1.0 + :type intensity: float + :return: The resulting image after applying the filter + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + ci_color = Quartz.CIColor.alloc().initWithColor_(color.xa_elem) + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIColorMonochrome") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(ci_color, "inputColor") + filter.setValue_forKey_(intensity, "inputIntensity") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def bump(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, curvature: float = 0.5) -> 'XAImage': + """Creates a concave (inward) or convex (outward) bump at the specified location within the image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The radius of the bump in pixels, defaults to 300.0 + :type radius: float + :param curvature: Controls the direction and intensity of the bump's curvature. Positive values create convex bumps while negative values create concave bumps. Defaults to 0.5 + :type curvature: float + :return: The resulting image after applying the distortion + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIBumpDistortion") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(curvature, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def pinch(self, center: Union[tuple[int, int], None] = None, intensity: float = 0.5) -> 'XAImage': + """Creates an inward pinch distortion at the specified location within the image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param intensity: Controls the scale of the pinch effect. Higher values stretch pixels away from the specified center to a greater degree. Defaults to 0.5 + :type intensity: float + :return: The resulting image after applying the distortion + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CIPinchDistortion") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(intensity, "inputScale") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def twirl(self, center: Union[tuple[int, int], None] = None, radius: float = 300.0, angle: float = 3.14) -> 'XAImage': + """Creates a twirl distortion by rotating pixels around the specified location within the image. + + :param center: The center point of the effect, or None to use the center of the image, defaults to None + :type center: Union[tuple[int, int], None] + :param radius: The pixel radius around the centerpoint that defines the area to apply the effect to, defaults to 300.0 + :type radius: float + :param angle: The angle of the twirl in radians, defaults to 3.14 + :type angle: float + :return: The resulting image after applying the distortion + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if center is None: + center = Quartz.CIVector.vectorWithX_Y_(self.size[0] / 2, self.size[1] / 2) + else: + center = Quartz.CIVector.vectorWithX_Y_(center[0], center[1]) + + image = Quartz.CIImage.imageWithData_(self.data) + filter = Quartz.CIFilter.filterWithName_("CITwirlDistortion") + filter.setDefaults() + filter.setValue_forKey_(image, "inputImage") + filter.setValue_forKey_(center, "inputCenter") + filter.setValue_forKey_(radius, "inputRadius") + filter.setValue_forKey_(angle, "inputAngle") + uncropped = filter.valueForKey_(Quartz.kCIOutputImageKey) + return self.__update_image(uncropped)
+ +
[docs] def auto_enhance(self, correct_red_eye: bool = False, crop_to_features: bool = False, correct_rotation: bool = False) -> 'XAImage': + """Attempts to enhance the image by applying suggested filters. + + :param correct_red_eye: Whether to attempt red eye removal, defaults to False + :type correct_red_eye: bool, optional + :param crop_to_features: Whether to crop the image to focus on the main features with it, defaults to False + :type crop_to_features: bool, optional + :param correct_rotation: Whether attempt perspective correction by rotating the image, defaults to False + :type correct_rotation: bool, optional + :return: The resulting image after applying the enchantments + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + ci_image = Quartz.CIImage.imageWithData_(self.data) + options = { + Quartz.kCIImageAutoAdjustRedEye: correct_red_eye, + Quartz.kCIImageAutoAdjustCrop: crop_to_features, + Quartz.kCIImageAutoAdjustLevel: correct_rotation + } + enhancements = ci_image.autoAdjustmentFiltersWithOptions_(options) + print(enhancements) + for filter in enhancements: + filter.setValue_forKey_(ci_image, "inputImage") + ci_image = filter.outputImage() + return self.__update_image(ci_image)
+ +
[docs] def flip_horizontally(self) -> 'XAImage': + """Flips the image horizontally. + + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + flipped_image = AppKit.NSImage.alloc().initWithSize_(self.xa_elem.size()) + imageBounds = AppKit.NSMakeRect(0, 0, self.size[0], self.size[1]) + + transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(self.size[0], 0) + transform.scaleXBy_yBy_(-1, 1) + + flipped_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + self.xa_elem = flipped_image + self.modified = True return self
-
[docs] def red(self): - return self.xa_elem.redComponent()
+
[docs] def flip_vertically(self) -> 'XAImage': + """Flips the image vertically. -
[docs] def green(self): - return self.xa_elem.greenComponent()
+ :return: The image object, modifications included + :rtype: XAImage -
[docs] def blue(self): - return self.xa_elem.blueComponent()
+ .. versionadded:: 0.1.0 + """ + flipped_image = AppKit.NSImage.alloc().initWithSize_(self.xa_elem.size()) + imageBounds = AppKit.NSMakeRect(0, 0, self.size[0], self.size[1]) -
[docs] def alpha(self): - return self.xa_elem.alphaComponent()
+ transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(0, self.size[1]) + transform.scaleXBy_yBy_(1, -1) -
[docs] def set_hsla(self, hue, saturation, brightness, alpha): - # Alpha is 0.0 to 1.0 - self.xa_elem = AppKit.NSCalibratedRGBColor.initWithHue_saturation_brightness_alpha_(hue, saturation, brightness, alpha) + flipped_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + flipped_image.unlockFocus() + self.xa_elem = flipped_image + self.modified = True return self
-
[docs] def hue(self): - return self.xa_elem.hueComponent()
+
[docs] def rotate(self, degrees: float) -> 'XAImage': + """Rotates the image clockwise by the specified number of degrees. -
[docs] def saturation(self): - return self.xa_elem.saturationComponent()
+ :param degrees: The number of degrees to rotate the image by + :type degrees: float + :return: The image object, modifications included + :rtype: XAImage -
[docs] def brightness(self): - return self.xa_elem.brightnessComponent()
+ .. versionadded:: 0.1.0 + """ + sinDegrees = abs(math.sin(degrees * math.pi / 180.0)) + cosDegrees = abs(math.cos(degrees * math.pi / 180.0)) + newSize = Quartz.CGSizeMake(self.size[1] * sinDegrees + self.size[0] * cosDegrees, self.size[0] * sinDegrees + self.size[1] * cosDegrees) + rotated_image = AppKit.NSImage.alloc().initWithSize_(newSize) -
[docs] def mix_with(self, color: 'XAColor', fraction: int = 0.5) -> 'XAColor': - new_color = self.xa_elem.blendedColorWithFraction_ofColor_(fraction, color.xa_elem) - return XAColor(new_color.redComponent(), new_color.greenComponent(), new_color.blueComponent(), new_color.alphaComponent())
+ imageBounds = Quartz.CGRectMake((newSize.width - self.size[0]) / 2, (newSize.height - self.size[1]) / 2, self.size[0], self.size[1]) -
[docs] def brighten(self, amount: float = 0.5) -> 'XAColor': - self.xa_elem.highlightWithLevel_(amount) - return self
+ transform = AppKit.NSAffineTransform.alloc().init() + transform.translateXBy_yBy_(newSize.width / 2, newSize.height / 2) + transform.rotateByDegrees_(degrees) + transform.translateXBy_yBy_(-newSize.width / 2, -newSize.height / 2) -
[docs] def darken(self, amount: float = 0.5) -> 'XAColor': - self.xa_elem.shadowWithLevel_(amount) + rotated_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + rotated_image.unlockFocus() + self.xa_elem = rotated_image + self.modified = True return self
- def __repr__(self): - return f"<{str(type(self))}r={str(self.red())}, g={self.green()}, b={self.blue()}, a={self.alpha()}>"
+
[docs] def crop(self, size: tuple[int, int], corner: Union[tuple[int, int], None] = None) -> 'XAImage': + """Crops the image to the specified dimensions. + :param size: The width and height of the resulting image + :type size: tuple[int, int] + :param corner: The bottom-left corner location from which to crop the image, or None to use (0, 0), defaults to None + :type corner: Union[tuple[int, int], None] + :return: The image object, modifications included + :rtype: XAImage + .. versionadded:: 0.1.0 + """ + if corner is None: + # No corner provided -- use (0,0) by default + corner = (0, 0) + cropped_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(size[0], size[1])) + imageBounds = AppKit.NSMakeRect(corner[0], corner[1], self.size[0], self.size[1]) -
[docs]class XAImageList(XAList): - """A wrapper around lists of images that employs fast enumeration techniques. + cropped_image.lockFocus() + self.xa_elem.drawInRect_(imageBounds) + cropped_image.unlockFocus() + self.xa_elem = cropped_image + self.modified = True + return self
- .. versionadded:: 0.0.3 - """ - def __init__(self, properties: dict, filter: Union[dict, None] = None): - super().__init__(properties, XAImage, filter)
+
[docs] def scale(self, scale_factor_x: float, scale_factor_y: Union[float, None] = None) -> 'XAImage': + """Scales the image by the specified horizontal and vertical factors. -
[docs]class XAImage(XAObject, XAClipboardCodable): - """A wrapper around NSImage with specialized automation methods. + :param scale_factor_x: The factor by which to scale the image in the X dimension + :type scale_factor_x: float + :param scale_factor_y: The factor by which to scale the image in the Y dimension, or None to match the horizontal factor, defaults to None + :type scale_factor_y: Union[float, None] + :return: The image object, modifications included + :rtype: XAImage - .. versionadded:: 0.0.2 - """ - def __init__(self, file: Union[str, AppKit.NSURL, AppKit.NSImage, None] = None, data: Union[AppKit.NSData, None] = None, name: Union[str, None] = None): - self.size: Tuple[int, int] #: The dimensions of the image - self.name: str #: The name of the image + .. versionadded:: 0.1.0 + """ + if scale_factor_y is None: + scale_factor_y = scale_factor_x - if data is not None: - self.xa_elem = AppKit.NSImage.alloc().initWithData_(data) - else: - if file is None: - self.xa_elem = AppKit.NSImage.alloc().init() - else: - if isinstance(file, AppKit.NSImage): - self.xa_elem = AppKit.NSImage.alloc().initWithData_(file.TIFFRepresentation()) - else: - if isinstance(file, str): - if file.startswith("/"): - file = XAPath(file) - else: - file = XAURL(file) - self.xa_elem = AppKit.NSImage.alloc().initWithContentsOfURL_(file.xa_elem) - self.name = name or "image" + scaled_image = AppKit.NSImage.alloc().initWithSize_(AppKit.NSMakeSize(self.size[0] * scale_factor_x, self.size[1] * scale_factor_y)) + imageBounds = AppKit.NSMakeRect(0, 0, self.size[0], self.size[1]) - @property - def size(self): - return self.xa_elem.size() + transform = AppKit.NSAffineTransform.alloc().init() + transform.scaleXBy_yBy_(scale_factor_x, scale_factor_y) -
[docs] def show_in_preview(self): - """Opens the image in preview. + scaled_image.lockFocus() + transform.concat() + self.xa_elem.drawInRect_fromRect_operation_fraction_(imageBounds, Quartz.CGRectZero, AppKit.NSCompositingOperationCopy, 1.0) + scaled_image.unlockFocus() + self.xa_elem = scaled_image + self.modified = True + return self
- .. versionadded:: 0.0.8 - """ - tmp_file = tempfile.NamedTemporaryFile(suffix = self.name) - with open(tmp_file.name, 'wb') as f: - f.write(self.xa_elem.TIFFRepresentation()) +
[docs] def pad(self, horizontal_border_width: int = 50, vertical_border_width: int = 50, pad_color: Union[XAColor, None] = None) -> 'XAImage': + """Pads the image with the specified color; adds a border around the image with the specified vertical and horizontal width. + + :param horizontal_border_width: The border width, in pixels, in the x-dimension, defaults to 50 + :type horizontal_border_width: int + :param vertical_border_width: The border width, in pixels, in the y-dimension, defaults to 50 + :type vertical_border_width: int + :param pad_color: The color of the border, or None for a white border, defaults to None + :type pad_color: Union[XAColor, None] + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if pad_color is None: + # No color provided -- use white by default + pad_color = XAColor.white() + + new_width = self.size[0] + horizontal_border_width * 2 + new_height = self.size[1] + vertical_border_width * 2 + color_swatch = pad_color.make_swatch(new_width, new_height) + + color_swatch.xa_elem.lockFocus() + bounds = AppKit.NSMakeRect(horizontal_border_width, vertical_border_width, self.size[0], self.size[1]) + self.xa_elem.drawInRect_(bounds) + color_swatch.xa_elem.unlockFocus() + self.xa_elem = color_swatch.xa_elem + self.modified = True + return self
- AppKit.NSWorkspace.sharedWorkspace().openFile_withApplication_(tmp_file.name, "Preview") - time.sleep(0.1)
+
[docs] def overlay_image(self, image: 'XAImage', location: Union[tuple[int, int], None] = None, size: Union[tuple[int, int], None] = None) -> 'XAImage': + """Overlays an image on top of this image, at the specified location, with the specified size. -
[docs] def get_clipboard_representation(self) -> AppKit.NSImage: - return self.xa_elem
+ :param image: The image to overlay on top of this image + :type image: XAImage + :param location: The bottom-left point of the overlaid image in the result, or None to use the bottom-left point of the background image, defaults to None + :type location: Union[tuple[int, int], None] + :param size: The width and height of the overlaid image, or None to use the overlaid's images existing width and height, or (-1, -1) to use the dimensions of the background image, defaults to None + :type size: Union[tuple[int, int], None] + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use the bottom-left point of the background image by default + location = (0, 0) + + if size is None: + # No dimensions provided -- use size of overlay image by default + size = image.size + elif size == (-1, -1): + # Use remaining width/height of background image + size = (self.size[0] - location[0], self.size[1] - location[1]) + elif size[0] == -1: + # Use remaining width of background image + provided height + size = (self.size[0] - location[0], size[1]) + elif size[1] == -1: + # Use remaining height of background image + provided width + size = (size[1], self.size[1] - location[1]) + + self.xa_elem.lockFocus() + bounds = AppKit.NSMakeRect(location[0], location[1], size[0], size[1]) + image.xa_elem.drawInRect_(bounds) + self.xa_elem.unlockFocus() + self.modified = True + return self.xa_elem
+ +
[docs] def overlay_text(self, text: str, location: Union[tuple[int, int], None] = None, font_size: float = 12, font_color: Union[XAColor, None] = None) -> 'XAImage': + """Overlays text of the specified size and color at the provided location within the image. + + :param text: The text to overlay onto the image + :type text: str + :param location: The bottom-left point of the start of the text, or None to use (5, 5), defaults to None + :type location: Union[tuple[int, int], None] + :param font_size: The font size, in pixels, of the text, defaults to 12 + :type font_size: float + :param font_color: The color of the text, or None to use black, defaults to None + :type font_color: XAColor + :return: The image object, modifications included + :rtype: XAImage + + .. versionadded:: 0.1.0 + """ + if location is None: + # No location provided -- use (5, 5) by default + location = (5, 5) + + if font_color is None: + # No color provided -- use black by default + font_color = XAColor.black() + + font = AppKit.NSFont.userFontOfSize_(font_size) + textRect = Quartz.CGRectMake(location[0], 0, self.size[0] - location[0], location[1]) + attributes = { + AppKit.NSFontAttributeName: font, + AppKit.NSForegroundColorAttributeName: font_color.xa_elem + } + self.xa_elem.lockFocus() + AppKit.NSString.alloc().initWithString_(text).drawInRect_withAttributes_(textRect, attributes) + self.xa_elem.unlockFocus() + self.modified = True + return self
+
[docs] def extract_text(self) -> list[str]: + """Extracts and returns all visible text in the image. + :return: The array of extracted text strings + :rtype: list[str] -# TODO: Init NSLocation object -
[docs]class XALocation(XAObject): - """A location with a latitude and longitude, along with other data. + :Example: - .. versionadded:: 0.0.2 - """ - def __init__(self, raw_value: CoreLocation.CLLocation = None, title: str = None, latitude: float = 0, longitude: float = 0, altitude: float = None, radius: int = 0, address: str = None): - self.raw_value = raw_value #: The raw CLLocation object - self.title = title #: The name of the location - self.latitude = latitude #: The latitude of the location - self.longitude = longitude #: The longitude of the location - self.altitude = altitude #: The altitude of the location - self.radius = radius #: The horizontal accuracy of the location measurement - self.address = address #: The addres of the location - self.current_location #: The current location of the device + >>> import PyXA + >>> test = PyXA.XAImage("/Users/ExampleUser/Downloads/Example.jpg") + >>> print(test.extract_text()) + ["HERE'S TO THE", 'CRAZY ONES', 'the MISFITS the REBELS', 'THE TROUBLEMAKERS', ...] + + .. versionadded:: 0.1.0 + """ + # Prepare CGImage + ci_image = Quartz.CIImage.imageWithData_(self.data) + context = Quartz.CIContext.alloc().initWithOptions_(None) + img = context.createCGImage_fromRect_(ci_image, ci_image.extent()) + + # Handle request completion + extracted_strings = [] + def recognize_text_handler(request, error): + observations = request.results() + for observation in observations: + recognized_strings = observation.topCandidates_(1)[0].string() + extracted_strings.append(recognized_strings) + + # Perform request and return extracted text + request = Vision.VNRecognizeTextRequest.alloc().initWithCompletionHandler_(recognize_text_handler) + request_handler = Vision.VNImageRequestHandler.alloc().initWithCGImage_options_(img, None) + request_handler.performRequests_error_([request], None) + return extracted_strings
- @property - def raw_value(self) -> CoreLocation.CLLocation: - return self.__raw_value +
[docs] def show_in_preview(self): + """Opens the image in preview. - @raw_value.setter - def raw_value(self, raw_value: CoreLocation.CLLocation): - self.__raw_value = raw_value - if raw_value is not None: - self.latitude = raw_value.coordinate()[0] - self.longitude = raw_value.coordinate()[1] - self.altitude = raw_value.altitude() - self.radius = raw_value.horizontalAccuracy() + .. versionadded:: 0.0.8 + """ + if not self.modified and self.file is not None and isinstance(self.file, XAPath): + AppKit.NSWorkspace.sharedWorkspace().openFile_withApplication_(self.file.path, "Preview") + else: + tmp_file = tempfile.NamedTemporaryFile() + with open(tmp_file.name, 'wb') as f: + f.write(self.xa_elem.TIFFRepresentation()) - @property - def current_location(self) -> 'XALocation': - self.raw_value = None - self._spawn_thread(self.__get_current_location) - while self.raw_value is None: - time.sleep(0.01) - return self + img_url = XAPath(tmp_file.name).xa_elem + preview_url = XAPath("/System/Applications/Preview.app/").xa_elem + AppKit.NSWorkspace.sharedWorkspace().openURLs_withApplicationAtURL_configuration_completionHandler_([img_url], preview_url, None, None) + time.sleep(1)
-
[docs] def show_in_maps(self): - """Shows the location in Maps.app. +
[docs] def save(self, file_path: Union[XAPath, str, None] = None): + """Saves the image to a file on the disk. Saves to the original file (if there was one) by default. - .. versionadded:: 0.0.6 + :param file_path: The path at which to save the image file. Any existing file at that location will be overwritten, defaults to None + :type file_path: Union[XAPath, str, None] + + .. versionadded:: 0.1.0 """ - XAURL(f"maps://?q={self.title},ll={self.latitude},{self.longitude}").open()
+ if file_path is None and self.file is not None: + file_path = self.file.path + elif isinstance(file_path, XAPath): + file_path = file_path.path + fm = AppKit.NSFileManager.defaultManager() + fm.createFileAtPath_contents_attributes_(file_path, self.xa_elem.TIFFRepresentation(), None)
- def __get_current_location(self): - location_manager = CoreLocation.CLLocationManager.alloc().init() - old_self = self - class CLLocationManagerDelegate(AppKit.NSObject): - def locationManager_didUpdateLocations_(self, manager, locations): - if locations is not None: - old_self.raw_value = locations[0] - AppHelper.stopEventLoop() +
[docs] def get_clipboard_representation(self) -> AppKit.NSImage: + """Gets a clipboard-codable representation of the iimage. - def locationManager_didFailWithError_(self, manager, error): - print(manager, error) + When the clipboard content is set to an image, the image itself, including any modifications, is added to the clipboard. Pasting will then insert the image into the active document. - location_manager.requestWhenInUseAuthorization() - location_manager.setDelegate_(CLLocationManagerDelegate.alloc().init().retain()) - location_manager.requestLocation() + :return: The raw NSImage object for this XAIMage + :rtype: AppKit.NSImage - AppHelper.runConsoleEventLoop() + .. versionadded:: 0.1.0 + """ + return self.xa_elem
- def __repr__(self): - return "<" + str(type(self)) + str((self.latitude, self.longitude)) + ">"
- - + def __eq__(self, other): + return self.xa_elem.TIFFRepresentation() == other.xa_elem.TIFFRepresentation()
-
[docs]class XAAlertStyle(Enum): - """Options for which alert style an alert should display with. - """ - INFORMATIONAL = AppKit.NSAlertStyleInformational - WARNING = AppKit.NSAlertStyleWarning - CRITICAL = AppKit.NSAlertStyleCritical
-
[docs]class XAAlert(XAObject): - """A class for creating and interacting with an alert dialog window. - .. versionadded:: 0.0.5 +
[docs]class XASoundList(XAList, XAClipboardCodable): + """A wrapper around lists of sounds that employs fast enumeration techniques. + + .. versionadded:: 0.1.0 """ - def __init__(self, title: str = "Alert!", message: str = "", style: XAAlertStyle = XAAlertStyle.INFORMATIONAL, buttons = ["Ok", "Cancel"]): - super().__init__() - self.title: str = title - self.message: str = message - self.style: XAAlertStyle = style - self.buttons: List[str] = buttons + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XASound, filter) -
[docs] def display(self) -> int: - """Displays the alert. +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSSound, AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of each sound in the list. - :return: A number representing the button that the user selected, if any - :rtype: int + When the clipboard content is set to a list of sounds, each sound's raw sound data, its associated file URL, and its file path string are added to the clipboard. - .. versionadded:: 0.0.5 - """ - alert = AppKit.NSAlert.alloc().init() - alert.setMessageText_(self.title) - alert.setInformativeText_(self.message) - alert.setAlertStyle_(self.style.value) + :return: The clipboard-codable form of the sound + :rtype: Any - for button in self.buttons: - alert.addButtonWithTitle_(button) - return alert.runModal()
+ .. versionadded:: 0.1.0 + """ + return [self.xa_elem, self.file.xa_elem, self.file.xa_elem.path()]
+
[docs]class XASound(XAObject, XAClipboardCodable): + """A class for playing and interacting with audio files and data. + .. versionadded:: 0.0.1 + """ + def __init__(self, sound_reference: Union[str, XAURL, XAPath]): + self.file = None + match sound_reference: + case str() as ref if "://" in ref: + logging.debug(f"Sound ref is web/file URL --> Set file to URL") + self.file = XAURL(ref) -
[docs]class XAColorPickerStyle(Enum): - """Options for which tab a color picker should display when first opened. - """ - GRAYSCALE = AppKit.NSColorPanelModeGray - RGB_SLIDERS = AppKit.NSColorPanelModeRGB - CMYK_SLIDERS = AppKit.NSColorPanelModeCMYK - HSB_SLIDERS = AppKit.NSColorPanelModeHSB - COLOR_LIST = AppKit.NSColorPanelModeColorList - COLOR_WHEEL = AppKit.NSColorPanelModeWheel - CRAYONS = AppKit.NSColorPanelModeCrayon - IMAGE_PALETTE = AppKit.NSColorPanelModeCustomPalette
+ case str() as ref if os.path.exists(ref): + logging.debug(f"Sound ref is file path --> Set file to path") + self.file = XAPath(sound_reference) -
[docs]class XAColorPicker(XAObject): - """A class for creating and interacting with a color picker window. + case str() as ref: + logging.debug(f"Sound ref is raw string --> Set file to path of sound with ref name") + self.file = XAPath("/System/Library/Sounds/" + ref + ".aiff") - .. versionadded:: 0.0.5 - """ - def __init__(self, style: XAColorPickerStyle = XAColorPickerStyle.GRAYSCALE): - super().__init__() - self.style = style + case {"element": str() as ref}: + logging.debug(f"Sound ref is string from XASoundList --> Reinit with string") + self.file = XASound(ref).file -
[docs] def display(self) -> XAColor: - """Displays the color picker. + case XAPath() as ref: + logging.debug(f"Sound ref is path object --> Set file to path") + self.file = ref - :return: The color that the user selected - :rtype: XAColor + case XAURL() as ref: + logging.debug(f"Sound ref is web/file URL object --> Set file to URL") + self.file = ref - .. versionadded:: 0.0.5 - """ - panel = AppKit.NSColorPanel.sharedColorPanel() - panel.setMode_(self.style.value) - panel.setShowsAlpha_(True) + case XASound() as sound: + logging.debug(f"Sound ref is another XASound object --> Set file to that sound's file") + self.file = sound.file - def run_modal(panel): - initial_color = panel.color() - time.sleep(0.5) - while panel.isVisible() and panel.color() == initial_color: - time.sleep(0.01) - AppKit.NSApp.stopModal() + self.duration: float #: The duration of the sound in seconds - modal_thread = threading.Thread(target=run_modal, args=(panel, ), name="Run Modal", daemon=True) - modal_thread.start() + self.__audio_file = AVFoundation.AVAudioFile.alloc().initForReading_error_(self.file.xa_elem if self.file is not None else None, None)[0] - AppKit.NSApp.runModalForWindow_(panel) - return XAColor(panel.color())
+ self.__audio_engine = AVFoundation.AVAudioEngine.alloc().init() + self.__player_node = AVFoundation.AVAudioPlayerNode.alloc().init() + self.__audio_engine.attachNode_(self.__player_node) + self.__audio_engine.connect_to_format_(self.__player_node, self.__audio_engine.mainMixerNode(), self.__audio_file.processingFormat()) + self.__player_node.stop() + self.__audio_engine.stop() + self.xa_elem = self.__audio_file -
[docs]class XADialog(XAObject): - """A custom dialog window. + @property + def num_sample_frames(self) -> int: + """The number of sample frames in the audio file. - .. versionadded:: 0.0.8 - """ - def __init__(self, text: str = "", title: Union[str, None] = None, buttons: Union[None, List[Union[str, int]]] = None, hidden_answer: bool = False, default_button: Union[str, int, None] = None, cancel_button: Union[str, int, None] = None, icon: Union[Literal["stop", "note", "caution"], None] = None, default_answer: Union[str, int, None] = None): - super().__init__() - self.text: str = text - self.title: str = title - self.buttons: Union[None, List[Union[str, int]]] = buttons or [] - self.hidden_answer: bool = hidden_answer - self.icon: Union[str, None] = icon - self.default_button: Union[str, int, None] = default_button - self.cancel_button: Union[str, int, None] = cancel_button - self.default_answer: Union[str, int, None] = default_answer + .. versionadded:: 0.1.0 + """ + return self.xa_elem.length() -
[docs] def display(self) -> Union[str, int, None, List[str]]: - """Displays the dialog, waits for the user to select an option or cancel, then returns the selected button or None if cancelled. + @property + def sample_rate(self) -> float: + """The sample rate for the sound format, in hertz. - :return: The selected button or None if no value was selected - :rtype: Union[str, int, None, List[str]] + .. versionadded:: 0.1.0 + """ + return self.xa_elem.processingFormat().sampleRate() - .. versionadded:: 0.0.8 + @property + def duration(self) -> float: + """The duration of the sound in seconds. + + .. versionadded:: 0.1.0 """ - buttons = [x.replace("'", "") for x in self.buttons] - buttons = str(buttons).replace("'", '"') + return self.num_sample_frames / self.sample_rate - default_button = str(self.default_button).replace("'", "") - default_button_str = "default button \"" + default_button + "\"" if self.default_button is not None else "" +
[docs] def open(*sound_references: Union[str, XAPath, list[Union[str, XAPath]]]) -> Union['XASound', XASoundList]: + """Initializes one or more sounds from files. - cancel_button = str(self.cancel_button).replace("'", "") - cancel_button_str = "cancel button \"" + cancel_button + "\"" if self.cancel_button is not None else "" + :param sound_references: The sound(s) to open + :type sound_references: Union[str, XAPath, list[Union[str, XAPath]]] + :return: The newly created sound object, or a list of sound objects + :rtype: Union[XASound, XASoundList] - icon_str = "with icon " + self.icon + "" if self.icon is not None else "" + .. versionadded:: 0.1.0 + """ + if len(sound_references) == 1: + sound_references = sound_references[0] - default_answer = str(self.default_answer).replace("'", '"') - default_answer_str = "default answer \"" + default_answer + "\"" if self.default_answer is not None else "" + if isinstance(sound_references, list) or isinstance(sound_references, tuple): + return XASoundList({"element": sound_references}) + else: + return XASound(sound_references)
- script = AppleScript(f""" - tell application "Terminal" - display dialog \"{self.text}\" with title \"{self.title}\" buttons {buttons} {default_button_str} {cancel_button_str} {icon_str} {default_answer_str} hidden answer {self.hidden_answer} - end tell - """) +
[docs] def beep(): + """Plays the system Beep sound. - result = script.run()["event"] - if result is not None: - if result.numberOfItems() > 1: - return [result.descriptorAtIndex_(1).stringValue(), result.descriptorAtIndex_(2).stringValue()] - else: - return result.descriptorAtIndex_(1).stringValue()
- + .. versionadded:: 0.1.0 + """ + AppleScript(""" + beep + delay 0.5 + """).run()
+
[docs] def play(self) -> Self: + """Plays the sound from the beginning. + Audio playback runs in a separate thread. For the sound the play properly, you must keep the main thread alive over the duration of the desired playback. -
[docs]class XAMenu(XAObject): - """A custom list item selection menu. + :return: A reference to this sound object. + :rtype: Self - .. versionadded:: 0.0.8 - """ - def __init__(self, menu_items: List[Any], title: str = "Select Item", prompt: str = "Select an item", default_items: Union[List[str], None] = None, ok_button_name: str = "Okay", cancel_button_name: str = "Cancel", multiple_selections_allowed: bool = False, empty_selection_allowed: bool = False): - super().__init__() - self.menu_items: List[Union[str, int]] = menu_items #: The items the user can choose from - self.title: str = title #: The title of the dialog window - self.prompt: str = prompt #: The prompt to display in the dialog box - self.default_items: List[str] = default_items or [] #: The items to initially select - self.ok_button_name: str = ok_button_name #: The name of the OK button - self.cancel_button_name: str = cancel_button_name #: The name of the Cancel button - self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether multiple items can be selected - self.empty_selection_allowed: bool = empty_selection_allowed #: Whether the user can click OK without selecting anything + :Example: -
[docs] def display(self) -> Union[str, int, bool, List[str], List[int]]: - """Displays the menu, waits for the user to select an option or cancel, then returns the selected value or False if cancelled. + >>> import PyXA + >>> import time + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.play() + >>> time.sleep(glass_sound.duration) - :return: The selected value or False if no value was selected - :rtype: Union[str, int, bool, List[str], List[int]] + .. seealso:: :func:`pause`, :func:`stop` - .. versionadded:: 0.0.8 + .. versionadded:: 0.0.1 """ - menu_items = [x.replace("'", "") for x in self.menu_items] - menu_items = str(menu_items).replace("'", '"') - default_items = str(self.default_items).replace("'", '"') - script = AppleScript(f""" - tell application "Terminal" - choose from list {menu_items} with title \"{self.title}\" with prompt \"{self.prompt}\" default items {default_items} OK button name \"{self.ok_button_name}\" cancel button name \"{self.cancel_button_name}\" multiple selections allowed {self.multiple_selections_allowed} empty selection allowed {self.empty_selection_allowed} - end tell - """) - result = script.run()["event"] - if result is not None: - if self.multiple_selections_allowed: - values = [] - for x in range(1, result.numberOfItems() + 1): - desc = result.descriptorAtIndex_(x) - values.append(desc.stringValue()) - return values - else: - if result.stringValue() == "false": - return False - return result.stringValue()
- + def play_sound(self): + logging.debug(f"Playing XASound audio in new thread") + self.__player_node.scheduleFile_atTime_completionHandler_(self.xa_elem, None, None) + self.__audio_engine.startAndReturnError_(None) + self.__player_node.play() + while self.__player_node.isPlaying(): + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.1)) + self._spawn_thread(play_sound, [self]) + return self
+
[docs] def pause(self) -> Self: + """Pauses the sound. -
[docs]class XAFilePicker(XAObject): - """A file selection window. + :return: A reference to this sound object. + :rtype: Self - .. versionadded:: 0.0.8 - """ - def __init__(self, prompt: str = "Choose File", types: List[str] = None, default_location: Union[str, None] = None, show_invisibles: bool = False, multiple_selections_allowed: bool = False, show_package_contents: bool = False): - super().__init__() - self.prompt: str = prompt #: The prompt to display in the dialog box - self.types: List[str] = types #: The file types/type identifiers to allow for selection - self.default_location: Union[str, None] = default_location #: The default file location - self.show_invisibles: bool = show_invisibles #: Whether invisible files and folders are shown - self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether the user can select multiple files - self.show_package_contents: bool = show_package_contents #: Whether to show the contents of packages + :Example: -
[docs] def display(self) -> Union[XAPath, None]: - """Displays the file chooser, waits for the user to select a file or cancel, then returns the selected file URL or None if cancelled. + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.pause() - :return: The selected file URL or None if no file was selected - :rtype: Union[XAPath, None] + .. seealso:: :func:`resume`, :func:`stop` - .. versionadded:: 0.0.8 + .. versionadded:: 0.0.1 """ - types = [x.replace("'", "") for x in self.types] - types = str(types).replace("'", '"') - types_str = "of type " + types if self.types is not None else "" + self.__player_node.pause() + return self
- default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" +
[docs] def resume(self) -> Self: + """Plays the sound starting from the time it was last paused at. - script = AppleScript(f""" - tell application "Terminal" - choose file with prompt \"{self.prompt}\" {types_str}{default_location_str} invisibles {self.show_invisibles} multiple selections allowed {self.multiple_selections_allowed} showing package contents {self.show_package_contents} - end tell - """) - result = script.run()["event"] + Audio playback runs in a separate thread. For the sound the play properly, you must keep the main thread alive over the duration of the desired playback. - if result is not None: - if self.multiple_selections_allowed: - values = [] - for x in range(1, result.numberOfItems() + 1): - desc = result.descriptorAtIndex_(x) - values.append(XAPath(desc.fileURLValue())) - return values - else: - return XAPath(result.fileURLValue())
+ :return: A reference to this sound object. + :rtype: Self + :Example: + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.resume() + .. seealso:: :func:`pause`, :func:`play` -
[docs]class XAFolderPicker(XAObject): - """A folder selection window. + .. versionadded:: 0.0.1 + """ + def play_sound(self): + self.__player_node.scheduleFile_atTime_completionHandler_(self.xa_elem, None, None) + self.__audio_engine.startAndReturnError_(None) + self.__player_node.play() + while self.__player_node.isPlaying(): + AppKit.NSRunLoop.currentRunLoop().runUntilDate_(datetime.now() + timedelta(seconds = 0.1)) - .. versionadded:: 0.0.8 - """ - def __init__(self, prompt: str = "Choose Folder", default_location: Union[str, None] = None, show_invisibles: bool = False, multiple_selections_allowed: bool = False, show_package_contents: bool = False): - super().__init__() - self.prompt: str = prompt #: The prompt to display in the dialog box - self.default_location: Union[str, None] = default_location #: The default folder location - self.show_invisibles: bool = show_invisibles #: Whether invisible files and folders are shown - self.multiple_selections_allowed: bool = multiple_selections_allowed #: Whether the user can select multiple folders - self.show_package_contents: bool = show_package_contents #: Whether to show the contents of packages + self._spawn_thread(play_sound, [self]) + return self
-
[docs] def display(self) -> Union[XAPath, None]: - """Displays the folder chooser, waits for the user to select a folder or cancel, then returns the selected folder URL or None if cancelled. +
[docs] def stop(self) -> 'XASound': + """Stops playback of the sound and rewinds it to the beginning. - :return: The selected folder URL or None if no folder was selected - :rtype: Union[XAPath, None] + :return: A reference to this sound object. + :rtype: XASound - .. versionadded:: 0.0.8 + :Example: + + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.stop() + + .. seealso:: :func:`pause`, :func:`play` + + .. versionadded:: 0.0.1 """ + self.__audio_engine.stop() + return self
- default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" +
[docs] def set_volume(self, volume: float) -> Self: + """Sets the volume of the sound. - script = AppleScript(f""" - tell application "Terminal" - choose folder with prompt \"{self.prompt}\" {default_location_str} invisibles {self.show_invisibles} multiple selections allowed {self.multiple_selections_allowed} showing package contents {self.show_package_contents} - end tell - """) - result = script.run()["event"] - if result is not None: - if self.multiple_selections_allowed: - values = [] - for x in range(1, result.numberOfItems() + 1): - desc = result.descriptorAtIndex_(x) - values.append(XAPath(desc.fileURLValue())) - return values - else: - return XAPath(result.fileURLValue())
+ :param volume: The desired volume of the sound in the range [0.0, 1.0]. + :type volume: int + :return: A reference to this sound object. + :rtype: Self + :Example: + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.set_volume(1.0) + .. seealso:: :func:`volume` -
[docs]class XAFileNameDialog(XAObject): - """A file name input window. + .. versionadded:: 0.0.1 + """ + self.__audio_engine.mainMixerNode().setOutputVolume_(volume) + return self
- .. versionadded:: 0.0.8 - """ - def __init__(self, prompt: str = "Specify file name and location", default_name: str = "New File", default_location: Union[str, None] = None): - super().__init__() - self.prompt: str = prompt #: The prompt to display in the dialog box - self.default_name: str = default_name #: The default name for the new file - self.default_location: Union[str, None] = default_location #: The default file location +
[docs] def volume(self) -> float: + """Returns the current volume of the sound. -
[docs] def display(self) -> Union[XAPath, None]: - """Displays the file name input window, waits for the user to input a name and location or cancel, then returns the specified file URL or None if cancelled. + :return: The volume level of the sound. + :rtype: int - :return: The specified file URL or None if no file name was inputted - :rtype: Union[XAPath, None] + :Example: - .. versionadded:: 0.0.8 + >>> import PyXA + >>> glass_sound = PyXA.sound("Glass") + >>> print(glass_sound.volume()) + 1.0 + + .. seealso:: :func:`set_volume` + + .. versionadded:: 0.0.1 """ + return self.__audio_engine.mainMixerNode().volume()
- default_location_str = "default location \"" + self.default_location + "\"" if self.default_location is not None else "" +
[docs] def loop(self, times: int) -> Self: + """Plays the sound the specified number of times. - script = AppleScript(f""" - tell application "Terminal" - choose file name with prompt \"{self.prompt}\" default name \"{self.default_name}\" {default_location_str} - end tell - """) - result = script.run()["event"] - if result is not None: - return XAPath(result.fileURLValue())
+ Audio playback runs in a separate thread. For the sound the play properly, you must keep the main thread alive over the duration of the desired playback. + :param times: The number of times to loop the sound. + :type times: int + :return: A reference to this sound object. + :rtype: Self + :Example: + >>> import PyXA + >>> import time + >>> glass_sound = PyXA.sound("Glass") + >>> glass_sound.loop(10) + >>> time.sleep(glass_sound.duration * 10) -
[docs]class XASpeech(XAObject): - def __init__(self, message: str = "", voice: Union[str, None] = None, volume: float = 0.5, rate: int = 200): - self.message: str = message #: The message to speak - self.voice: Union[str, None] = voice #: The voice that the message is spoken in - self.volume: float = volume #: The speaking volume - self.rate: int = rate #: The speaking rate + .. versionadded:: 0.0.1 + """ + def play_sound(): + num_plays = 0 + while num_plays < times: + sound = XASound(self.file) + sound.play() + num_plays += 1 + time.sleep(self.duration) -
[docs] def voices(self) -> List[str]: - """Gets the list of voice names available on the system. + self._spawn_thread(play_sound) + return self
- :return: The list of voice names - :rtype: List[str] +
[docs] def trim(self, start_time: float, end_time: float) -> Self: + """Trims the sound to the specified start and end time, in seconds. - :Example: + This will create a momentary sound data file in the current working directory for storing the intermediary trimmed sound data. - >>> import PyXA - >>> speaker = PyXA.XASpeech() - >>> print(speaker.voices()) - ['Agnes', 'Alex', 'Alice', 'Allison', + :param start_time: The start time in seconds + :type start_time: float + :param end_time: The end time in seconds + :type end_time: float + :return: The updated sound object + :rtype: Self - .. versionadded:: 0.0.9 + .. versionadded:: 0.1.0 """ - ls = AppKit.NSSpeechSynthesizer.availableVoices() - return [x.replace("com.apple.speech.synthesis.voice.", "").replace(".premium", "").title() for x in ls]
- -
[docs] def speak(self, path: Union[str, XAPath, None] = None): - """Speaks the provided message using the desired voice, volume, and speaking rate. + # Clear the temp data path + file_path = "sound_data_tmp.m4a" + if os.path.exists(file_path): + AppKit.NSFileManager.defaultManager().removeItemAtPath_error_(file_path, None) - :param path: The path to a .AIFF file to output sound to, defaults to None - :type path: Union[str, XAPath, None], optional + # Configure the export session + asset = AVFoundation.AVAsset.assetWithURL_(self.file.xa_elem) + export_session = AVFoundation.AVAssetExportSession.exportSessionWithAsset_presetName_(asset, AVFoundation.AVAssetExportPresetAppleM4A) - :Example 1: Speak a message aloud + start_time = CoreMedia.CMTimeMake(start_time * 100, 100) + end_time = CoreMedia.CMTimeMake(end_time * 100, 100) + time_range = CoreMedia.CMTimeRangeFromTimeToTime(start_time, end_time); - >>> import PyXA - >>> PyXA.XASpeech("This is a test").speak() + export_session.setTimeRange_(time_range) + export_session.setOutputURL_(XAPath(file_path).xa_elem) + export_session.setOutputFileType_(AVFoundation.AVFileTypeAppleM4A) - :Example 2: Output spoken message to an AIFF file + # Export to file path + waiting = False + def handler(): + nonlocal waiting + waiting = True - >>> import PyXA - >>> speaker = PyXA.XASpeech("Hello, world!") - >>> speaker.speak("/Users/steven/Downloads/Hello.AIFF") + export_session.exportAsynchronouslyWithCompletionHandler_(handler) - :Example 3: Control the voice, volume, and speaking rate + while not waiting: + time.sleep(0.01) - >>> import PyXA - >>> speaker = PyXA.XASpeech( - >>> message = "Hello, world!", - >>> voice = "Alex", - >>> volume = 1, - >>> rate = 500 - >>> ) - >>> speaker.speak() + # Load the sound file back into active memory + self.__audio_file = AVFoundation.AVAudioFile.alloc().initForReading_error_(XAPath(file_path).xa_elem, None)[0] + self.xa_elem = self.__audio_file + AppKit.NSFileManager.defaultManager().removeItemAtPath_error_(file_path, None) + return self
- .. versionadded:: 0.0.9 - """ - # Get the selected voice by name - voice = None - for v in AppKit.NSSpeechSynthesizer.availableVoices(): - if self.voice.lower() in v.lower(): - voice = v +
[docs] def save(self, file_path: Union[XAPath, str]): + """Saves the sound to the specified file path. - # Set up speech synthesis object - synthesizer = AppKit.NSSpeechSynthesizer.alloc().initWithVoice_(voice) - synthesizer.setVolume_(self.volume) - synthesizer.setRate_(self.rate) + :param file_path: The path to save the sound to + :type file_path: Union[XAPath, str] - # Start speaking - if path is None: - synthesizer.startSpeakingString_(self.message) - else: - if isinstance(path, str): - path = XAPath(path) - synthesizer.startSpeakingString_toURL_(self.message, path.xa_elem) + .. versionadded:: 0.1.0 + """ + if isinstance(file_path, str): + file_path = XAPath(file_path) - # Wait for speech to complete - while synthesizer.isSpeaking(): - time.sleep(0.01)
+ # Configure the export session + asset = AVFoundation.AVAsset.assetWithURL_(self.file.xa_elem) + export_session = AVFoundation.AVAssetExportSession.exportSessionWithAsset_presetName_(asset, AVFoundation.AVAssetExportPresetAppleM4A) + start_time = CoreMedia.CMTimeMake(0, 100) + end_time = CoreMedia.CMTimeMake(self.duration * 100, 100) + time_range = CoreMedia.CMTimeRangeFromTimeToTime(start_time, end_time); + export_session.setTimeRange_(time_range) + export_session.setOutputURL_(file_path.xa_elem) + # export_session.setOutputFileType_(AVFoundation.AVFileTypeAppleM4A) + # Export to file path + waiting = False + def handler(): + nonlocal waiting + waiting = True -
[docs]class XAMenuBar(XAObject): - def __init__(self): - """Creates a new menu bar object for interacting with the system menu bar. + export_session.exportAsynchronouslyWithCompletionHandler_(handler) + + while not waiting: + time.sleep(0.01)
- .. versionadded:: 0.0.9 - """ - self._menus = {} - self._menu_items = {} - self._methods = {} +
[docs] def get_clipboard_representation(self) -> list[Union[AppKit.NSSound, AppKit.NSURL, str]]: + """Gets a clipboard-codable representation of the sound. - detector = self - class MyApplicationAppDelegate(AppKit.NSObject): - start_time = datetime.now() + When the clipboard content is set to a sound, the raw sound data, the associated file URL, and the path string of the file are added to the clipboard. - def applicationDidFinishLaunching_(self, sender): - for item_name, status_item in detector._menus.items(): - menu = status_item.menu() + :return: The clipboard-codable form of the sound + :rtype: Any - menuitem = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_('Quit', 'terminate:', '') - menu.addItem_(menuitem) + .. versionadded:: 0.0.8 + """ + return [self.xa_elem, self.file.xa_elem, self.file.xa_elem.path()]
- def action_(self, menu_item): - selection = menu_item.title() - for item_name in detector._methods: - if selection == item_name: - detector._methods[item_name]() - app = AppKit.NSApplication.sharedApplication() - app.setDelegate_(MyApplicationAppDelegate.alloc().init().retain()) -
[docs] def add_menu(self, title: str, image: Union[XAImage, None] = None, tool_tip: Union[str, None] = None, img_width: int = 30, img_height: int = 30): - """Adds a new menu to be displayed in the system menu bar. +
[docs]class XAVideo(XAObject): + """A class for interacting with video files and data. - :param title: The name of the menu - :type title: str - :param image: The image to display for the menu, defaults to None - :type image: Union[XAImage, None], optional - :param tool_tip: The tooltip to display on hovering over the menu, defaults to None - :type tool_tip: Union[str, None], optional - :param img_width: The width of the image, in pixels, defaults to 30 - :type img_width: int, optional - :param img_height: The height of the image, in pixels, defaults to 30 - :type img_height: int, optional + .. versionadded:: 0.1.0 + """ + def __init__(self, video_reference: Union[str, XAURL, XAPath]): + if isinstance(video_reference, str): + # References is to some kind of path or URL + if "://" in video_reference: + video_reference = XAURL(video_reference) + else: + video_reference = XAPath(video_reference) - :Example: + self.xa_elem = AVFoundation.AVURLAsset.alloc().initWithURL_options_(video_reference.xa_elem, { AVFoundation.AVURLAssetPreferPreciseDurationAndTimingKey: objc.YES }) - >>> import PyXA - >>> menu_bar = PyXA.XAMenuBar() - >>> img = PyXA.XAImage("/Users/steven/Downloads/Blackness.jpg") - >>> menu_bar.add_menu("Menu 1", image=img, img_width=100, img_height=100) - >>> menu_bar.display() +
[docs] def reverse(self, output_file: Union[XAPath, str]): + """Reverses the video and exports the result to the specified output file path. - .. versionadded:: 0.0.9 + :param output_file: The file to export the reversed video to + :type output_file: Union[XAPath, str] + + .. versionadded:: 0.1.0 """ - status_bar = AppKit.NSStatusBar.systemStatusBar() - status_item = status_bar.statusItemWithLength_(AppKit.NSVariableStatusItemLength).retain() - status_item.setTitle_(title) - - if isinstance(image, XAImage): - img = image.xa_elem.copy() - img.setScalesWhenResized_(True) - img.setSize_((img_width, img_height)) - status_item.button().setImage_(img) + if isinstance(output_file, str): + output_file = XAPath(output_file) + output_url = output_file.xa_elem - status_item.setHighlightMode_(objc.YES) + reader = AVFoundation.AVAssetReader.alloc().initWithAsset_error_(self.xa_elem, None)[0] - if isinstance(tool_tip, str): - status_item.setToolTip_(tool_tip) + video_track = self.xa_elem.tracksWithMediaType_(AVFoundation.AVMediaTypeVideo)[-1] + + reader_output = AVFoundation.AVAssetReaderTrackOutput.alloc().initWithTrack_outputSettings_(video_track, { Quartz.CoreVideo.kCVPixelBufferPixelFormatTypeKey: Quartz.CoreVideo.kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange }) - menu = AppKit.NSMenu.alloc().init() - status_item.setMenu_(menu) + reader.addOutput_(reader_output) + reader.startReading() - status_item.setEnabled_(objc.YES) - self._menus[title] = status_item
+ samples = [] + while sample := reader_output.copyNextSampleBuffer(): + samples.append(sample) -
[docs] def add_item(self, menu: str, item_name: str, method: Union[Callable[[], None], None] = None, image: Union[XAImage, None] = None, img_width: int = 20, img_height: int = 20): - """Adds an item to a menu, creating the menu if necessary. + writer = AVFoundation.AVAssetWriter.alloc().initWithURL_fileType_error_(output_url, AVFoundation.AVFileTypeMPEG4, None)[0] - :param menu: The name of the menu to add an item to, or the name of the menu to create - :type menu: str - :param item_name: The name of the item - :type item_name: str - :param method: The method to associate with the item (the method called when the item is clicked) - :type method: Callable[[], None] - :param image: The image for the item, defaults to None - :type image: Union[XAImage, None], optional - :param img_width: The width of image, in pixels, defaults to 30 - :type img_width: int, optional - :param img_height: The height of the image, in pixels, defaults to 30 - :type img_height: int, optional + writer_settings = { + AVFoundation.AVVideoCodecKey: AVFoundation.AVVideoCodecTypeH264, + AVFoundation.AVVideoWidthKey: video_track.naturalSize().width, + AVFoundation.AVVideoHeightKey: video_track.naturalSize().height, + AVFoundation.AVVideoCompressionPropertiesKey: { AVFoundation.AVVideoAverageBitRateKey: video_track.estimatedDataRate() } + } - :Example: + format_hint = video_track.formatDescriptions()[-1] + writer_input = AVFoundation.AVAssetWriterInput.alloc().initWithMediaType_outputSettings_sourceFormatHint_(AVFoundation.AVMediaTypeVideo, writer_settings, format_hint) - >>> import PyXA - >>> menu_bar = PyXA.XAMenuBar() - >>> - >>> menu_bar.add_menu("Menu 1") - >>> menu_bar.add_item(menu="Menu 1", item_name="Item 1", method=lambda : print("Action 1")) - >>> menu_bar.add_item(menu="Menu 1", item_name="Item 2", method=lambda : print("Action 2")) - >>> - >>> menu_bar.add_item(menu="Menu 2", item_name="Item 1", method=lambda : print("Action 1")) - >>> img = PyXA.XAImage("/Users/exampleUser/Downloads/example.jpg") - >>> menu_bar.add_item("Menu 2", "Item 1", lambda : print("Action 1"), image=img, img_width=100) - >>> menu_bar.display() + writer_input.setExpectsMediaDataInRealTime_(False) - .. versionadded:: 0.0.9 - """ - item = AppKit.NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(item_name, 'action:', '') - - if isinstance(image, XAImage): - img = image.xa_elem.copy() - img.setScalesWhenResized_(True) - img.setSize_((img_width, img_height)) - item.setImage_(img) - - if menu not in self._menus: - self.add_menu(menu) - self._menu_items[item_name] = item - self._menus[menu].menu().addItem_(item) - self._methods[item_name] = method
+ pixel_buffer_adaptor = AVFoundation.AVAssetWriterInputPixelBufferAdaptor.alloc().initWithAssetWriterInput_sourcePixelBufferAttributes_(writer_input, None) + writer.addInput_(writer_input) + writer.startWriting() + writer.startSessionAtSourceTime_(CoreMedia.CMSampleBufferGetPresentationTimeStamp(samples[0])) -
[docs] def set_image(self, item_name: str, image: XAImage, img_width: int = 30, img_height: int = 30): - """Sets the image displayed for a menu or menu item. + for index, sample in enumerate(samples): + presentation_time = CoreMedia.CMSampleBufferGetPresentationTimeStamp(sample) - :param item_name: The name of the item to update - :type item_name: str - :param image: The image to display - :type image: XAImage - :param img_width: The width of the image, in pixels, defaults to 30 - :type img_width: int, optional - :param img_height: The height of the image, in pixels, defaults to 30 - :type img_height: int, optional + image_buffer_ref = CoreMedia.CMSampleBufferGetImageBuffer(samples[len(samples) - index - 1]) + if image_buffer_ref is not None: + pixel_buffer_adaptor.appendPixelBuffer_withPresentationTime_(image_buffer_ref, presentation_time) - :Example: Set Image on State Change + while not writer_input.isReadyForMoreMediaData(): + time.sleep(0.1) - >>> import PyXA - >>> current_state = True # On - >>> img_on = PyXA.XAImage("/Users/exampleUser/Documents/on.jpg") - >>> img_off = PyXA.XAImage("/Users/exampleUser/Documents/off.jpg") - >>> menu_bar = PyXA.XAMenuBar() - >>> menu_bar.add_menu("Status", image=img_on) - >>> - >>> def update_state(): - >>> global current_state - >>> if current_state is True: - >>> # ... (Actions for turning off) - >>> menu_bar.set_text("Turn off", "Turn on") - >>> menu_bar.set_image("Status", img_off) - >>> current_state = False - >>> else: - >>> # ... (Actions for turning on) - >>> menu_bar.set_text("Turn off", "Turn off") - >>> menu_bar.set_image("Status", img_on) - >>> current_state = True + self._spawn_thread(writer.finishWriting) + return AVFoundation.AVAsset.assetWithURL_(output_url)
- menu_bar.add_item("Status", "Turn off", update_state) - menu_bar.display() +
[docs] def show_in_quicktime(self): + """Shows the video in QuickTime Player. - .. versionadded:: 0.0.9 - """ - img = image.xa_elem.copy() - img.setScalesWhenResized_(True) - img.setSize_((img_width, img_height)) - if item_name in self._menus: - self._menus[item_name].button().setImage_(img) - elif item_name in self._methods: - self._menu_items[item_name].setImage_(img)
+ This will create a momentary video data file in the current working directory to store intermediary video data. -
[docs] def set_text(self, item_name: str, text: str): - """Sets the text displayed for a menu or menu item. + .. versionadded:: 0.1.0 + """ + self.save("video-data-tmp.mp4") - :param item_name: The name of the item to update - :type item_name: str - :param text: The new text to display - :type text: str + video_url = XAPath(os.getcwd() + "/video-data-tmp.mp4").xa_elem + quicktime_url = XAPath("/System/Applications/QuickTime Player.app").xa_elem + AppKit.NSWorkspace.sharedWorkspace().openURLs_withApplicationAtURL_configuration_completionHandler_([video_url], quicktime_url, None, None) + time.sleep(1) - :Example: Random Emoji Ticker + AppKit.NSFileManager.defaultManager().removeItemAtPath_error_(video_url.path(), None)
- >>> import PyXA - >>> import random - >>> import threading - >>> - >>> menu_bar = PyXA.XAMenuBar() - >>> menu_bar.add_menu("Emoji") - >>> - >>> emojis = ["😀", "😍", "🙂", "😎", "🤩", "🤯", "😭", "😱", "😴", "🤒", "😈", "🤠"] - >>> - >>> def update_display(): - >>> while True: - >>> new_emoji = random.choice(emojis) - >>> menu_bar.set_text("Emoji", new_emoji) - >>> sleep(0.25) - >>> - >>> emoji_ticker = threading.Thread(target=update_display) - >>> emoji_ticker.start() - >>> menu_bar.display() +
[docs] def save(self, file_path: Union[XAPath, str]): + """Saves the video at the specified file path. - .. versionadded:: 0.0.9 + :param file_path: The path to save the video at + :type file_path: Union[XAPath, str] + + .. versionadded:: 0.1.0 """ - if item_name in self._menus: - self._menus[item_name].setTitle_(text) - elif item_name in self._methods: - self._menu_items[item_name].setTitle_(text) - self._methods[text] = self._methods[item_name]
+ if isinstance(file_path, str): + file_path = XAPath(file_path) -
[docs] def display(self): - """Displays the custom menus on the menu bar. + # Configure the export session + export_session = AVFoundation.AVAssetExportSession.exportSessionWithAsset_presetName_(self.xa_elem, AVFoundation.AVAssetExportPresetHighestQuality) - :Example: + start_time = CoreMedia.CMTimeMake(0, 100) + end_time = CoreMedia.CMTimeMake(self.xa_elem.duration().value * self.xa_elem.duration().timescale, 100) + time_range = CoreMedia.CMTimeRangeFromTimeToTime(start_time, end_time); - >>> import PyXA - >>> mbar = PyXA.XAMenuBar() - >>> mbar.add_menu("🔥") - >>> mbar.display() + export_session.setTimeRange_(time_range) + export_session.setOutputURL_(file_path.xa_elem) - .. versionadded:: 0.0.9 - """ - try: - AppHelper.runEventLoop(installInterrupt=True) - except Exception as e: - print(e)
+ # Export to file path + waiting = False + def handler(): + nonlocal waiting + waiting = True + + export_session.exportAsynchronouslyWithCompletionHandler_(handler) + + while not waiting: + time.sleep(0.01)
+
diff --git a/docs/_modules/PyXA/XABaseScriptable 2.html b/docs/_modules/PyXA/XABaseScriptable 2.html new file mode 100644 index 0000000..163b72a --- /dev/null +++ b/docs/_modules/PyXA/XABaseScriptable 2.html @@ -0,0 +1,362 @@ + + + + + + PyXA.XABaseScriptable — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.XABaseScriptable

+from enum import Enum
+from typing import List, Tuple, Union
+import threading
+import ScriptingBridge
+
+from PyXA import XABase
+
+import signal
+
+from .XAProtocols import XACloseable
+
+
[docs]class timeout: + def __init__(self, seconds=1, error_message='Timeout'): + self.seconds = seconds + self.error_message = error_message +
[docs] def handle_timeout(self, signum, frame): + raise TimeoutError(self.error_message)
+ def __enter__(self): + signal.signal(signal.SIGALRM, self.handle_timeout) + signal.alarm(self.seconds) + def __exit__(self, type, value, traceback): + signal.alarm(0)
+ +
[docs]class XASBObject(XABase.XAObject): + """A class for PyXA objects scriptable with AppleScript/JXA. + + .. seealso:: :class:`XABase.XAObject` + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def set_property(self, property_name, value): + parts = property_name.split("_") + titled_parts = [part.title() for part in parts[1:]] + property_name = parts[0] + "".join(titled_parts) + if self.xa_scel is not None: + self.xa_scel.setValue_forKey_(value, property_name) + else: + self.xa_elem.setValue_forKey_(value, property_name)
+ + + + +
[docs]class XASBPrintable(XABase.XAObject): + def __print_dialog(self, show_prompt: bool = True): + """Displays a print dialog.""" + try: + if self.xa_scel is not None: + self.xa_scel.printWithProperties_(None) + else: + self.xa_elem.printWithProperties_(None) + except: + try: + if self.xa_scel is not None: + self.xa_scel.print_withProperties_printDialog_(self.xa_scel, None, show_prompt) + else: + self.xa_elem.print_withProperties_printDialog_(self.xa_elem, None, show_prompt) + except: + if self.xa_scel is not None: + self.xa_scel.print_printDialog_withProperties_(self.xa_scel, show_prompt, None) + else: + self.xa_elem.print_printDialog_withProperties_(self.xa_elem, show_prompt, None) + +
[docs] def print(self, properties: dict = None, print_dialog = None) -> XABase.XAObject: + """Prints a document, window, or item. + + :return: A reference to the PyXA objects that called this method. + :rtype: XABase.XAObject + + .. versionchanged:: 0.0.2 + Printing now initialized from a separate thread to avoid delaying main thread + + .. versionadded:: 0.0.1 + """ + print_thread = threading.Thread(target=self.__print_dialog, name="Print", daemon=True) + print_thread.start() + return self
+ + + + +
[docs]class XASBApplication(XASBObject, XABase.XAApplication): + """An application class for scriptable applications. + + .. seealso:: :class:`XABase.XAApplication`, :class:`XABase.XAWindow` + """ +
[docs] class SaveOption(Enum): + """Options for whether to save documents when closing them. + """ + YES = XABase.OSType('yes ') #: Save the file + NO = XABase.OSType('no ') #: Do not save the file + ASK = XABase.OSType('ask ') #: Ask user whether to save the file (bring up dialog)
+ +
[docs] class PrintErrorHandling(Enum): + """Options for how to handle errors while printing. + """ + STANDARD = 'lwst' #: Standard PostScript error handling + DETAILED = 'lwdt' #: Print a detailed report of PostScript errors
+ + def __init__(self, properties): + super().__init__(properties) + self.xa_scel = ScriptingBridge.SBApplication.alloc().initWithBundleIdentifier_(self.xa_elem.bundleIdentifier()) + self.xa_wcls = XASBWindow + + self.front_window: XASBWindow #: The front window of the application + + @property + def front_window(self) -> 'XASBWindow': + return self._new_element(self.xa_scel.windows()[0], self.xa_wcls) + +
[docs] def windows(self, filter: dict = None) -> 'XASBWindowList': + try: + return self._new_element(self.xa_scel.windows(), XASBWindowList) + except AttributeError: + return self._new_element([], XASBWindowList)
+ + + + +
[docs]class XASBWindowList(XABase.XAList): + """A wrapper around a list of windows. + + .. versionadded:: 0.0.5 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, None, filter) + self.xa_ocls = self.xa_prnt.xa_wcls + +
[docs] def name(self) -> List[str]: + return list(self.xa_elem.arrayByApplyingSelector_("name") or [])
+ + # TODO + +
[docs] def collapse(self): + """Collapses all windows in the list. + + .. versionadded:: 0.0.5 + """ + for window in self: + window.collapse()
+ +
[docs] def get_clipboard_representation(self) -> str: + """Gets a clipboard-codable representation of each window in the list. + + When the clipboard content is set to a list of windows, the name of each window is added to the clipboard. + + :return: A list of window names + :rtype: str + + .. versionadded:: 0.0.8 + """ + return self.name()
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XASBWindow(XASBObject, XACloseable): + def __init__(self, properties): + super().__init__(properties) + self.name: str #: The title of the window + self.id: str #: The unique identifier for the window + self.index: int #: The index of the window, ordered front to back + self.bounds: Tuple[Tuple[int, int]] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window has a zoom button + self.zoomed: bool #: Whether the window is currently zoomed + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @property + def bounds(self) -> Tuple[Tuple[int, int]]: + return self.xa_elem.bounds() + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + +
[docs] def collapse(self) -> 'XABase.XAWindow': + """Collapses (minimizes) the window. + + :return: A reference to the now-collapsed window object. + :rtype: XABase.XAWindow + """ + try: + self.set_property("miniaturized", True) + except: + try: + self.set_property("minimized", True) + except: + self.set_property("collapsed", True) + return self
+ +
[docs] def uncollapse(self) -> 'XABase.XAWindow': + """Uncollapses (unminimizes/expands) the window. + + :return: A reference to the uncollapsed window object. + :rtype: XABase.XAWindow + """ + try: + self.set_property("miniaturized", False) + except: + try: + self.set_property("minimized", False) + except: + self.set_property("collapsed", False) + return self
+ +
[docs] def toggle_zoom(self) -> 'XABase.XAWindow': + """Uncollapses (unminimizes/expands) the window. + + :return: A reference to the uncollapsed window object. + :rtype: XABase.XAWindow + """ + self.zoomed = not self.zoomed + self.set_property("zoomed", self.zoomed) + return self
+ + # TODO: + # def fullscreen(self): + # print(dir(self.element)) + +
[docs] def get_clipboard_representation(self) -> str: + """Gets a clipboard-codable representation of the window. + + When the clipboard content is set to a window, the name of the window is added to the clipboard. + + :return: The name of the window + :rtype: str + + .. versionadded:: 0.0.8 + """ + return self.name
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/XABaseScriptable.html b/docs/_modules/PyXA/XABaseScriptable.html index ce4a387..ff21496 100644 --- a/docs/_modules/PyXA/XABaseScriptable.html +++ b/docs/_modules/PyXA/XABaseScriptable.html @@ -3,7 +3,7 @@ - PyXA.XABaseScriptable — PyXA 0.0.9 documentation + PyXA.XABaseScriptable — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -69,6 +71,7 @@

Source code for PyXA.XABaseScriptable

 from enum import Enum
+from pprint import pprint
 from typing import List, Tuple, Union
 import threading
 import ScriptingBridge
@@ -169,17 +172,22 @@ 

Source code for PyXA.XABaseScriptable

 
     def __init__(self, properties):
         super().__init__(properties)
-        self.xa_scel = ScriptingBridge.SBApplication.alloc().initWithBundleIdentifier_(self.xa_elem.bundleIdentifier())
+        self.xa_scel = ScriptingBridge.SBApplication.alloc().initWithURL_(self.xa_elem.bundleURL())
         self.xa_wcls = XASBWindow
 
-        self.front_window: XASBWindow #: The front window of the application
-
     @property
     def front_window(self) -> 'XASBWindow':
+        """The front window of the application.
+
+        .. versionadded:: 0.0.1
+        """
         return self._new_element(self.xa_scel.windows()[0], self.xa_wcls)
 
 
[docs] def windows(self, filter: dict = None) -> 'XASBWindowList': - return self._new_element(self.xa_scel.windows(), XASBWindowList)
+ try: + return self._new_element(self.xa_scel.windows(), XASBWindowList) + except AttributeError: + return self._new_element([], XASBWindowList)
diff --git a/docs/_modules/PyXA/XAErrors 2.html b/docs/_modules/PyXA/XAErrors 2.html new file mode 100644 index 0000000..318ef41 --- /dev/null +++ b/docs/_modules/PyXA/XAErrors 2.html @@ -0,0 +1,133 @@ + + + + + + PyXA.XAErrors — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.XAErrors

+
[docs]class ApplicationNotFoundError(Exception): + def __init__(self, name: str): + self.name = name + Exception.__init__(self, name) + + def __str__(self): + return f"Application {self.name} not found."
+ +
[docs]class AuthenticationError(Exception): + def __init__(self, message: str): + self.message = message + Exception.__init__(self, message) + + def __str__(self): + return f"Failed due to insufficient authorization. {self.message}"
+ +
[docs]class InvalidPredicateError(Exception): + def __init__(self, message: str): + self.message = message + Exception.__init__(self, message) + + def __str__(self): + return f"Could not construct valid predicate format. {self.message}"
+ +
[docs]class UnconstructableClassError(Exception): + def __init__(self, message: str): + self.message = message + Exception.__init__(self, message) + + def __str__(self): + return f"Could not create new element. {self.message}"
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/XAErrors.html b/docs/_modules/PyXA/XAErrors.html index 6668d37..318ef41 100644 --- a/docs/_modules/PyXA/XAErrors.html +++ b/docs/_modules/PyXA/XAErrors.html @@ -14,7 +14,9 @@ + + @@ -74,7 +76,15 @@

Source code for PyXA.XAErrors

 		Exception.__init__(self, name)
 	
 	def __str__(self):
-		return f'Application {self.name} not found.'
+ return f"Application {self.name} not found."
+ +
[docs]class AuthenticationError(Exception): + def __init__(self, message: str): + self.message = message + Exception.__init__(self, message) + + def __str__(self): + return f"Failed due to insufficient authorization. {self.message}"
[docs]class InvalidPredicateError(Exception): def __init__(self, message: str): @@ -83,6 +93,14 @@

Source code for PyXA.XAErrors

 
 	def __str__(self):
 		return f"Could not construct valid predicate format. {self.message}"
+ +
[docs]class UnconstructableClassError(Exception): + def __init__(self, message: str): + self.message = message + Exception.__init__(self, message) + + def __str__(self): + return f"Could not create new element. {self.message}"
diff --git a/docs/_modules/PyXA/apps/Automator 2.html b/docs/_modules/PyXA/apps/Automator 2.html new file mode 100644 index 0000000..56327de --- /dev/null +++ b/docs/_modules/PyXA/apps/Automator 2.html @@ -0,0 +1,1721 @@ + + + + + + PyXA.apps.Automator — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Automator

+""".. versionadded:: 0.0.4
+
+Control Automator using JXA-like syntax.
+"""
+
+from enum import Enum
+from typing import Any, List, Tuple, Union
+
+import AppKit
+
+from PyXA import XABase
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath
+
+
[docs]class XAAutomatorApplication(XABaseScriptable.XASBApplication, XACanOpenPath): + """A class for managing and interacting with Automator.app. + + .. seealso:: :class:`XAAutomatorWindow`, :class:`XAAutomatorDocument` + + .. versionadded:: 0.0.4 + """ +
[docs] class WarningLevel(Enum): + """Options for warning level in regard to likelihood of data loss. + """ + IRREVERSIBLE = XABase.OSType("irrv") + NONE = XABase.OSType('none') + REVERSIBLE = XABase.OSType('rvbl')
+ + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XAAutomatorWindow + + self.name: str #: The name of the application + self.frontmost: bool #: Whether Chromium is the active application + self.version: str #: The version of Chromium + + @property + def name(self) -> str: + return self.xa_scel.name() + + @property + def frontmost(self) -> bool: + return self.xa_scel.frontmost() + + @frontmost.setter + def frontmost(self, frontmost: bool): + self.set_property('frontmost', frontmost) + + @property + def version(self) -> str: + return self.xa_scel.version() + +
[docs] def open(self, path: Union[str, AppKit.NSURL]) -> 'XAAutomatorWorkflow': + """Opens the file at the given filepath. + + :param target: The path to a file or the URL to a website to open. + :type target: Union[str, AppKit.NSURL] + :return: A reference to the PyXA object that called this method. + :rtype: XAObject + + .. versionadded:: 0.0.1 + """ + if not isinstance(path, AppKit.NSURL): + path = XABase.XAPath(path) + self.xa_wksp.openURLs_withAppBundleIdentifier_options_additionalEventParamDescriptor_launchIdentifiers_([path.xa_elem], self.xa_elem.bundleIdentifier(), 0, None, None) + return self.workflows()[0]
+ +
[docs] def add(self, action: 'XAAutomatorAction', workflow: 'XAAutomatorWorkflow', index: int = -1) -> 'XAAutomatorApplication': + """Adds the specified action to a workflow at the specified index. + + :param action: The action to add + :type action: XAAutomatorAction + :param workflow: The workflow to add the action to + :type workflow: XAAutomatorWorkflow + :param index: The index at which to add the action, defaults to -1 + :type index: int, optional + :return: A reference to the application object + :rtype: XAAutomatorApplication + + .. versionadded:: 0.0.4 + """ + self.xa_scel.add_to_atIndex_(action.xa_elem, workflow.xa_elem, index) + return self
+ + +
[docs] def documents(self, filter: Union[dict, None] = None) -> 'XAAutomatorDocumentList': + """Returns a list of documents, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter documents by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of documents + :rtype: XAAutomatorDocumentList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_scel.documents(), XAAutomatorDocumentList, filter)
+ +
[docs] def automator_actions(self, filter: Union[dict, None] = None) -> 'XAAutomatorActionList': + """Returns a list of Automator actions, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter actions by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of actions + :rtype: XAAutomatorActionList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_scel.AutomatorActions(), XAAutomatorActionList, filter)
+ +
[docs] def variables(self, filter: Union[dict, None] = None) -> 'XAAutomatorVariableList': + """Returns a list of Automator variables, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter variables by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of variables + :rtype: XAAutomatorVariableList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_scel.variables(), XAAutomatorVariableList, filter)
+ +
[docs] def workflows(self, filter: Union[dict, None] = None) -> 'XAAutomatorWorkflowList': + """Returns a list of Automator workflows, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter workflows by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of workflows + :rtype: XAAutomatorWorkflowList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_scel.workflows(), XAAutomatorWorkflowList, filter)
+ +
[docs] def make(self, specifier: str, properties: dict): + """Creates a new element of the given specifier class without adding it to any list. + + Use :func:`XABase.XAList.push` to push the element onto a list. + + :param specifier: The classname of the object to create + :type specifier: str + :param properties: The properties to give the object + :type properties: dict + :return: A PyXA wrapped form of the object + :rtype: XABase.XAObject + + .. versionadded:: 0.0.4 + """ + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + + if specifier == "workflow": + if "path" not in properties and "name" in properties: + fm = AppKit.NSFileManager.defaultManager() + properties.update({"path": f"{fm.homeDirectoryForCurrentUser().path()}/Downloads/{properties.get('name')}.workflow"}) + elif not properties.get("path").endswith(".workflow"): + properties.update({"path": properties.get("path") + ".workflow"}) + + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + return self._new_element(obj, XAAutomatorWorkflow) + elif specifier == "variable": + return self._new_element(obj, XAAutomatorVariable) + elif specifier == "document": + return self._new_element(obj, XAAutomatorDocument) + elif specifier == "action": + return self._new_element(obj, XAAutomatorAction) + elif specifier == "requiredResource": + return self._new_element(obj, XAAutomatorRequiredResource) + elif specifier == "setting": + return self._new_element(obj, XAAutomatorSetting)
+ + + + +
[docs]class XAAutomatorWindow(XABaseScriptable.XASBWindow): + """A class for managing and interacting with Automator windows. + + .. seealso:: :class:`XAAutomatorApplication` + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties): + super().__init__(properties) + self.name: str #: The full title of the window + self.id: int #: The unique identifier for the window + self.index: int #: The index of the window in the front-to-back ordering + self.bounds: Tuple[int, int, int, int] #: The bounding rectangle of the window + self.floating: bool #: Whether the window float + self.modal: bool #: Whether the window is a modal window + self.closeable: bool #: Whether the window has a close button + self.miniaturizable: bool #: Whether the window can be minimized + self.miniaturized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window can be zoomed + self.zoomed: bool #: Whether the window is currently zoomed + self.titled: bool #: Whether the window has a title bar + self.document: XAAutomatorDocument #: The document currently displayed in the window + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property("name", name) + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property("index", index) + + @property + def bounds(self) -> Tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: Tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def floating(self) -> bool: + return self.xa_elem.floating() + + @property + def modal(self) -> bool: + return self.xa_elem.modal() + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def miniaturizable(self) -> bool: + return self.xa_elem.miniaturizable() + + @property + def miniaturized(self) -> bool: + return self.xa_elem.miniaturized() + + @miniaturized.setter + def miniaturized(self, miniaturized: bool): + self.set_property("miniaturized", miniaturized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property("visible", visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property("zoomed", zoomed) + + @property + def titled(self) -> bool: + return self.xa_elem.titled() + + @property + def document(self) -> 'XAAutomatorDocument': + return self._new_element(self.xa_elem.document(), XAAutomatorDocument)
+ + + + +
[docs]class XAAutomatorDocumentList(XABase.XAList): + """A wrapper around a list of Automator documents which utilizes fast enumeration techniques. + + All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAAutomatorDocument, filter) + +
[docs] def id(self) -> List[int]: + """Retrieves the ID of each document in the list. + + :return: The list of document IDs. + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def title(self) -> List[str]: + """Retrieves the title of each document in the list. + + :return: The list of document titles. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("title"))
+ +
[docs] def index(self) -> List[int]: + """Retrieves the index of each document in the list. + + :return: The list of document indexes. + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("index"))
+ +
[docs] def by_id(self, id: int) -> Union['XAAutomatorDocument', None]: + """Retrieves the document whose ID matches the given ID, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XAAutomatorDocument, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("id", id)
+ +
[docs] def by_title(self, title: str) -> Union['XAAutomatorDocument', None]: + """Retrieves the first document whose title matches the given title, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XAAutomatorDocument, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("title", title)
+ +
[docs] def by_index(self, index: int) -> Union['XAAutomatorDocument', None]: + """Retrieves the document whose index matches the given index, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XAAutomatorDocument, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("index", index)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAAutomatorDocument(XABase.XAObject): + """A class for managing and interacting with Automator windows. + + .. seealso:: :class:`XAAutomatorApplication` + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties): + super().__init__(properties) + self.modified: bool #: Whether the document has been modified since its last save + self.name: str #: The title of the document + self.path: str #: The path to the document on the disk + + @property + def modified(self) -> bool: + return self.xa_elem.modified() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property("name", name) + + @property + def path(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.path()) + + @path.setter + def path(self, path: Union[str, XABase.XAPath]): + if isinstance(path, str): + path = XABase.XAPath(path) + self.set_property("path", path.path) + + def __repr__(self): + return "<" + str(type(self)) + self.name + ">"
+ + + + +
[docs]class XAAutomatorActionList(XABase.XAList): + """A wrapper around a list of Automator required resources which utilizes fast enumeration techniques. + + All properties of required resources can be called as methods on the wrapped list, returning a list containing each resource's value for the property. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAAutomatorAction, filter) + +
[docs] def bundle_id(self) -> List[str]: + """Retrieves the bundle identifier of each action in the list. + + :return: The list of bundle identifiers. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("bundleId"))
+ +
[docs] def category(self) -> List[List[str]]: + """Retrieves the category/categories of each action in the list. + + :return: The list of categories. + :rtype: List[List[str]] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("category"))
+ +
[docs] def comment(self) -> List[str]: + """Retrieves the comments of each action in the list. + + :return: The list of comments. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("comment"))
+ +
[docs] def enabled(self) -> List[bool]: + """Retrieves the enabled status of each action in the list. + + :return: The list of enabled status booleans. + :rtype: List[bool] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("enabled"))
+ +
[docs] def execution_error_message(self) -> List[str]: + """Retrieves the execution error message of each action in the list. + + :return: The list of error messages. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("executionErrorMessage"))
+ +
[docs] def execution_error_number(self) -> List[int]: + """Retrieves the execution error number of each action in the list. + + :return: The list of error numbers. + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("executionErrorNumber"))
+ +
[docs] def execution_result(self) -> List[Any]: + """Retrieves the result value of each action in the list. + + :return: The list of results. + :rtype: List[Any] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("executionResult"))
+ +
[docs] def icon_name(self) -> List[str]: + """Retrieves the icon name of each action in the list. + + :return: The list of icon names. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("iconName"))
+ +
[docs] def ignores_input(self) -> List[bool]: + """Retrieves the ignore input status of each action in the list. + + :return: The list of ignore input status booleans. + :rtype: List[bool] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("ignoresInput"))
+ +
[docs] def index(self) -> List[int]: + """Retrieves the index of each action in the list. + + :return: The list of action indices. + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("index"))
+ +
[docs] def input_types(self) -> List[List[str]]: + """Retrieves the input types of each action in the list. + + :return: The list of input types. + :rtype: List[List[str]] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("inputTypes"))
+ +
[docs] def keywords(self) -> List[List[str]]: + """Retrieves the keywords of each action in the list. + + :return: The list of keywords. + :rtype: List[List[str]] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("keywords"))
+ +
[docs] def name(self) -> List[str]: + """Retrieves the name of each action in the list. + + :return: The list of names. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def output_types(self) -> List[List[str]]: + """Retrieves the output types of each action in the list. + + :return: The list of output types. + :rtype: List[List[str]] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("outputTypes"))
+ +
[docs] def parent_workflow(self) -> 'XAAutomatorWorkflowList': + """Retrieves the parent workflow of each action in the list. + + :return: The list of parent workflows. + :rtype: XAAutomatorWorkflowList + + .. versionadded:: 0.0.4 + """ + ls = self.xa_elem.arrayByApplyingSelector_("parentWorkflow") + return self._new_element(ls, XAAutomatorWorkflowList)
+ +
[docs] def path(self) -> List[str]: + """Retrieves the file path of each action in the list. + + :return: The list of paths. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("path"))
+ +
[docs] def show_action_when_run(self) -> List[bool]: + """Retrieves the status of the show action when run setting of each action in the list. + + :return: The list of boolean statuses. + :rtype: List[bool] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("showActionWhenRun"))
+ +
[docs] def target_application(self) -> List[List[str]]: + """Retrieves the target application name of each action in the list. + + :return: The list of target application names. + :rtype: List[List[str]] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("targetApplication"))
+ +
[docs] def version(self) -> List[str]: + """Retrieves the version of each action in the list. + + :return: The list of versions. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("version"))
+ +
[docs] def warning_action(self) -> List[str]: + """Retrieves the warning action of each action in the list. + + :return: The list of warning actions. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("warningAction"))
+ +
[docs] def warning_level(self) -> List[XAAutomatorApplication.WarningLevel]: + """Retrieves the warning level of each action in the list. + + :return: The list of warning levels. + :rtype: List[XAAutomatorApplication.WarningLevel] + + .. versionadded:: 0.0.4 + """ + ls = self.xa_elem.arrayByApplyingSelector_("warningLevel") + return [XAAutomatorApplication.WarningLevel(x) for x in ls]
+ +
[docs] def warning_message(self) -> List[str]: + """Retrieves the warning message of each action in the list. + + :return: The list of warning messages. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("warningMessage"))
+ +
[docs] def by_bundle_id(self, bundle_id: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose bundle identifier matches the given identifier, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("bundleId", bundle_id)
+ +
[docs] def by_category(self, category: List[str]) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose category matches the given category, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("category", category)
+ +
[docs] def by_comment(self, comment: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose comment matches the given comment, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("comment", comment)
+ +
[docs] def by_enabled(self, enabled: bool) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose enabled status matches the given boolean value, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("enabled", enabled)
+ +
[docs] def by_execution_error_message(self, execution_error_message: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose error message matches the given message, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("executionErrorMessage", execution_error_message)
+ +
[docs] def by_execution_error_number(self, execution_error_number: int) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose error number matches the given error number, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("executionErrorNumber", execution_error_number)
+ +
[docs] def by_execution_result(self, execution_result: Any) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose execution result matches the given value, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("executionResult", execution_result)
+ +
[docs] def by_icon_name(self, icon_name: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose icon name matches the given name, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("iconName", icon_name)
+ +
[docs] def by_id(self, id: str) -> Union['XAAutomatorAction', None]: + """Retrieves the action whose ID matches the given ID, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("id", id)
+ +
[docs] def by_ignores_input(self, ignores_input: bool) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose ignore input status matches the given boolean value, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("ignoresInput", ignores_input)
+ +
[docs] def by_input_types(self, input_types: List[str]) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose input types match the given input types, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("inputTypes", input_types)
+ +
[docs] def by_keywords(self, keywords: List[str]) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose keywords match the given keywords, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("keywords", keywords)
+ +
[docs] def by_name(self, name: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose name matches the given name, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("name", name)
+ +
[docs] def by_output_types(self, output_types: List[str]) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose output types match the given output types, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("outputTypes", output_types)
+ +
[docs] def by_parent_workflow(self, parent_workflow: 'XAAutomatorWorkflow') -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose parent workflow matches the given workflow, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("parentWorkflow", parent_workflow.xa_elem)
+ +
[docs] def by_path(self, path: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose path matches the given path, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("path", path)
+ +
[docs] def by_show_action_when_run(self, show_action_when_run: bool) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose show action when run status matches the given boolean value, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("show_action_when_run", show_action_when_run)
+ +
[docs] def by_target_application(self, target_application: List[str]) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose target application matches the given application name, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("targetApplication", target_application)
+ +
[docs] def by_version(self, version: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose version matches the given version, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("version", version)
+ +
[docs] def by_warning_action(self, warning_action: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose warning action matches the given action, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("warningAction", warning_action)
+ +
[docs] def by_warning_level(self, warning_level: XAAutomatorApplication.WarningLevel) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose warning level matches the given warning level, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("warningLevel", warning_level.value)
+ +
[docs] def by_warning_message(self, warning_message: str) -> Union['XAAutomatorAction', None]: + """Retrieves the first action whose warning message matches the given message, if one exists. + + :return: The desired action, if it is found + :rtype: Union[XAAutomatorAction, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("warningMessage", warning_message)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAAutomatorAction(XABase.XAObject): + """A class for managing and interacting with actions in Automator.app. + + .. seealso:: :class:`XAAutomatorApplication` + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties): + super().__init__(properties) + self.bundle_id: str #: The bundle identifier for the action + self.category: List[str] #: The category that contains the action + self.comment: str #: The comment for the name of the action + self.enabled: bool #: Whether the action is enabled + self.execution_error_message: str #: The text error message generated by execution of the action + self.execution_error_number: int #: The numeric error code generated by execution of the action + self.execution_result: Any #: The result of the action, passed as input to the next action + self.icon_name: str #: The name for the icon associated with the action + self.id: str #: The unique identifier for the action + self.ignores_input: bool #: Whether the action ignores input when run + self.index: int #: The index of the action from the first action in the workflow + self.input_types: List[str] #: The input types accepted by the action + self.keywords: List[str] #: The keywords that describe the action + self.name: str #: The localized name of the action + self.output_types: List[str] #: The output types produces by the action + self.parent_workflow: XAAutomatorWorkflow #: The workflow that contains the action + self.path: str #: The path of the file that contains the action + self.show_action_when_run: bool #: Whether the action should show its user interface when run + self.target_application: List[str] #: The application(s) with which the action communicates + self.version: str #: The version of the action + self.warning_action: str #: The action suggested by the warning, if any + self.warning_level: XAAutomatorApplication.WarningLevel #: The level of the warning, increasing in likelihood of data loss + self.warning_message: str #: The message that accompanies the warning, if any + + @property + def bundle_id(self) -> str: + return self.xa_elem.bundleId() + + @property + def category(self) -> List[str]: + return self.xa_elem.category() + + @property + def comment(self) -> str: + return self.xa_elem.comment() + + @comment.setter + def comment(self, comment: str): + self.set_property("comment", comment) + + @property + def enabled(self) -> bool: + return self.xa_elem.enabled() + + @enabled.setter + def enabled(self, enabled: bool): + self.set_property("enabled", enabled) + + @property + def execution_error_message(self) -> str: + return self.xa_elem.executionErrorMessage() + + @property + def execution_error_number(self) -> int: + return self.xa_elem.executionErrorNumber() + + @property + def execution_result(self) -> Any: + return self.xa_elem.executionResult() + + @property + def icon_name(self) -> str: + return self.xa_elem.iconName() + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def ignores_input(self) -> bool: + return self.xa_elem.ignoresInput() + + @ignores_input.setter + def ignores_input(self, ignores_input: bool): + self.set_property("ignoresInput", ignores_input) + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property("index", index) + + @property + def input_types(self) -> List[str]: + return self.xa_elem.inputTypes() + + @property + def keywords(self) -> List[str]: + return self.xa_elem.keywords() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def output_types(self) -> List[str]: + return self.xa_elem.outputTypes() + + @property + def parent_workflow(self) -> 'XAAutomatorWorkflow': + return self._new_element(self.xa_elem.parentWorkflow(), XAAutomatorWorkflow) + + @property + def path(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.path()) + + @property + def show_action_when_run(self) -> bool: + return self.xa_elem.showActionWehnRun() + + @show_action_when_run.setter + def show_action_when_run(self, show_action_when_run: bool): + self.set_property("showActionWhenRun", show_action_when_run) + + @property + def target_application(self) -> List[str]: + return self.xa_elem.targetApplication() + + @property + def version(self) -> str: + return self.xa_elem.version() + + @property + def warning_action(self) -> str: + return self.xa_elem.warningAction() + + @property + def warning_level(self) -> XAAutomatorApplication.WarningLevel: + return XAAutomatorApplication.WarningLevel(self.xa_elem.warningLevel()) + + @property + def warning_message(self) -> str: + return self.xa_elem.warningMessage() + +
[docs] def required_resources(self, filter: Union[dict, None] = None) -> 'XAAutomatorRequiredResourceList': + """Returns a list of required resource, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter resources by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of required resources + :rtype: XAAutomatorVariableList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_elem.requiredResources(), XAAutomatorRequiredResourceList, filter)
+ +
[docs] def settings(self, filter: Union[dict, None] = None) -> 'XAAutomatorSettingList': + """Returns a list of settings, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter settings by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of settings + :rtype: XAAutomatorWorkflowList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_elem.settings(), XAAutomatorSettingList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + self.name + ">"
+ + + + +
[docs]class XAAutomatorRequiredResourceList(XABase.XAList): + """A wrapper around a list of Automator required resources which utilizes fast enumeration techniques. + + All properties of required resources can be called as methods on the wrapped list, returning a list containing each resource's value for the property. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAAutomatorRequiredResource, filter) + +
[docs] def kind(self) -> List[str]: + """Retrieves the resource type of each resource in the list. + + :return: The list of resource types. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("kind"))
+ +
[docs] def name(self) -> List[str]: + """Retrieves the name of each resource in the list. + + :return: The list of resource names. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def resource(self) -> List[str]: + """Retrieves the specification of each resource in the list. + + :return: The list of resource specifications. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("resource"))
+ +
[docs] def version(self) -> List[int]: + """Retrieves the version of each resource in the list. + + :return: The list of resource versions. + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("version"))
+ +
[docs] def by_kind(self, kind: str) -> Union['XAAutomatorRequiredResource', None]: + """Retrieves the first resource whose kind matches the given kind, if one exists. + + :return: The desired resource, if it is found + :rtype: Union[XAAutomatorRequiredResource, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("kind", kind)
+ +
[docs] def by_name(self, name: str) -> Union['XAAutomatorRequiredResource', None]: + """Retrieves the first resource whose name matches the given name, if one exists. + + :return: The desired resource, if it is found + :rtype: Union[XAAutomatorRequiredResource, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("name", name)
+ +
[docs] def by_resource(self, resource: str) -> Union['XAAutomatorRequiredResource', None]: + """Retrieves the first resource whose specification matches the given specification, if one exists. + + :return: The desired resource, if it is found + :rtype: Union[XAAutomatorRequiredResource, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("resource", resource)
+ +
[docs] def by_version(self, version: int) -> Union['XAAutomatorRequiredResource', None]: + """Retrieves the first resource whose version matches the given version, if one exists. + + :return: The desired resource, if it is found + :rtype: Union[XAAutomatorRequiredResource, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("version", version)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAAutomatorRequiredResource(XABase.XAObject): + """A class for managing and interacting with required resources in Automator.app. + + .. seealso:: :class:`XAAutomatorApplication` + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties): + super().__init__(properties) + self.kind: str #: The kind of required resource + self.name: str #: The name of the required resource + self.resource: str #: The specification of the required resource + self.version: int #: The minimum acceptable version of the required resource + + @property + def kind(self) -> str: + return self.xa_elem.kind() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def resource(self) -> str: + return self.xa_elem.resource() + + @property + def version(self) -> int: + return self.xa_elem.version() + + def __repr__(self): + return "<" + str(type(self)) + self.name + ">"
+ + + + +
[docs]class XAAutomatorSettingList(XABase.XAList): + """A wrapper around a list of Automator settings which utilizes fast enumeration techniques. + + All properties of settings can be called as methods on the wrapped list, returning a list containing each setting's value for the property. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAAutomatorSetting, filter) + +
[docs] def default_value(self) -> List[Any]: + """Retrieves the default value of each setting in the list. + + :return: The list of default values. + :rtype: List[Any] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("defaultValue"))
+ +
[docs] def name(self) -> List[str]: + """Retrieves the name of each setting in the list. + + :return: The list of setting names. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def value(self) -> List[Any]: + """Retrieves the current value of each setting in the list. + + :return: The list of current values. + :rtype: List[Any] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ +
[docs] def by_default_value(self, default_value: Any) -> Union['XAAutomatorSetting', None]: + """Retrieves the first setting whose default value matches the given value, if one exists. + + :return: The desired setting, if it is found + :rtype: Union[XAAutomatorSetting, None] + + .. versionadded:: 0.0.4 + """ + if isinstance(default_value, XABase.XAObject): + default_value = default_value.xa_elem + return self.by_property("defaultValue", default_value)
+ +
[docs] def by_name(self, name: str) -> Union['XAAutomatorSetting', None]: + """Retrieves the first setting whose name matches the given name, if one exists. + + :return: The desired setting, if it is found + :rtype: Union[XAAutomatorSetting, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("name", name)
+ +
[docs] def by_value(self, value: Any) -> Union['XAAutomatorSetting', None]: + """Retrieves the first setting whose current value matches the given value, if one exists. + + :return: The desired setting, if it is found + :rtype: Union[XAAutomatorSetting, None] + + .. versionadded:: 0.0.4 + """ + if isinstance(value, XABase.XAObject): + value = value.xa_elem + return self.by_property("value", value)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAAutomatorSetting(XABase.XAObject): + """A class for managing and interacting with Automator settings (i.e. named values). + + .. seealso:: :class:`XAAutomatorApplication` + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties): + super().__init__(properties) + self.default_value: Any #: The default value of the setting + self.name: str #: The name of the setting + self.value: Any #: The value of the setting + + @property + def default_value(self) -> Any: + return self.xa_elem.defaultValue() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def value(self) -> Any: + return self.xa_elem.value() + + @value.setter + def value(self, value: Any): + self.set_property("value", value) + + def __repr__(self): + return "<" + str(type(self)) + self.name + ">"
+ + + + +
[docs]class XAAutomatorVariableList(XABase.XAList): + """A wrapper around a list of Automator variables which utilizes fast enumeration techniques. + + All properties of variables can be called as methods on the wrapped list, returning a list containing each variable's value for the property. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAAutomatorVariable, filter) + +
[docs] def name(self) -> List[str]: + """Retrieves the name of each variable in the list. + + :return: The list of variable names. + :rtype: List[Any] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def settable(self) -> List[bool]: + """Retrieves the value of the settable attribute of each variable in the list. + + :return: The list of settable status booleans. + :rtype: List[bool] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("settable"))
+ +
[docs] def value(self) -> List[Any]: + """Retrieves the current value of each variable in the list. + + :return: The list of current values. + :rtype: List[Any] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ +
[docs] def by_name(self, name: str) -> Union['XAAutomatorVariable', None]: + """Retrieves the first variable whose name matches the given name, if one exists. + + :return: The desired variable, if it is found + :rtype: Union[XAAutomatorVariable, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("name", name)
+ +
[docs] def by_settable(self, settable: bool) -> Union['XAAutomatorVariable', None]: + """Retrieves the first variable whose settable status matches the given boolean, if one exists. + + :return: The desired variable, if it is found + :rtype: Union[XAAutomatorVariable, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("settable", settable)
+ +
[docs] def by_value(self, value: Any) -> Union['XAAutomatorVariable', None]: + """Retrieves the first variable whose current value matches the given value, if one exists. + + :return: The desired variable, if it is found + :rtype: Union[XAAutomatorVariable, None] + + .. versionadded:: 0.0.4 + """ + if isinstance(value, XABase.XAObject): + value = value.xa_elem + return self.by_property("value", value)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAAutomatorVariable(XABase.XAObject): + """A class for managing and interacting with Automator variables. + + .. seealso:: :class:`XAAutomatorApplication` + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties): + super().__init__(properties) + self.name: str #: The name of the variable + self.settable: bool #: Whether the name and value of the variable can be changed + self.value: Any #: The value of the variable + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property("name", name) + + @property + def settable(self) -> bool: + return self.xa_elem.settable() + + @property + def value(self) -> Any: + return self.xa_elem.value() + + @value.setter + def value(self, value: Any): + self.set_property("value", value) + + def __repr__(self): + return "<" + str(type(self)) + self.name + ">"
+ + + + +
[docs]class XAAutomatorWorkflowList(XABase.XAList): + """A wrapper around a list of Automator workflows which utilizes fast enumeration techniques. + + All properties of workflows can be called as methods on the wrapped list, returning a list containing each workflow's value for the property. + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAAutomatorWorkflow, filter) + +
[docs] def current_action(self) -> XAAutomatorActionList: + """Retrieves the current action of each workflow in the list. + + :return: The list of current actions. + :rtype: XAAutomatorActionList + + .. versionadded:: 0.0.4 + """ + ls = self.xa_elem.arrayByApplyingSelector_("currentAction") + return self._new_element(ls, XAAutomatorActionList)
+ +
[docs] def execution_error_message(self) -> List[str]: + """Retrieves the execution error message of each workflow in the list. + + :return: The list of error messages. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("executionErrorMessage"))
+ +
[docs] def execution_error_number(self) -> List[int]: + """Retrieves the execution error numbers of each workflow in the list. + + :return: The list of error numbers. + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("executionErrorNumber"))
+ +
[docs] def execution_id(self) -> List[str]: + """Retrieves the execution ID of each workflow in the list. + + :return: The list of execution IDs. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("executionId"))
+ +
[docs] def execution_result(self) -> List[Any]: + """Retrieves the execution result of each workflow in the list. + + :return: The list of results. + :rtype: List[Any] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("executionResult"))
+ +
[docs] def name(self) -> List[str]: + """Retrieves the name of each workflow in the list. + + :return: The list of workflow names. + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def by_current_action(self, current_action: XAAutomatorAction) -> Union['XAAutomatorWorkflow', None]: + """Retrieves the first workflow whose current action matches the given action, if one exists. + + :return: The desired workflow, if it is found + :rtype: Union[XAAutomatorWorkflow, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("currentAction", current_action.xa_elem)
+ +
[docs] def by_execution_error_message(self, execution_error_message: str) -> Union['XAAutomatorWorkflow', None]: + """Retrieves the first workflow whose error message matches the given message, if one exists. + + :return: The desired workflow, if it is found + :rtype: Union[XAAutomatorWorkflow, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("executionErrorMessage", execution_error_message)
+ +
[docs] def by_execution_error_number(self, execution_error_number: int) -> Union['XAAutomatorWorkflow', None]: + """Retrieves the first workflow whose error number matches the given error number, if one exists. + + :return: The desired workflow, if it is found + :rtype: Union[XAAutomatorWorkflow, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("executionErrorNumber", execution_error_number)
+ +
[docs] def by_execution_id(self, execution_id: str) -> Union['XAAutomatorWorkflow', None]: + """Retrieves the workflow whose execution ID matches the given ID, if one exists. + + :return: The desired workflow, if it is found + :rtype: Union[XAAutomatorWorkflow, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("executionId", execution_id)
+ +
[docs] def by_execution_result(self, result: Any) -> Union['XAAutomatorWorkflow', None]: + """Retrieves the first workflow whose execution result matches the given value, if one exists. + + :return: The desired workflow, if it is found + :rtype: Union[XAAutomatorWorkflow, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("result", result)
+ +
[docs] def by_name(self, name: str) -> Union['XAAutomatorWorkflow', None]: + """Retrieves the first workflow whose name matches the given name, if one exists. + + :return: The desired workflow, if it is found + :rtype: Union[XAAutomatorWorkflow, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("name", name)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAAutomatorWorkflow(XAAutomatorDocument): + """A class for managing and interacting with Automator workflows. + + .. seealso:: :class:`XAAutomatorApplication` + + .. versionadded:: 0.0.4 + """ + def __init__(self, properties): + super().__init__(properties) + self.current_action: XAAutomatorAction #: The current or most recent action of the workflow + self.execution_error_message: str #: The text error message generated by the most recent execution + self.execution_error_number: int #: The numeric error code generated by the most recent execution + self.execution_id: str #: The unique identifier for the current or most recent execution + self.execution_result: Any #: The result of the most resent execution + self.name: str #: The name of the workflow + + @property + def current_action(self) -> XAAutomatorAction: + return self._new_element(self.xa_elem.currentAction(), XAAutomatorAction) + + @property + def execution_error_message(self) -> str: + return self.xa_elem.executionErrorMessage() + + @property + def execution_error_number(self) -> int: + return self.xa_elem.executionErrorNumber() + + @property + def execution_id(self) -> str: + return self.xa_elem.executionId() + + @property + def execution_result(self) -> Any: + return self.xa_elem.executionResult().get() + + @property + def name(self) -> str: + return self.xa_elem.name() + +
[docs] def execute(self) -> Any: + """Executes the workflow. + + :return: The return value of the workflow after execution + :rtype: Any + + .. versionadded:: 0.0.5 + """ + return self.xa_elem.execute()
+ +
[docs] def automator_actions(self, filter: Union[dict, None] = None) -> 'XAAutomatorActionList': + """Returns a list of actions, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter actions by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of actions + :rtype: XAAutomatorActionList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_elem.AutomatorActions(), XAAutomatorActionList, filter)
+ +
[docs] def variables(self, filter: Union[dict, None] = None) -> 'XAAutomatorVariableList': + """Returns a list of variables, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter variables by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of variables + :rtype: XAAutomatorVariableList + + .. versionadded:: 0.0.4 + """ + return self._new_element(self.xa_elem.variables(), XAAutomatorVariableList, filter)
+ +
[docs] def delete(self): + """Closes the workflow. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.delete()
+ +
[docs] def save(self) -> 'XAAutomatorWorkflow': + """Saves the workflow to the disk at the location specified by :attr:`XAAutomatorWorkflow.path`, or in the downloads folder if no path has been specified. + + :return: The workflow object. + :rtype: XAAutomatorWorkflow + + .. versionadded:: 0.0.5 + """ + self.xa_elem.saveAs_in_("workflow", self.path) + return self
+ + def __repr__(self): + return "<" + str(type(self)) + self.name + ">"
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Automator.html b/docs/_modules/PyXA/apps/Automator.html index 4f61803..5a84e52 100644 --- a/docs/_modules/PyXA/apps/Automator.html +++ b/docs/_modules/PyXA/apps/Automator.html @@ -3,7 +3,7 @@ - PyXA.apps.Automator — PyXA 0.0.9 documentation + PyXA.apps.Automator — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -74,11 +76,9 @@

Source code for PyXA.apps.Automator

 """
 
 from enum import Enum
-from turtle import st
-from typing import Any, List, Tuple, Union
-from AppKit import NSFileManager, NSURL, NSSet, NSValue, NSMakeRect
+from typing import Any, Union
 
-from AppKit import NSPredicate, NSMutableArray, NSFileManager
+import AppKit
 
 from PyXA import XABase
 from PyXA import XABaseScriptable
@@ -114,11 +114,15 @@ 

Source code for PyXA.apps.Automator

     def frontmost(self) -> bool:
         return self.xa_scel.frontmost()
 
+    @frontmost.setter
+    def frontmost(self, frontmost: bool):
+        self.set_property('frontmost', frontmost)
+
     @property
     def version(self) -> str:
         return self.xa_scel.version()
 
-
[docs] def open(self, path: Union[str, NSURL]) -> 'XAAutomatorWorkflow': +
[docs] def open(self, path: Union[str, AppKit.NSURL]) -> 'XAAutomatorWorkflow': """Opens the file at the given filepath. :param target: The path to a file or the URL to a website to open. @@ -128,7 +132,7 @@

Source code for PyXA.apps.Automator

 
         .. versionadded:: 0.0.1
         """
-        if not isinstance(path, NSURL):
+        if not isinstance(path, AppKit.NSURL):
             path = XABase.XAPath(path)
         self.xa_wksp.openURLs_withAppBundleIdentifier_options_additionalEventParamDescriptor_launchIdentifiers_([path.xa_elem], self.xa_elem.bundleIdentifier(), 0, None, None)
         return self.workflows()[0]
@@ -217,7 +221,7 @@

Source code for PyXA.apps.Automator

 
         if specifier == "workflow":
             if "path" not in properties and "name" in properties:
-                fm = NSFileManager.defaultManager()
+                fm = AppKit.NSFileManager.defaultManager()
                 properties.update({"path": f"{fm.homeDirectoryForCurrentUser().path()}/Downloads/{properties.get('name')}.workflow"})
             elif not properties.get("path").endswith(".workflow"):
                 properties.update({"path": properties.get("path") + ".workflow"})
@@ -235,6 +239,9 @@ 

Source code for PyXA.apps.Automator

         elif specifier == "setting":
             return self._new_element(obj, XAAutomatorSetting)
+ + +
[docs]class XAAutomatorWindow(XABaseScriptable.XASBWindow): """A class for managing and interacting with Automator windows. @@ -247,7 +254,7 @@

Source code for PyXA.apps.Automator

         self.name: str #: The full title of the window
         self.id: int #: The unique identifier for the window
         self.index: int #: The index of the window in the front-to-back ordering
-        self.bounds: Tuple[Tuple[int, int], Tuple[int, int]] #: The bounding rectangle of the window
+        self.bounds: tuple[int, int, int, int] #: The bounding rectangle of the window
         self.floating: bool #: Whether the window float
         self.modal: bool #: Whether the window is a modal window
         self.closeable: bool #: Whether the window has a close button
@@ -281,16 +288,19 @@ 

Source code for PyXA.apps.Automator

         self.set_property("index", index)
 
     @property
-    def bounds(self) -> Tuple[Tuple[int, int], Tuple[int, int]]:
-        return self.xa_elem.bounds()
+    def bounds(self) -> tuple[int, int, int, int]:
+        rect = self.xa_elem.bounds()
+        origin = rect.origin
+        size = rect.size
+        return (origin.x, origin.y, size.width, size.height)
 
     @bounds.setter
-    def bounds(self, bounds: Tuple[Tuple[int, int], Tuple[int, int]]):
-        x = bounds[0][0]
-        y = bounds[0][1]
-        w = bounds[1][0]
-        h = bounds[1][1]
-        value = NSValue.valueWithRect_(NSMakeRect(x, y, w, h))
+    def bounds(self, bounds: tuple[int, int, int, int]):
+        x = bounds[0]
+        y = bounds[1]
+        w = bounds[2]
+        h = bounds[3]
+        value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h))
         self.set_property("bounds", value)
 
     @property
@@ -362,31 +372,31 @@ 

Source code for PyXA.apps.Automator

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAAutomatorDocument, filter)
 
-
[docs] def id(self) -> List[int]: +
[docs] def id(self) -> list[int]: """Retrieves the ID of each document in the list. :return: The list of document IDs. - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def title(self) -> List[str]: +
[docs] def title(self) -> list[str]: """Retrieves the title of each document in the list. :return: The list of document titles. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("title"))
-
[docs] def index(self) -> List[int]: +
[docs] def index(self) -> list[int]: """Retrieves the index of each document in the list. :return: The list of document indexes. - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ @@ -451,12 +461,14 @@

Source code for PyXA.apps.Automator

         self.set_property("name", name)
 
     @property
-    def path(self) -> str:
-        return self.xa_elem.path()
+    def path(self) -> XABase.XAPath:
+        return XABase.XAPath(self.xa_elem.path())
 
     @path.setter
-    def path(self, path: str):
-        self.set_property("path", path)
+    def path(self, path: Union[str, XABase.XAPath]):
+        if isinstance(path, str):
+            path = XABase.XAPath(path)
+        self.set_property("path", path.path)
 
     def __repr__(self):
         return "<" + str(type(self)) + self.name + ">"
@@ -474,141 +486,141 @@

Source code for PyXA.apps.Automator

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAAutomatorAction, filter)
 
-
[docs] def bundle_id(self) -> List[str]: +
[docs] def bundle_id(self) -> list[str]: """Retrieves the bundle identifier of each action in the list. :return: The list of bundle identifiers. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("bundleId"))
-
[docs] def category(self) -> List[List[str]]: +
[docs] def category(self) -> list[list[str]]: """Retrieves the category/categories of each action in the list. :return: The list of categories. - :rtype: List[List[str]] + :rtype: list[list[str]] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("category"))
-
[docs] def comment(self) -> List[str]: +
[docs] def comment(self) -> list[str]: """Retrieves the comments of each action in the list. :return: The list of comments. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("comment"))
-
[docs] def enabled(self) -> List[bool]: +
[docs] def enabled(self) -> list[bool]: """Retrieves the enabled status of each action in the list. :return: The list of enabled status booleans. - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("enabled"))
-
[docs] def execution_error_message(self) -> List[str]: +
[docs] def execution_error_message(self) -> list[str]: """Retrieves the execution error message of each action in the list. :return: The list of error messages. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("executionErrorMessage"))
-
[docs] def execution_error_number(self) -> List[int]: +
[docs] def execution_error_number(self) -> list[int]: """Retrieves the execution error number of each action in the list. :return: The list of error numbers. - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("executionErrorNumber"))
-
[docs] def execution_result(self) -> List[Any]: +
[docs] def execution_result(self) -> list[Any]: """Retrieves the result value of each action in the list. :return: The list of results. - :rtype: List[Any] + :rtype: list[Any] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("executionResult"))
-
[docs] def icon_name(self) -> List[str]: +
[docs] def icon_name(self) -> list[str]: """Retrieves the icon name of each action in the list. :return: The list of icon names. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("iconName"))
-
[docs] def ignores_input(self) -> List[bool]: +
[docs] def ignores_input(self) -> list[bool]: """Retrieves the ignore input status of each action in the list. :return: The list of ignore input status booleans. - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("ignoresInput"))
-
[docs] def index(self) -> List[int]: +
[docs] def index(self) -> list[int]: """Retrieves the index of each action in the list. :return: The list of action indices. - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("index"))
-
[docs] def input_types(self) -> List[List[str]]: +
[docs] def input_types(self) -> list[list[str]]: """Retrieves the input types of each action in the list. :return: The list of input types. - :rtype: List[List[str]] + :rtype: list[list[str]] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("inputTypes"))
-
[docs] def keywords(self) -> List[List[str]]: +
[docs] def keywords(self) -> list[list[str]]: """Retrieves the keywords of each action in the list. :return: The list of keywords. - :rtype: List[List[str]] + :rtype: list[list[str]] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("keywords"))
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Retrieves the name of each action in the list. :return: The list of names. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def output_types(self) -> List[List[str]]: +
[docs] def output_types(self) -> list[list[str]]: """Retrieves the output types of each action in the list. :return: The list of output types. - :rtype: List[List[str]] + :rtype: list[list[str]] .. versionadded:: 0.0.4 """ @@ -625,72 +637,72 @@

Source code for PyXA.apps.Automator

         ls = self.xa_elem.arrayByApplyingSelector_("parentWorkflow")
         return self._new_element(ls, XAAutomatorWorkflowList)
-
[docs] def path(self) -> List[str]: +
[docs] def path(self) -> list[str]: """Retrieves the file path of each action in the list. :return: The list of paths. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("path"))
-
[docs] def show_action_when_run(self) -> List[bool]: +
[docs] def show_action_when_run(self) -> list[bool]: """Retrieves the status of the show action when run setting of each action in the list. :return: The list of boolean statuses. - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("showActionWhenRun"))
-
[docs] def target_application(self) -> List[List[str]]: +
[docs] def target_application(self) -> list[list[str]]: """Retrieves the target application name of each action in the list. :return: The list of target application names. - :rtype: List[List[str]] + :rtype: list[list[str]] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("targetApplication"))
-
[docs] def version(self) -> List[str]: +
[docs] def version(self) -> list[str]: """Retrieves the version of each action in the list. :return: The list of versions. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("version"))
-
[docs] def warning_action(self) -> List[str]: +
[docs] def warning_action(self) -> list[str]: """Retrieves the warning action of each action in the list. :return: The list of warning actions. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("warningAction"))
-
[docs] def warning_level(self) -> List[XAAutomatorApplication.WarningLevel]: +
[docs] def warning_level(self) -> list[XAAutomatorApplication.WarningLevel]: """Retrieves the warning level of each action in the list. :return: The list of warning levels. - :rtype: List[XAAutomatorApplication.WarningLevel] + :rtype: list[XAAutomatorApplication.WarningLevel] .. versionadded:: 0.0.4 """ ls = self.xa_elem.arrayByApplyingSelector_("warningLevel") return [XAAutomatorApplication.WarningLevel(x) for x in ls]
-
[docs] def warning_message(self) -> List[str]: +
[docs] def warning_message(self) -> list[str]: """Retrieves the warning message of each action in the list. :return: The list of warning messages. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ @@ -706,7 +718,7 @@

Source code for PyXA.apps.Automator

         """
         return self.by_property("bundleId", bundle_id)
-
[docs] def by_category(self, category: List[str]) -> Union['XAAutomatorAction', None]: +
[docs] def by_category(self, category: list[str]) -> Union['XAAutomatorAction', None]: """Retrieves the first action whose category matches the given category, if one exists. :return: The desired action, if it is found @@ -796,7 +808,7 @@

Source code for PyXA.apps.Automator

         """
         return self.by_property("ignoresInput", ignores_input)
-
[docs] def by_input_types(self, input_types: List[str]) -> Union['XAAutomatorAction', None]: +
[docs] def by_input_types(self, input_types: list[str]) -> Union['XAAutomatorAction', None]: """Retrieves the first action whose input types match the given input types, if one exists. :return: The desired action, if it is found @@ -806,7 +818,7 @@

Source code for PyXA.apps.Automator

         """
         return self.by_property("inputTypes", input_types)
-
[docs] def by_keywords(self, keywords: List[str]) -> Union['XAAutomatorAction', None]: +
[docs] def by_keywords(self, keywords: list[str]) -> Union['XAAutomatorAction', None]: """Retrieves the first action whose keywords match the given keywords, if one exists. :return: The desired action, if it is found @@ -826,7 +838,7 @@

Source code for PyXA.apps.Automator

         """
         return self.by_property("name", name)
-
[docs] def by_output_types(self, output_types: List[str]) -> Union['XAAutomatorAction', None]: +
[docs] def by_output_types(self, output_types: list[str]) -> Union['XAAutomatorAction', None]: """Retrieves the first action whose output types match the given output types, if one exists. :return: The desired action, if it is found @@ -866,7 +878,7 @@

Source code for PyXA.apps.Automator

         """
         return self.by_property("show_action_when_run", show_action_when_run)
-
[docs] def by_target_application(self, target_application: List[str]) -> Union['XAAutomatorAction', None]: +
[docs] def by_target_application(self, target_application: list[str]) -> Union['XAAutomatorAction', None]: """Retrieves the first action whose target application matches the given application name, if one exists. :return: The desired action, if it is found @@ -929,7 +941,7 @@

Source code for PyXA.apps.Automator

     def __init__(self, properties):
         super().__init__(properties)
         self.bundle_id: str #: The bundle identifier for the action
-        self.category: List[str] #: The category that contains the action
+        self.category: list[str] #: The category that contains the action
         self.comment: str #: The comment for the name of the action
         self.enabled: bool #: Whether the action is enabled
         self.execution_error_message: str #: The text error message generated by execution of the action
@@ -939,14 +951,14 @@ 

Source code for PyXA.apps.Automator

         self.id: str #: The unique identifier for the action
         self.ignores_input: bool #: Whether the action ignores input when run
         self.index: int #: The index of the action from the first action in the workflow
-        self.input_types: List[str] #: The input types accepted by the action
-        self.keywords: List[str] #: The keywords that describe the action
+        self.input_types: list[str] #: The input types accepted by the action
+        self.keywords: list[str] #: The keywords that describe the action
         self.name: str #: The localized name of the action
-        self.output_types: List[str] #: The output types produces by the action
+        self.output_types: list[str] #: The output types produces by the action
         self.parent_workflow: XAAutomatorWorkflow #: The workflow that contains the action
         self.path: str #: The path of the file that contains the action
         self.show_action_when_run: bool #: Whether the action should show its user interface when run
-        self.target_application: List[str] #: The application(s) with which the action communicates
+        self.target_application: list[str] #: The application(s) with which the action communicates
         self.version: str #: The version of the action
         self.warning_action: str #: The action suggested by the warning, if any
         self.warning_level: XAAutomatorApplication.WarningLevel #: The level of the warning, increasing in likelihood of data loss
@@ -957,7 +969,7 @@ 

Source code for PyXA.apps.Automator

         return self.xa_elem.bundleId()
 
     @property
-    def category(self) -> List[str]:
+    def category(self) -> list[str]:
         return self.xa_elem.category()
 
     @property
@@ -1013,11 +1025,11 @@ 

Source code for PyXA.apps.Automator

         self.set_property("index", index)
 
     @property
-    def input_types(self) -> List[str]:
+    def input_types(self) -> list[str]:
         return self.xa_elem.inputTypes()
 
     @property
-    def keywords(self) -> List[str]:
+    def keywords(self) -> list[str]:
         return self.xa_elem.keywords()
 
     @property
@@ -1025,7 +1037,7 @@ 

Source code for PyXA.apps.Automator

         return self.xa_elem.name()
 
     @property
-    def output_types(self) -> List[str]:
+    def output_types(self) -> list[str]:
         return self.xa_elem.outputTypes()
 
     @property
@@ -1033,8 +1045,8 @@ 

Source code for PyXA.apps.Automator

         return self._new_element(self.xa_elem.parentWorkflow(), XAAutomatorWorkflow)
 
     @property
-    def path(self) -> str:
-        return self.xa_elem.path()
+    def path(self) -> XABase.XAPath:
+        return XABase.XAPath(self.xa_elem.path())
 
     @property
     def show_action_when_run(self) -> bool:
@@ -1045,7 +1057,7 @@ 

Source code for PyXA.apps.Automator

         self.set_property("showActionWhenRun", show_action_when_run)
 
     @property
-    def target_application(self) -> List[str]:
+    def target_application(self) -> list[str]:
         return self.xa_elem.targetApplication()
 
     @property
@@ -1104,41 +1116,41 @@ 

Source code for PyXA.apps.Automator

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAAutomatorRequiredResource, filter)
 
-
[docs] def kind(self) -> List[str]: +
[docs] def kind(self) -> list[str]: """Retrieves the resource type of each resource in the list. :return: The list of resource types. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("kind"))
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Retrieves the name of each resource in the list. :return: The list of resource names. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def resource(self) -> List[str]: +
[docs] def resource(self) -> list[str]: """Retrieves the specification of each resource in the list. :return: The list of resource specifications. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("resource"))
-
[docs] def version(self) -> List[int]: +
[docs] def version(self) -> list[int]: """Retrieves the version of each resource in the list. :return: The list of resource versions. - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ @@ -1233,31 +1245,31 @@

Source code for PyXA.apps.Automator

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAAutomatorSetting, filter)
 
-
[docs] def default_value(self) -> List[Any]: +
[docs] def default_value(self) -> list[Any]: """Retrieves the default value of each setting in the list. :return: The list of default values. - :rtype: List[Any] + :rtype: list[Any] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("defaultValue"))
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Retrieves the name of each setting in the list. :return: The list of setting names. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def value(self) -> List[Any]: +
[docs] def value(self) -> list[Any]: """Retrieves the current value of each setting in the list. :return: The list of current values. - :rtype: List[Any] + :rtype: list[Any] .. versionadded:: 0.0.4 """ @@ -1345,31 +1357,31 @@

Source code for PyXA.apps.Automator

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAAutomatorVariable, filter)
 
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Retrieves the name of each variable in the list. :return: The list of variable names. - :rtype: List[Any] + :rtype: list[Any] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def settable(self) -> List[bool]: +
[docs] def settable(self) -> list[bool]: """Retrieves the value of the settable attribute of each variable in the list. :return: The list of settable status booleans. - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("settable"))
-
[docs] def value(self) -> List[Any]: +
[docs] def value(self) -> list[Any]: """Retrieves the current value of each variable in the list. :return: The list of current values. - :rtype: List[Any] + :rtype: list[Any] .. versionadded:: 0.0.4 """ @@ -1470,51 +1482,51 @@

Source code for PyXA.apps.Automator

         ls = self.xa_elem.arrayByApplyingSelector_("currentAction")
         return self._new_element(ls, XAAutomatorActionList)
-
[docs] def execution_error_message(self) -> List[str]: +
[docs] def execution_error_message(self) -> list[str]: """Retrieves the execution error message of each workflow in the list. :return: The list of error messages. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("executionErrorMessage"))
-
[docs] def execution_error_number(self) -> List[int]: +
[docs] def execution_error_number(self) -> list[int]: """Retrieves the execution error numbers of each workflow in the list. :return: The list of error numbers. - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("executionErrorNumber"))
-
[docs] def execution_id(self) -> List[str]: +
[docs] def execution_id(self) -> list[str]: """Retrieves the execution ID of each workflow in the list. :return: The list of execution IDs. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("executionId"))
-
[docs] def execution_result(self) -> List[Any]: +
[docs] def execution_result(self) -> list[Any]: """Retrieves the execution result of each workflow in the list. :return: The list of results. - :rtype: List[Any] + :rtype: list[Any] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("executionResult"))
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Retrieves the name of each workflow in the list. :return: The list of workflow names. - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ @@ -1623,10 +1635,6 @@

Source code for PyXA.apps.Automator

     def name(self) -> str:
         return self.xa_elem.name()
 
-    @name.setter
-    def name(self, name: str):
-        self.set_property("name", name)
-
 
[docs] def execute(self) -> Any: """Executes the workflow. diff --git a/docs/_modules/PyXA/apps/Bike 3.html b/docs/_modules/PyXA/apps/Bike 3.html new file mode 100644 index 0000000..7fba7cd --- /dev/null +++ b/docs/_modules/PyXA/apps/Bike 3.html @@ -0,0 +1,1388 @@ + + + + + + PyXA.apps.Bike — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Bike

+""".. versionadded:: 0.1.0
+
+Control Bike using JXA-like syntax.
+"""
+
+from enum import Enum
+from typing import Any, List, Tuple, Union
+
+import AppKit
+
+from PyXA import XABase
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath, XAClipboardCodable, XACloseable, XADeletable, XAPrintable
+from ..XAErrors import UnconstructableClassError
+
+
[docs]class XABikeApplication(XABaseScriptable.XASBApplication, XACanOpenPath): + """A class for managing and interacting with Bike.app. + + .. versionadded:: 0.1.0 + """ +
[docs] class FileFormat(Enum): + BIKE = XABase.OSType("BKff") + OPML = XABase.OSType("OPml") + PLAINTEXT = XABase.OSType("PTfm")
+ + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XABikeWindow + + self.name: str #: The name of the application + self.frontmost: bool #: Whether Bike is the frontmost application + self.version: str #: The version of Bike.app + self.font_size: Union[int, float] #: Bike font size preference + self.background_color: XABase.XAColor #: Bike background color preference + self.foreground_color: XABase.XAColor #: Bike foreground color preference + + @property + def name(self) -> str: + return self.xa_scel.name() + + @property + def frontmost(self) -> bool: + return self.xa_scel.frontmost() + + @property + def version(self) -> str: + return self.xa_scel.version() + + @property + def font_size(self) -> Union[int, float]: + return self.xa_scel.fontSize() + + @font_size.setter + def font_size(self, font_size: Union[int, float]): + self.set_property('fontSize', font_size) + + @property + def background_color(self) -> XABase.XAColor: + return XABase.XAColor(self.xa_scel.backgroundColor()) + + @background_color.setter + def background_color(self, background_color: XABase.XAColor): + self.set_property('backgroundColor', background_color.xa_elem) + + @property + def foreground_color(self) -> XABase.XAColor: + return XABase.XAColor(self.xa_scel.foregroundColor()) + + @foreground_color.setter + def foreground_color(self, foreground_color: XABase.XAColor): + self.set_property('foregroundColor', foreground_color.xa_elem) + +
[docs] def documents(self, filter: dict = None) -> Union['XABikeDocumentList', None]: + """Returns a list of documents, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned documents will have, or None + :type filter: Union[dict, None] + :return: The list of documents + :rtype: XABikeDocumentList + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Bike") + >>> print(app.documents()) + <<class 'PyXA.apps.Bike.XABikeDocumentList'>['Untitled', 'PyXA Notes.bike']> + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_scel.documents(), XABikeDocumentList, filter)
+ +
[docs] def make(self, specifier: str, properties: Union[dict, None] = None) -> XABase.XAObject: + """Creates a new element of the given specifier class without adding it to any list. + + Use :func:`XABase.XAList.push` to push the element onto a list. + + :param specifier: The classname of the object to create + :type specifier: str + :param properties: The properties to give the object + :type properties: dict + :return: A PyXA wrapped form of the object + :rtype: XABase.XAObject + + :Example 1: Add new rows to the current document + + >>> import PyXA + >>> app = PyXA.application("Bike") + >>> front_doc_rows = app.front_window.document.rows() + >>> + >>> row1 = app.make("row", {"name": "This is a new row!"}) + >>> row2 = app.make("row", {"name": "This is another new row!"}) + >>> row3 = app.make("row", {"name": "This is a third new row!"}) + >>> + >>> front_doc_rows.push(row1) # Add to the end of the document + >>> front_doc_rows.insert(row2, 0) # Insert at the beginning of the document + >>> front_doc_rows.insert(row3, 5) # Insert at the middle of the document + + .. versionadded:: 0.1,0 + """ + if properties is None: + properties = {} + + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + + if specifier == "window": + raise UnconstructableClassError("Windows cannot be created for Bike.app.") + elif specifier == "document": + return self._new_element(obj, XABikeDocument) + elif specifier == "row": + return self._new_element(obj, XABikeRow) + elif specifier == "attribute": + return self._new_element(obj, XABikeAttribute)
+ + + + +
[docs]class XABikeWindow(XABaseScriptable.XASBWindow): + """A window of Bike.app. + + .. versionadded:: 0.1.0 + """ + + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The title of the window + self.id: int #: The unique identifier of the window + self.index: int #: The index of the window, ordered front to back + self.bounds: Tuple[int, int, int, int] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.miniaturizable: bool #: Whether the window has a minimize button + self.miniaturized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window has a zoom button + self.zoomed: bool #: Whether the window is currently zoomed + self.document: XABikeDocument #: The document whose contents are currently displayed in the window + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property('index', index) + + @property + def bounds(self) -> Tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: Tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def miniaturizable(self) -> bool: + return self.xa_elem.miniaturizable() + + @property + def miniaturized(self) -> bool: + return self.xa_elem.miniaturized() + + @miniaturized.setter + def miniaturized(self, miniaturized: bool): + self.set_property('miniaturized', miniaturized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property('visible', visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property('zoomed', zoomed) + + @property + def document(self) -> 'XABikeDocument': + return self._new_element(self.xa_elem.document(), XABikeDocument) + + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ + + + +
[docs]class XABikeDocumentList(XABase.XAList, XACanOpenPath, XAClipboardCodable): + """A wrapper around lists of Bike documents that employs fast enumeration techniques. + + All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XABikeDocument, filter) + +
[docs] def name(self) -> List[str]: + """Gets the name of each document in the list. + + :return: A list of document names + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def modified(self) -> List[bool]: + """Gets the modified status of each document in the list. + + :return: A list of document modified status booleans + :rtype: List[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modified"))
+ +
[docs] def file(self) -> List[XABase.XAPath]: + """Gets the file path of each document in the list. + + :return: A list of document file paths + :rtype: List[XABase.XAPath] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("file") + return [XABase.XAPath(x) for x in ls]
+ +
[docs] def id(self) -> List[str]: + """Gets the ID of each document in the list. + + :return: A list of document IDs + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def url(self) -> List[XABase.XAURL]: + """Gets the URL of each document in the list. + + :return: A list of document urls + :rtype: List[XABase.XAURL] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("url") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def root_row(self) -> 'XABikeRowList': + """Gets the root row of each document in the list. + + :return: A list of document root rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("rootRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def entireContents(self) -> 'XABikeRowList': + """Gets the entire contents of each document in the list. + + :return: A list of document rows + :rtype: XABikeRowLists + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("entireContents") + ls = [row for contents in ls for row in contents] + return self._new_element(ls, XABikeRowList)
+ +
[docs] def focused_row(self) -> 'XABikeRowList': + """Gets the focused row of each document in the list. + + :return: A list of document focused rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("focusedRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def hoisted_row(self) -> 'XABikeRowList': + """Gets the hoisted row of each document in the list. + + :return: A list of document hoisted rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("hoistedRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def selected_text(self) -> List[str]: + """Gets the selected text of each document in the list. + + :return: A list of document selected texts + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("selectedText"))
+ +
[docs] def selection_row(self) -> 'XABikeRowList': + """Gets the selection row of each document in the list. + + :return: A list of document selection rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("selectionRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def selection_rows(self) -> 'XABikeRowList': + """Gets the selection rows of each document in the list. + + :return: A list of document selection rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("selectionRows") + ls = [row for contents in ls for row in contents] + return self._new_element(ls, XABikeRowList)
+ +
[docs] def by_name(self, name: str) -> Union['XABikeDocument', None]: + """Retrieves the document whose name matches the given name, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_modified(self, modified: bool) -> Union['XABikeDocument', None]: + """Retrieves the first document whose modified status matches the given boolean value, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("modified", modified)
+ +
[docs] def by_file(self, file: Union[str, XABase.XAPath]) -> Union['XABikeDocument', None]: + """Retrieves the document whose file path matches the given path, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(file, XABase.XAPath): + file = file.path + return self.by_property("file", file)
+ +
[docs] def by_id(self, id: str) -> Union['XABikeDocument', None]: + """Retrieves the document whose ID matches the given ID, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("id", id)
+ +
[docs] def by_url(self, url: Union[str, XABase.XAURL]) -> Union['XABikeDocument', None]: + """Retrieves the document whose URL matches the given URL, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(url, XABase.XAURL): + url = url.url + return self.by_property("url", url)
+ +
[docs] def by_root_row(self, root_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose root row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("rootRow", root_row.xa_elem)
+ +
[docs] def by_entire_contents(self, entire_contents: Union['XABikeRowList', List['XABikeRow']]) -> Union['XABikeDocument', None]: + """Retrieves the document whose entire contents matches the given list of rows, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(entire_contents, list): + entire_contents = [x.xa_elem for x in entire_contents] + return self.by_property("entireContents", entire_contents) + else: + return self.by_property("entireContents", entire_contents.xa_elem)
+ +
[docs] def by_focused_row(self, focused_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose focused row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("focusedRow", focused_row.xa_elem)
+ +
[docs] def by_hoisted_row(self, hoisted_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose hoisted row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("hoistedRow", hoisted_row.xa_elem)
+ +
[docs] def by_selected_text(self, selected_text: str) -> Union['XABikeDocument', None]: + """Retrieves the document whose selected text matches the given text, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("selectedText", selected_text)
+ +
[docs] def by_selection_row(self, selection_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose selection row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("selectionRow", selection_row.xa_elem)
+ +
[docs] def by_selection_rows(self, selection_rows: Union['XABikeRowList', List['XABikeRow']]) -> Union['XABikeDocument', None]: + """Retrieves the document whose selection rows match the given list of rows, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(selection_rows, list): + selection_rows = [x.xa_elem for x in selection_rows] + return self.by_property("selectionRows", selection_rows) + else: + return self.by_property("selectionRows", selection_rows.xa_elem)
+ +
[docs] def close(self, save: 'XACloseable.SaveOption' = None): + """Closes every document in the list. Leaves the last document open if it is the only document open in the application. + + :param save: Whether to save the documents before closing, defaults to YES + :type save: XACloseable.SaveOption, optional + + .. versionadded:: 0.1.0 + """ + if save is None: + save = 1852776480 + else: + save = save.value + + for document in self.xa_elem: + document.closeSaving_savingIn_(save, None)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XABikeDocument(XABase.XAObject, XACloseable): + """A document of Bike.app. + + .. versionadded:: 0.1.0 + """ + + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The name of the document + self.modified: bool #: Whether the document has been modified since it was last saved + self.file: XABase.XAPath #: The location of the document on disk, if it has one + self.id: str #: The unique and persistent identifier for the document + self.url: XABase.XAURL #: The Bike URL link for the document + self.root_row: XABikeRow #: The top 'root' row of the document, not visible in the outline editor + self.entire_contents: XABikeRowList #: All rows in the document + self.focused_row: XABikeRow #: The currently focused row + self.hoisted_row: XABikeRow #: The currently hoisted row + self.selected_text: str #: The currently selected text + self.selection_row: XABikeRow #: The row intersecting the selected text head + self.selection_rows: XABikeRowList #: All rows intersecting the selected text + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def modified(self) -> str: + return self.xa_elem.modified() + + @property + def file(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.file()) + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def url(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.url()) + + @property + def root_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.rootRow(), XABikeRow) + + @property + def entire_contents(self) -> 'XABikeRowList': + return self._new_element(self.xa_elem.entireContents(), XABikeRowList) + + @property + def focused_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.focusedRow(), XABikeRow) + + @focused_row.setter + def focused_row(self, focused_row: 'XABikeRow'): + self.set_property('focusedRow', focused_row.xa_elem) + + @property + def hoisted_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.hoisted_row(), XABikeRow) + + @hoisted_row.setter + def hoisted_row(self, hoisted_row: 'XABikeRow'): + self.set_property('hoistedRow', hoisted_row.xa_elem) + + @property + def selected_text(self) -> str: + return self.xa_elem.selectedText() + + @property + def selection_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.selectionRow(), XABikeRow) + + @property + def selection_rows(self) -> 'XABikeRowList': + return self._new_element(self.xa_elem.selectionRows(), XABikeRowList) + +
[docs] def save(self, path: Union[str, XABase.XAPath, None] = None, format: XABikeApplication.FileFormat = XABikeApplication.FileFormat.BIKE): + """Saves the document to the specified location, or at its existing location. + + :param path: The location to save the document in, defaults to None + :type path: Union[str, XABase.XAPath, None], optional + :param format: The format to save the document as, defaults to XABikeApplication.FileFormat.BIKE + :type format: XABikeApplication.FileFormat, optional + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Bike") + >>> doc = app.documents()[0] + >>> doc.save("/Users/exampleUser/Documents/Notes.opml", app.FileFormat.OPML) + + .. versionadded:: 0.1.0 + """ + if path is None: + path = self.xa_elem.file() + elif isinstance(path, str): + path = XABase.XAPath(path).xa_elem + self.xa_elem.saveIn_as_(path, format.value)
+ +
[docs] def rows(self, filter: dict = None) -> Union['XABikeRowList', None]: + """Returns a list of rows, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned rows will have, or None + :type filter: Union[dict, None] + :return: The list of rows + :rtype: XABikeRowList + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Bike") + >>> doc = app.front_window.document + >>> print(doc.rows()) + <<class 'PyXA.apps.Bike.XABikeRowList'>['Row 1', 'Row 2', 'Row 2.1']> + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.rows(), XABikeRowList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + + +
[docs]class XABikeRowList(XABase.XAList): + """A wrapper around a list of rows. + + All properties of row objects can be accessed via methods on the list, returning a list of each row's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XABikeRow, filter) + +
[docs] def id(self) -> List[str]: + """Gets the ID of each row in the list. + + :return: A list of row IDs + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def url(self) -> List[XABase.XAURL]: + """Gets the URL of each row in the list. + + :return: A list of row URLs + :rtype: List[XABase.XAURL] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("url") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def level(self) -> List[int]: + """Gets the level of each row in the list. + + :return: A list of row levels + :rtype: List[int] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("level"))
+ +
[docs] def contains_rows(self) -> List[bool]: + """Gets the contains rows status of each row in the list. + + :return: A list of row contains rows status booleans + :rtype: List[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("containsRows"))
+ +
[docs] def name(self) -> List[str]: + """Gets the name of each row in the list. + + :return: A list of row names + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def container(self) -> List[Union[XABikeDocument, 'XABikeRow']]: + """Gets the container of each row in the list. + + :return: A list of row containers + :rtype: List[Union[XABikeDocument, 'XABikeRow']] + + .. versionadded:: 0.1.0 + """ + return [x.container for x in self]
+ +
[docs] def container_row(self) -> 'XABikeRowList': + """Gets the container row of each row in the list. + + :return: A list of row container rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("containerRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def prev_sibling_row(self) -> 'XABikeRowList': + """Gets the previous sibling row of each row in the list. + + :return: A list of row previous sibling rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("prevSiblingRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def next_sibling_row(self) -> 'XABikeRowList': + """Gets the next sibling row of each row in the list. + + :return: A list of row next sibling rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("nextSiblingRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def entire_contents(self) -> 'XABikeRowList': + """Gets the all contained rows of each row in the list. + + :return: A list of contained rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("entireContents") + ls = [item for contents in ls for item in contents] + return self._new_element(ls, XABikeRowList)
+ +
[docs] def visible(self) -> List[bool]: + """Gets the visible status of each row in the list. + + :return: A list of row visible status booleans + :rtype: List[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("visible"))
+ +
[docs] def selected(self) -> List[bool]: + """Gets the selected status of each row in the list. + + :return: A list of row selected status booleans + :rtype: List[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("selected"))
+ +
[docs] def expanded(self) -> List[bool]: + """Gets the expanded status of each row in the list. + + :return: A list of row expanded status booleans + :rtype: List[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("expanded"))
+ +
[docs] def collapsed(self) -> List[bool]: + """Gets the collapsed status of each row in the list. + + :return: A list of row collapsed status booleans + :rtype: List[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("collapsed"))
+ +
[docs] def by_id(self, id: str) -> Union['XABikeRow', None]: + """Retrieves the row whose ID matches the given ID, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("id", id)
+ +
[docs] def by_url(self, url: Union[str, XABase.XAURL]) -> Union['XABikeRow', None]: + """Retrieves the row whose URL matches the given URL, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(url, XABase.XAURL): + url = url.url + return self.by_property("url", url)
+ +
[docs] def by_level(self, level: int) -> Union['XABikeRow', None]: + """Retrieves the first row whose level matches the given level, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("level", level)
+ +
[docs] def by_contains_rows(self, contains_rows: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose contains rows status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containsRows", contains_rows)
+ +
[docs] def by_name(self, name: str) -> Union['XABikeRow', None]: + """Retrieves the row whose name matches the given name, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_container(self, container: Union[XABikeDocument, 'XABikeRow']) -> Union['XABikeRow', None]: + """Retrieves the first row whose container matches the given container, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("container", container.xa_elem)
+ +
[docs] def by_container_row(self, container_row: 'XABikeRow') -> Union['XABikeRow', None]: + """Retrieves the first row whose container row matches the given row, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containerRow", container_row.xa_elem)
+ +
[docs] def by_prev_sibling_row(self, prev_sibling_row: 'XABikeRow') -> Union['XABikeRow', None]: + """Retrieves the row whose previous sibling row matches the given row, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("prevSiblingRow", prev_sibling_row.xa_elem)
+ +
[docs] def by_next_sibling_row(self, next_sibling_row: 'XABikeRow') -> Union['XABikeRow', None]: + """Retrieves the row whose next sibling row matches the given row, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("nextSiblingRow", next_sibling_row.xa_elem)
+ +
[docs] def by_container_document(self, container_document: XABikeDocument) -> Union['XABikeRow', None]: + """Retrieves the first row whose container document matches the given document, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containerDocument", container_document.xa_elem)
+ +
[docs] def by_entire_contents(self, entire_contents: Union['XABikeRowList', List['XABikeRow']]) -> Union['XABikeRow', None]: + """Retrieves the row whose entire contents matches the given list of rows, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(entire_contents, list): + entire_contents = [x.xa_elem for x in entire_contents] + return self.by_property("entireContents", entire_contents) + else: + return self.by_property("entireContents", entire_contents.xa_elem)
+ +
[docs] def by_visible(self, visible: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose visible status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("visible", visible)
+ +
[docs] def by_selected(self, selected: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose selected status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("selected", selected)
+ +
[docs] def by_expanded(self, expanded: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose expanded status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("expanded", expanded)
+ +
[docs] def by_collapsed(self, collapsed: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose collapsed status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("collapsed", collapsed)
+ +
[docs] def collapse(self, all: bool = False): + """Collapses all rows in the list, optionally collapsing all of the children as well. + + :param all: Whether to collapse all child rows, defaults to False + :type all: bool, optional + + .. versionadded:: 0.1.0 + """ + for row in self.xa_elem: + row.collapse_all_([row], all)
+ +
[docs] def expand(self, all: bool = False): + """Expands all rows in the list, optionally expanding all of the children as well. + + :param all: Whether to expand all child rows, defaults to False + :type all: bool, optional + + .. versionadded:: 0.1.0 + """ + for row in self.xa_elem: + row.expand_all_(row, all)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XABikeRow(XABase.XAObject, XADeletable): + """A row in an outline. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.id: str #: The unique and persistent identifier for the row + self.url: XABase.XAURL #: The Bike URL for the row combining the document ID with the item ID + self.level: int #: The indentation level for the row in the outline + self.contains_rows: bool #: True if the row contains other rows + self.name: str #: The plain text content of the row + self.container: Union[XABikeRow, XABikeDocument] #: Container of the row + self.container_row: XABikeRow #: The row that directly contains this row + self.prev_sibling_row: XABikeRow #: The previous row with the same container row as this row + self.next_sibling_row: XABikeRow #: The next row with the same container as this row + self.container_document: XABikeDocument #: The document that contains this row + self.entire_contents: XABikeRowList #: The list of all rows contained by this row + self.visible: bool #: True if this row is visible in the window (may require scrolling) + self.selected: bool #: True if this row is selected in the window + self.expanded: bool #: True if this row is expanded in the window + self.collapsed: bool #: True if this row is collapsed in the window + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def url(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.url()) + + @property + def level(self) -> int: + return self.xa_elem.level() + + @property + def contains_rows(self) -> bool: + return self.xa_elem.containsRows() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property('name', name) + + @property + def container(self) -> Union['XABikeRow', XABikeDocument]: + container = self.xa_elem.container() + if hasattr(container, "level"): + return self._new_element(container, XABikeRow) + else: + return self._new_element(container, XABikeDocument) + + @property + def container_row(self) -> Union['XABikeRow', None]: + row = self.xa_elem.containerRow() + if row is not None: + return self._new_element(row, XABikeRow) + + @property + def prev_sibling_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.prevSiblingRow(), XABikeRow) + + @property + def next_sibling_row(self) -> type: + return self._new_element(self.xa_elem.nextSiblingRow(), XABikeRow) + + @property + def container_document(self) -> XABikeDocument: + return self._new_element(self.xa_elem.containerDocument(), XABikeDocument) + + @property + def entire_contents(self) -> XABikeRowList: + return self._new_element(self.xa_elem.entire_contents(), XABikeRowList) + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @property + def selected(self) -> bool: + return self.xa_elem.selected() + + @property + def expanded(self) -> bool: + return self.xa_elem.expanded() + + @property + def collapsed(self) -> bool: + return self.xa_elem.collapsed() + +
[docs] def collapse(self, all: bool = False): + """Collapses the row and optionally all rows it contains. + + :param recursive: Whether to also collapse all rows contained by this row, defaults to False + :type recursive: bool, optional + + .. versionadded:: 0.1.0 + """ + self.xa_elem.collapseAll_(all)
+ +
[docs] def expand(self, all: bool = False): + """Expanded the row and optionally all rows it contains. + + :param recursive: Whether to also expand all rows contained by this row, defaults to False + :type recursive: bool, optional + + .. versionadded:: 0.1.0 + """ + self.xa_elem.expandAll_(all)
+ +
[docs] def move_to(self, location: 'XABikeRow'): + """Makes the row a child of the specified row. + + :param location: The row to move this row to. + :type location: XABikeRow + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Bike") + >>> doc = app.documents()[0] + >>> row1 = doc.rows()[0] + >>> row2 = doc.rows()[5] + >>> row1.move_to(row2) + + .. versionadded:: 0.1.0 + """ + self.xa_elem.moveTo_(location.xa_elem)
+ + # def duplicate(self, location: Union[XABikeDocument, 'XABikeRow', None] = None, properties: Union[dict, None] = None): + # """Duplicates the row either in-place or at a specified location. + + # :param location: The document or row to create a duplicate of this row in, defaults to None (duplicate in-place) + # :type location: Union[XABikeDocument, XABikeRow, None], optional + # :param properties: Properties to set in the new copy right away, defaults to None (no properties changed) + # :type properties: Union[dict, None], options + # """ + # if properties is None: + # properties = {} + + # if location is None: + # self.xa_elem.duplicateTo_withProperties_(self.xa_elem.containerDocument(), properties) + # else: + # self.xa_elem.duplicateTo_withProperties_(location.xa_elem, properties) + +
[docs] def rows(self, filter: dict = None) -> Union['XABikeRowList', None]: + """Returns a list of rows, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned rows will have, or None + :type filter: Union[dict, None] + :return: The list of rows + :rtype: XABikeRowList + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Bike") + >>> doc = app.front_window.document + >>> row = doc.rows()[4] + >>> print(row.rows()) + <<class 'PyXA.apps.Bike.XABikeRowList'>['Watch intro movie', 'Glance through features list', 'https://www.hogbaysoftware.com/bike']> + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.rows(), XABikeRowList, filter)
+ +
[docs] def attributes(self, filter: dict = None) -> Union['XABikeAttributeList', None]: + """Returns a list of attributes, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned attributes will have, or None + :type filter: Union[dict, None] + :return: The list of attributes + :rtype: XABikeAttributeList + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.attributes(), XABikeAttributeList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + +
[docs]class XABikeAttributeList(XABase.XAList): + """A wrapper around a list of attributes. + + All properties of attribute objects can be accessed via methods on the list, returning a list of each attribute's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XABikeAttribute, filter) + +
[docs] def name(self) -> List[str]: + """Gets the name of each attribute in the list. + + :return: A list of attribute names + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def value(self) -> List[str]: + """Gets the value of each attribute in the list. + + :return: A list of attribute values + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ +
[docs] def container_row(self) -> XABikeRowList: + """Gets the container row of each attribute in the list. + + :return: A list of attribute container rows + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("containerRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def by_name(self, name: str) -> Union['XABikeAttribute', None]: + """Retrieves the attribute whose name matches the given name, if one exists. + + :return: The desired attribute, if it is found + :rtype: Union[XABikeAttribute, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_value(self, value: str) -> Union['XABikeAttribute', None]: + """Retrieves the first attribute whose value matches the given value, if one exists. + + :return: The desired attribute, if it is found + :rtype: Union[XABikeAttribute, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("value", value)
+ +
[docs] def by_container_row(self, container_row: XABikeRow) -> Union['XABikeAttribute', None]: + """Retrieves the first attribute whose container row matches the given row, if one exists. + + :return: The desired attribute, if it is found + :rtype: Union[XABikeAttribute, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containerRow", container_row.xa_elem)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XABikeAttribute(XABase.XAObject): + """An attribute in a row. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The name of the attribute + self.value: str #: The value of the attribute + self.container_row: XABikeRow #: The row that contains this attribute + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def value(self) -> str: + return self.xa_elem.value() + + @value.setter + def value(self, value: str): + self.set_property('value', value) + + @property + def container_row(self) -> XABikeRow: + return self._new_element(self.xa_elem.containerRow(), XABikeRow) + + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Bike.html b/docs/_modules/PyXA/apps/Bike.html new file mode 100644 index 0000000..55ec502 --- /dev/null +++ b/docs/_modules/PyXA/apps/Bike.html @@ -0,0 +1,1388 @@ + + + + + + PyXA.apps.Bike — PyXA 0.1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Bike

+""".. versionadded:: 0.1.0
+
+Control Bike using JXA-like syntax.
+"""
+
+from enum import Enum
+from typing import Union
+
+import AppKit
+
+from PyXA import XABase
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath, XAClipboardCodable, XACloseable, XADeletable, XAPrintable
+from ..XAErrors import UnconstructableClassError
+
+
[docs]class XABikeApplication(XABaseScriptable.XASBApplication, XACanOpenPath): + """A class for managing and interacting with Bike.app. + + .. versionadded:: 0.1.0 + """ +
[docs] class FileFormat(Enum): + BIKE = XABase.OSType("BKff") + OPML = XABase.OSType("OPml") + PLAINTEXT = XABase.OSType("PTfm")
+ + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XABikeWindow + + self.name: str #: The name of the application + self.frontmost: bool #: Whether Bike is the frontmost application + self.version: str #: The version of Bike.app + self.font_size: Union[int, float] #: Bike font size preference + self.background_color: XABase.XAColor #: Bike background color preference + self.foreground_color: XABase.XAColor #: Bike foreground color preference + + @property + def name(self) -> str: + return self.xa_scel.name() + + @property + def frontmost(self) -> bool: + return self.xa_scel.frontmost() + + @property + def version(self) -> str: + return self.xa_scel.version() + + @property + def font_size(self) -> Union[int, float]: + return self.xa_scel.fontSize() + + @font_size.setter + def font_size(self, font_size: Union[int, float]): + self.set_property('fontSize', font_size) + + @property + def background_color(self) -> XABase.XAColor: + return XABase.XAColor(self.xa_scel.backgroundColor()) + + @background_color.setter + def background_color(self, background_color: XABase.XAColor): + self.set_property('backgroundColor', background_color.xa_elem) + + @property + def foreground_color(self) -> XABase.XAColor: + return XABase.XAColor(self.xa_scel.foregroundColor()) + + @foreground_color.setter + def foreground_color(self, foreground_color: XABase.XAColor): + self.set_property('foregroundColor', foreground_color.xa_elem) + +
[docs] def documents(self, filter: dict = None) -> Union['XABikeDocumentList', None]: + """Returns a list of documents, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned documents will have, or None + :type filter: Union[dict, None] + :return: The list of documents + :rtype: XABikeDocumentList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Bike") + >>> print(app.documents()) + <<class 'PyXA.apps.Bike.XABikeDocumentList'>['Untitled', 'PyXA Notes.bike']> + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_scel.documents(), XABikeDocumentList, filter)
+ +
[docs] def make(self, specifier: str, properties: Union[dict, None] = None) -> XABase.XAObject: + """Creates a new element of the given specifier class without adding it to any list. + + Use :func:`XABase.XAList.push` to push the element onto a list. + + :param specifier: The classname of the object to create + :type specifier: str + :param properties: The properties to give the object + :type properties: dict + :return: A PyXA wrapped form of the object + :rtype: XABase.XAObject + + :Example 1: Add new rows to the current document + + >>> import PyXA + >>> app = PyXA.Application("Bike") + >>> front_doc_rows = app.front_window.document.rows() + >>> + >>> row1 = app.make("row", {"name": "This is a new row!"}) + >>> row2 = app.make("row", {"name": "This is another new row!"}) + >>> row3 = app.make("row", {"name": "This is a third new row!"}) + >>> + >>> front_doc_rows.push(row1) # Add to the end of the document + >>> front_doc_rows.insert(row2, 0) # Insert at the beginning of the document + >>> front_doc_rows.insert(row3, 5) # Insert at the middle of the document + + .. versionadded:: 0.1,0 + """ + if properties is None: + properties = {} + + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + + if specifier == "window": + raise UnconstructableClassError("Windows cannot be created for Bike.app.") + elif specifier == "document": + return self._new_element(obj, XABikeDocument) + elif specifier == "row": + return self._new_element(obj, XABikeRow) + elif specifier == "attribute": + return self._new_element(obj, XABikeAttribute)
+ + + + +
[docs]class XABikeWindow(XABaseScriptable.XASBWindow): + """A window of Bike.app. + + .. versionadded:: 0.1.0 + """ + + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The title of the window + self.id: int #: The unique identifier of the window + self.index: int #: The index of the window, ordered front to back + self.bounds: tuple[int, int, int, int] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.miniaturizable: bool #: Whether the window has a minimize button + self.miniaturized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window has a zoom button + self.zoomed: bool #: Whether the window is currently zoomed + self.document: XABikeDocument #: The document whose contents are currently displayed in the window + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property('index', index) + + @property + def bounds(self) -> tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def miniaturizable(self) -> bool: + return self.xa_elem.miniaturizable() + + @property + def miniaturized(self) -> bool: + return self.xa_elem.miniaturized() + + @miniaturized.setter + def miniaturized(self, miniaturized: bool): + self.set_property('miniaturized', miniaturized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property('visible', visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property('zoomed', zoomed) + + @property + def document(self) -> 'XABikeDocument': + return self._new_element(self.xa_elem.document(), XABikeDocument) + + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ + + + +
[docs]class XABikeDocumentList(XABase.XAList, XACanOpenPath, XAClipboardCodable): + """A wrapper around lists of Bike documents that employs fast enumeration techniques. + + All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XABikeDocument, filter) + +
[docs] def name(self) -> list[str]: + """Gets the name of each document in the list. + + :return: A list of document names + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def modified(self) -> list[bool]: + """Gets the modified status of each document in the list. + + :return: A list of document modified status booleans + :rtype: list[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modified"))
+ +
[docs] def file(self) -> list[XABase.XAPath]: + """Gets the file path of each document in the list. + + :return: A list of document file paths + :rtype: list[XABase.XAPath] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("file") + return [XABase.XAPath(x) for x in ls]
+ +
[docs] def id(self) -> list[str]: + """Gets the ID of each document in the list. + + :return: A list of document IDs + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def url(self) -> list[XABase.XAURL]: + """Gets the URL of each document in the list. + + :return: A list of document urls + :rtype: list[XABase.XAURL] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("url") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def root_row(self) -> 'XABikeRowList': + """Gets the root row of each document in the list. + + :return: A list of document root rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("rootRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def entireContents(self) -> 'XABikeRowList': + """Gets the entire contents of each document in the list. + + :return: A list of document rows + :rtype: XABikeRowLists + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("entireContents") + ls = [row for contents in ls for row in contents] + return self._new_element(ls, XABikeRowList)
+ +
[docs] def focused_row(self) -> 'XABikeRowList': + """Gets the focused row of each document in the list. + + :return: A list of document focused rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("focusedRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def hoisted_row(self) -> 'XABikeRowList': + """Gets the hoisted row of each document in the list. + + :return: A list of document hoisted rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("hoistedRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def selected_text(self) -> list[str]: + """Gets the selected text of each document in the list. + + :return: A list of document selected texts + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("selectedText"))
+ +
[docs] def selection_row(self) -> 'XABikeRowList': + """Gets the selection row of each document in the list. + + :return: A list of document selection rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("selectionRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def selection_rows(self) -> 'XABikeRowList': + """Gets the selection rows of each document in the list. + + :return: A list of document selection rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("selectionRows") + ls = [row for contents in ls for row in contents] + return self._new_element(ls, XABikeRowList)
+ +
[docs] def by_name(self, name: str) -> Union['XABikeDocument', None]: + """Retrieves the document whose name matches the given name, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_modified(self, modified: bool) -> Union['XABikeDocument', None]: + """Retrieves the first document whose modified status matches the given boolean value, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("modified", modified)
+ +
[docs] def by_file(self, file: Union[str, XABase.XAPath]) -> Union['XABikeDocument', None]: + """Retrieves the document whose file path matches the given path, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(file, XABase.XAPath): + file = file.path + return self.by_property("file", file)
+ +
[docs] def by_id(self, id: str) -> Union['XABikeDocument', None]: + """Retrieves the document whose ID matches the given ID, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("id", id)
+ +
[docs] def by_url(self, url: Union[str, XABase.XAURL]) -> Union['XABikeDocument', None]: + """Retrieves the document whose URL matches the given URL, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(url, XABase.XAURL): + url = url.url + return self.by_property("url", url)
+ +
[docs] def by_root_row(self, root_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose root row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("rootRow", root_row.xa_elem)
+ +
[docs] def by_entire_contents(self, entire_contents: Union['XABikeRowList', list['XABikeRow']]) -> Union['XABikeDocument', None]: + """Retrieves the document whose entire contents matches the given list of rows, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(entire_contents, list): + entire_contents = [x.xa_elem for x in entire_contents] + return self.by_property("entireContents", entire_contents) + else: + return self.by_property("entireContents", entire_contents.xa_elem)
+ +
[docs] def by_focused_row(self, focused_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose focused row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("focusedRow", focused_row.xa_elem)
+ +
[docs] def by_hoisted_row(self, hoisted_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose hoisted row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("hoistedRow", hoisted_row.xa_elem)
+ +
[docs] def by_selected_text(self, selected_text: str) -> Union['XABikeDocument', None]: + """Retrieves the document whose selected text matches the given text, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("selectedText", selected_text)
+ +
[docs] def by_selection_row(self, selection_row: 'XABikeRow') -> Union['XABikeDocument', None]: + """Retrieves the document whose selection row matches the given row, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("selectionRow", selection_row.xa_elem)
+ +
[docs] def by_selection_rows(self, selection_rows: Union['XABikeRowList', list['XABikeRow']]) -> Union['XABikeDocument', None]: + """Retrieves the document whose selection rows match the given list of rows, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XABikeDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(selection_rows, list): + selection_rows = [x.xa_elem for x in selection_rows] + return self.by_property("selectionRows", selection_rows) + else: + return self.by_property("selectionRows", selection_rows.xa_elem)
+ +
[docs] def close(self, save: 'XACloseable.SaveOption' = None): + """Closes every document in the list. Leaves the last document open if it is the only document open in the application. + + :param save: Whether to save the documents before closing, defaults to YES + :type save: XACloseable.SaveOption, optional + + .. versionadded:: 0.1.0 + """ + if save is None: + save = 1852776480 + else: + save = save.value + + for document in self.xa_elem: + document.closeSaving_savingIn_(save, None)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XABikeDocument(XABase.XAObject, XACloseable): + """A document of Bike.app. + + .. versionadded:: 0.1.0 + """ + + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The name of the document + self.modified: bool #: Whether the document has been modified since it was last saved + self.file: XABase.XAPath #: The location of the document on disk, if it has one + self.id: str #: The unique and persistent identifier for the document + self.url: XABase.XAURL #: The Bike URL link for the document + self.root_row: XABikeRow #: The top 'root' row of the document, not visible in the outline editor + self.entire_contents: XABikeRowList #: All rows in the document + self.focused_row: XABikeRow #: The currently focused row + self.hoisted_row: XABikeRow #: The currently hoisted row + self.selected_text: str #: The currently selected text + self.selection_row: XABikeRow #: The row intersecting the selected text head + self.selection_rows: XABikeRowList #: All rows intersecting the selected text + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def modified(self) -> str: + return self.xa_elem.modified() + + @property + def file(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.file()) + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def url(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.url()) + + @property + def root_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.rootRow(), XABikeRow) + + @property + def entire_contents(self) -> 'XABikeRowList': + return self._new_element(self.xa_elem.entireContents(), XABikeRowList) + + @property + def focused_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.focusedRow(), XABikeRow) + + @focused_row.setter + def focused_row(self, focused_row: 'XABikeRow'): + self.set_property('focusedRow', focused_row.xa_elem) + + @property + def hoisted_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.hoisted_row(), XABikeRow) + + @hoisted_row.setter + def hoisted_row(self, hoisted_row: 'XABikeRow'): + self.set_property('hoistedRow', hoisted_row.xa_elem) + + @property + def selected_text(self) -> str: + return self.xa_elem.selectedText() + + @property + def selection_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.selectionRow(), XABikeRow) + + @property + def selection_rows(self) -> 'XABikeRowList': + return self._new_element(self.xa_elem.selectionRows(), XABikeRowList) + +
[docs] def save(self, path: Union[str, XABase.XAPath, None] = None, format: XABikeApplication.FileFormat = XABikeApplication.FileFormat.BIKE): + """Saves the document to the specified location, or at its existing location. + + :param path: The location to save the document in, defaults to None + :type path: Union[str, XABase.XAPath, None], optional + :param format: The format to save the document as, defaults to XABikeApplication.FileFormat.BIKE + :type format: XABikeApplication.FileFormat, optional + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Bike") + >>> doc = app.documents()[0] + >>> doc.save("/Users/exampleUser/Documents/Notes.opml", app.FileFormat.OPML) + + .. versionadded:: 0.1.0 + """ + if path is None: + path = self.xa_elem.file() + elif isinstance(path, str): + path = XABase.XAPath(path).xa_elem + self.xa_elem.saveIn_as_(path, format.value)
+ +
[docs] def rows(self, filter: dict = None) -> Union['XABikeRowList', None]: + """Returns a list of rows, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned rows will have, or None + :type filter: Union[dict, None] + :return: The list of rows + :rtype: XABikeRowList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Bike") + >>> doc = app.front_window.document + >>> print(doc.rows()) + <<class 'PyXA.apps.Bike.XABikeRowList'>['Row 1', 'Row 2', 'Row 2.1']> + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.rows(), XABikeRowList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + + +
[docs]class XABikeRowList(XABase.XAList): + """A wrapper around a list of rows. + + All properties of row objects can be accessed via methods on the list, returning a list of each row's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XABikeRow, filter) + +
[docs] def id(self) -> list[str]: + """Gets the ID of each row in the list. + + :return: A list of row IDs + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def url(self) -> list[XABase.XAURL]: + """Gets the URL of each row in the list. + + :return: A list of row URLs + :rtype: list[XABase.XAURL] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("url") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def level(self) -> list[int]: + """Gets the level of each row in the list. + + :return: A list of row levels + :rtype: list[int] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("level"))
+ +
[docs] def contains_rows(self) -> list[bool]: + """Gets the contains rows status of each row in the list. + + :return: A list of row contains rows status booleans + :rtype: list[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("containsRows"))
+ +
[docs] def name(self) -> list[str]: + """Gets the name of each row in the list. + + :return: A list of row names + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def container(self) -> list[Union[XABikeDocument, 'XABikeRow']]: + """Gets the container of each row in the list. + + :return: A list of row containers + :rtype: list[Union[XABikeDocument, 'XABikeRow']] + + .. versionadded:: 0.1.0 + """ + return [x.container for x in self]
+ +
[docs] def container_row(self) -> 'XABikeRowList': + """Gets the container row of each row in the list. + + :return: A list of row container rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("containerRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def prev_sibling_row(self) -> 'XABikeRowList': + """Gets the previous sibling row of each row in the list. + + :return: A list of row previous sibling rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("prevSiblingRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def next_sibling_row(self) -> 'XABikeRowList': + """Gets the next sibling row of each row in the list. + + :return: A list of row next sibling rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("nextSiblingRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def entire_contents(self) -> 'XABikeRowList': + """Gets the all contained rows of each row in the list. + + :return: A list of contained rows + :rtype: XABikeRowList + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("entireContents") + ls = [item for contents in ls for item in contents] + return self._new_element(ls, XABikeRowList)
+ +
[docs] def visible(self) -> list[bool]: + """Gets the visible status of each row in the list. + + :return: A list of row visible status booleans + :rtype: list[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("visible"))
+ +
[docs] def selected(self) -> list[bool]: + """Gets the selected status of each row in the list. + + :return: A list of row selected status booleans + :rtype: list[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("selected"))
+ +
[docs] def expanded(self) -> list[bool]: + """Gets the expanded status of each row in the list. + + :return: A list of row expanded status booleans + :rtype: list[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("expanded"))
+ +
[docs] def collapsed(self) -> list[bool]: + """Gets the collapsed status of each row in the list. + + :return: A list of row collapsed status booleans + :rtype: list[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("collapsed"))
+ +
[docs] def by_id(self, id: str) -> Union['XABikeRow', None]: + """Retrieves the row whose ID matches the given ID, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("id", id)
+ +
[docs] def by_url(self, url: Union[str, XABase.XAURL]) -> Union['XABikeRow', None]: + """Retrieves the row whose URL matches the given URL, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(url, XABase.XAURL): + url = url.url + return self.by_property("url", url)
+ +
[docs] def by_level(self, level: int) -> Union['XABikeRow', None]: + """Retrieves the first row whose level matches the given level, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("level", level)
+ +
[docs] def by_contains_rows(self, contains_rows: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose contains rows status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containsRows", contains_rows)
+ +
[docs] def by_name(self, name: str) -> Union['XABikeRow', None]: + """Retrieves the row whose name matches the given name, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_container(self, container: Union[XABikeDocument, 'XABikeRow']) -> Union['XABikeRow', None]: + """Retrieves the first row whose container matches the given container, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("container", container.xa_elem)
+ +
[docs] def by_container_row(self, container_row: 'XABikeRow') -> Union['XABikeRow', None]: + """Retrieves the first row whose container row matches the given row, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containerRow", container_row.xa_elem)
+ +
[docs] def by_prev_sibling_row(self, prev_sibling_row: 'XABikeRow') -> Union['XABikeRow', None]: + """Retrieves the row whose previous sibling row matches the given row, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("prevSiblingRow", prev_sibling_row.xa_elem)
+ +
[docs] def by_next_sibling_row(self, next_sibling_row: 'XABikeRow') -> Union['XABikeRow', None]: + """Retrieves the row whose next sibling row matches the given row, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("nextSiblingRow", next_sibling_row.xa_elem)
+ +
[docs] def by_container_document(self, container_document: XABikeDocument) -> Union['XABikeRow', None]: + """Retrieves the first row whose container document matches the given document, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containerDocument", container_document.xa_elem)
+ +
[docs] def by_entire_contents(self, entire_contents: Union['XABikeRowList', list['XABikeRow']]) -> Union['XABikeRow', None]: + """Retrieves the row whose entire contents matches the given list of rows, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(entire_contents, list): + entire_contents = [x.xa_elem for x in entire_contents] + return self.by_property("entireContents", entire_contents) + else: + return self.by_property("entireContents", entire_contents.xa_elem)
+ +
[docs] def by_visible(self, visible: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose visible status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("visible", visible)
+ +
[docs] def by_selected(self, selected: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose selected status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("selected", selected)
+ +
[docs] def by_expanded(self, expanded: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose expanded status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("expanded", expanded)
+ +
[docs] def by_collapsed(self, collapsed: bool) -> Union['XABikeRow', None]: + """Retrieves the first row whose collapsed status matches the given boolean value, if one exists. + + :return: The desired row, if it is found + :rtype: Union[XABikeRow, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("collapsed", collapsed)
+ +
[docs] def collapse(self, all: bool = False): + """Collapses all rows in the list, optionally collapsing all of the children as well. + + :param all: Whether to collapse all child rows, defaults to False + :type all: bool, optional + + .. versionadded:: 0.1.0 + """ + for row in self.xa_elem: + row.collapse_all_([row], all)
+ +
[docs] def expand(self, all: bool = False): + """Expands all rows in the list, optionally expanding all of the children as well. + + :param all: Whether to expand all child rows, defaults to False + :type all: bool, optional + + .. versionadded:: 0.1.0 + """ + for row in self.xa_elem: + row.expand_all_(row, all)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XABikeRow(XABase.XAObject, XADeletable): + """A row in an outline. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.id: str #: The unique and persistent identifier for the row + self.url: XABase.XAURL #: The Bike URL for the row combining the document ID with the item ID + self.level: int #: The indentation level for the row in the outline + self.contains_rows: bool #: True if the row contains other rows + self.name: str #: The plain text content of the row + self.container: Union[XABikeRow, XABikeDocument] #: Container of the row + self.container_row: XABikeRow #: The row that directly contains this row + self.prev_sibling_row: XABikeRow #: The previous row with the same container row as this row + self.next_sibling_row: XABikeRow #: The next row with the same container as this row + self.container_document: XABikeDocument #: The document that contains this row + self.entire_contents: XABikeRowList #: The list of all rows contained by this row + self.visible: bool #: True if this row is visible in the window (may require scrolling) + self.selected: bool #: True if this row is selected in the window + self.expanded: bool #: True if this row is expanded in the window + self.collapsed: bool #: True if this row is collapsed in the window + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def url(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.url()) + + @property + def level(self) -> int: + return self.xa_elem.level() + + @property + def contains_rows(self) -> bool: + return self.xa_elem.containsRows() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property('name', name) + + @property + def container(self) -> Union['XABikeRow', XABikeDocument]: + container = self.xa_elem.container() + if hasattr(container, "level"): + return self._new_element(container, XABikeRow) + else: + return self._new_element(container, XABikeDocument) + + @property + def container_row(self) -> Union['XABikeRow', None]: + row = self.xa_elem.containerRow() + if row is not None: + return self._new_element(row, XABikeRow) + + @property + def prev_sibling_row(self) -> 'XABikeRow': + return self._new_element(self.xa_elem.prevSiblingRow(), XABikeRow) + + @property + def next_sibling_row(self) -> type: + return self._new_element(self.xa_elem.nextSiblingRow(), XABikeRow) + + @property + def container_document(self) -> XABikeDocument: + return self._new_element(self.xa_elem.containerDocument(), XABikeDocument) + + @property + def entire_contents(self) -> XABikeRowList: + return self._new_element(self.xa_elem.entire_contents(), XABikeRowList) + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @property + def selected(self) -> bool: + return self.xa_elem.selected() + + @property + def expanded(self) -> bool: + return self.xa_elem.expanded() + + @property + def collapsed(self) -> bool: + return self.xa_elem.collapsed() + +
[docs] def collapse(self, all: bool = False): + """Collapses the row and optionally all rows it contains. + + :param recursive: Whether to also collapse all rows contained by this row, defaults to False + :type recursive: bool, optional + + .. versionadded:: 0.1.0 + """ + self.xa_elem.collapseAll_(all)
+ +
[docs] def expand(self, all: bool = False): + """Expanded the row and optionally all rows it contains. + + :param recursive: Whether to also expand all rows contained by this row, defaults to False + :type recursive: bool, optional + + .. versionadded:: 0.1.0 + """ + self.xa_elem.expandAll_(all)
+ +
[docs] def move_to(self, location: 'XABikeRow'): + """Makes the row a child of the specified row. + + :param location: The row to move this row to. + :type location: XABikeRow + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Bike") + >>> doc = app.documents()[0] + >>> row1 = doc.rows()[0] + >>> row2 = doc.rows()[5] + >>> row1.move_to(row2) + + .. versionadded:: 0.1.0 + """ + self.xa_elem.moveTo_(location.xa_elem)
+ + # def duplicate(self, location: Union[XABikeDocument, 'XABikeRow', None] = None, properties: Union[dict, None] = None): + # """Duplicates the row either in-place or at a specified location. + + # :param location: The document or row to create a duplicate of this row in, defaults to None (duplicate in-place) + # :type location: Union[XABikeDocument, XABikeRow, None], optional + # :param properties: Properties to set in the new copy right away, defaults to None (no properties changed) + # :type properties: Union[dict, None], options + # """ + # if properties is None: + # properties = {} + + # if location is None: + # self.xa_elem.duplicateTo_withProperties_(self.xa_elem.containerDocument(), properties) + # else: + # self.xa_elem.duplicateTo_withProperties_(location.xa_elem, properties) + +
[docs] def rows(self, filter: dict = None) -> Union['XABikeRowList', None]: + """Returns a list of rows, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned rows will have, or None + :type filter: Union[dict, None] + :return: The list of rows + :rtype: XABikeRowList + + :Example: + + >>> import PyXA + >>> app = PyXA.Application("Bike") + >>> doc = app.front_window.document + >>> row = doc.rows()[4] + >>> print(row.rows()) + <<class 'PyXA.apps.Bike.XABikeRowList'>['Watch intro movie', 'Glance through features list', 'https://www.hogbaysoftware.com/bike']> + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.rows(), XABikeRowList, filter)
+ +
[docs] def attributes(self, filter: dict = None) -> Union['XABikeAttributeList', None]: + """Returns a list of attributes, as PyXA-wrapped objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned attributes will have, or None + :type filter: Union[dict, None] + :return: The list of attributes + :rtype: XABikeAttributeList + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_elem.attributes(), XABikeAttributeList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + +
[docs]class XABikeAttributeList(XABase.XAList): + """A wrapper around a list of attributes. + + All properties of attribute objects can be accessed via methods on the list, returning a list of each attribute's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XABikeAttribute, filter) + +
[docs] def name(self) -> list[str]: + """Gets the name of each attribute in the list. + + :return: A list of attribute names + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def value(self) -> list[str]: + """Gets the value of each attribute in the list. + + :return: A list of attribute values + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ +
[docs] def container_row(self) -> XABikeRowList: + """Gets the container row of each attribute in the list. + + :return: A list of attribute container rows + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("containerRow") + return self._new_element(ls, XABikeRowList)
+ +
[docs] def by_name(self, name: str) -> Union['XABikeAttribute', None]: + """Retrieves the attribute whose name matches the given name, if one exists. + + :return: The desired attribute, if it is found + :rtype: Union[XABikeAttribute, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_value(self, value: str) -> Union['XABikeAttribute', None]: + """Retrieves the first attribute whose value matches the given value, if one exists. + + :return: The desired attribute, if it is found + :rtype: Union[XABikeAttribute, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("value", value)
+ +
[docs] def by_container_row(self, container_row: XABikeRow) -> Union['XABikeAttribute', None]: + """Retrieves the first attribute whose container row matches the given row, if one exists. + + :return: The desired attribute, if it is found + :rtype: Union[XABikeAttribute, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("containerRow", container_row.xa_elem)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XABikeAttribute(XABase.XAObject): + """An attribute in a row. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The name of the attribute + self.value: str #: The value of the attribute + self.container_row: XABikeRow #: The row that contains this attribute + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def value(self) -> str: + return self.xa_elem.value() + + @value.setter + def value(self, value: str): + self.set_property('value', value) + + @property + def container_row(self) -> XABikeRow: + return self._new_element(self.xa_elem.containerRow(), XABikeRow) + + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Calculator 2.html b/docs/_modules/PyXA/apps/Calculator 2.html new file mode 100644 index 0000000..d00ae27 --- /dev/null +++ b/docs/_modules/PyXA/apps/Calculator 2.html @@ -0,0 +1,287 @@ + + + + + + PyXA.apps.Calculator — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Calculator

+""".. versionadded:: 0.0.2
+
+Control the macOS Calculator application using JXA-like syntax.
+"""
+
+from PyXA import XABase
+
+
[docs]class XACalculatorApplication(XABase.XAApplication): + """A class for managing and interacting with Calculator.app. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + self.xa_btns = None + + def __load_buttons(self): + """Loads references to primary buttons of the calculator. + + .. versionadded:: 0.0.2 + """ + buttons = self.front_window.groups()[-1].buttons() + self.xa_btns = { + "0": buttons[4], + "1": buttons[5], + "2": buttons[0], + "3": buttons[2], + "4": buttons[10], + "5": buttons[7], + "6": buttons[11], + "7": buttons[15], + "8": buttons[14], + "9": buttons[3], + ".": buttons[9], + "+": buttons[12], + "-": buttons[6], + "*": buttons[8], + "/": buttons[17], + "%": buttons[13], + "~": buttons[18], + "=": buttons[1], + "c": buttons[16], + } + + ## Menu Bar + # Calculator Menu +
[docs] def open_about_panel(self): + """Opens the "About Calculator" panel. Mimics clicking File->About Calculator. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[1].menus()[0].menu_items()[0].press()
+ + # File Menu +
[docs] def save_tape(self): + """Opens the "save tape" dialog. Mimics clicking File->Save Tape As... + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[2].menus()[0].menu_items()[2].press()
+ +
[docs] def open_page_setup(self): + """Opens the page setup dialog. Mimics clicking File->Page Setup... + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[2].menus()[0].menu_items()[4].press()
+ +
[docs] def print_tape(self): + """Opens the print tape dialog. Mimics clicking File->Print Tape... + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[2].menus()[0].menu_items()[5].press()
+ + # Edit Menu +
[docs] def copy_value(self): + """Copies the current value of the calculator result. Mimics clicking Edit->Copy. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[3].menus()[0].menu_items()[4].press()
+ + # View Menu +
[docs] def show_basic_calculator(self): + """Switches the view to the basic calculator. Mimics clicking View->Basic. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[4].menus()[0].menu_items()[0].press()
+ +
[docs] def show_scientific_calculator(self): + """Switches the view to the scientific calculator. Mimics clicking View->Scientific. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[4].menus()[0].menu_items()[1].press()
+ +
[docs] def show_programmer_calculator(self): + """Switches the view to the programmer calculator. Mimics clicking View->Programmer. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[4].menus()[0].menu_items()[2].press()
+ +
[docs] def toggle_thousands_separators(self): + """Toggles whether comma separators are shown at thousand intervals. Mimics clicking View->Show Thousands Separators. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[4].menus()[0].menu_items()[4].press()
+ +
[docs] def toggle_RPN_mode(self): + """Toggles Reverse Polish Notation. Mimics clicking View->RPN Mode. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[4].menus()[0].menu_items()[6].press()
+ + # Window Menu +
[docs] def show_paper_tape(self): + """Opens the paper tape window. Mimics clicking Window->Show Paper Tape. + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[7].menus()[0].menu_items()[6].press()
+ + # Help Menu +
[docs] def show_help(self): + """Opens the Calculator help window. Mimics clicking Help->Calculator Help.add() + + .. versionadded:: 0.0.2 + """ + self.menu_bars()[0].menu_bar_items()[8].menus()[0].menu_items()[1].press()
+ + # Actions +
[docs] def clear_value(self): + """Clears the current calculator output. Mimics clicking the "C" (clear) button. + + .. versionadded:: 0.0.2 + """ + if self.xa_btns is None: + self.__load_buttons() + self.xa_btns["c"].press()
+ +
[docs] def input(self, sequence: str): + """Inputs a sequence of numbers and operations into the calculator by mimicking button clicks. + + This method does not obtain the result of the input. For that, use :func:`current_value`. + + The sequence should be a continuous string (no spaces). The valid characters are numbers `0-9`, `+`, `-`, `*`, `/`, `%`, `~`, `=`, and `c`. Their meanings are as follows: + + - `+`, `-`, `*`, and `/` correspond to their usual operation buttons. + - `%` designates the percentage button. + - `~` corresponds to the negation button. + - `=` represents the equals button. + - `c` denotes the clear button. + + :param sequence: The sequence of numbers and operations to execute. + :type sequence: str + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Calculator") + >>> app.input("c2*3.14*5*5=") + 34.54 + + .. versionadded:: 0.0.2 + """ + if self.xa_btns is None: + self.__load_buttons() + for symbol in sequence: + self.xa_btns[symbol].press()
+ + # Misc Methods +
[docs] def current_value(self) -> float: + """Retrieves the current value of the calculator output. + + :return: The calculator's current displayed value + :rtype: float + + .. versionadded:: 0.0.2 + """ + return float(self.front_window.groups()[0].static_texts()[0].value.get())
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Calculator.html b/docs/_modules/PyXA/apps/Calculator.html index 90c7189..50f91bc 100644 --- a/docs/_modules/PyXA/apps/Calculator.html +++ b/docs/_modules/PyXA/apps/Calculator.html @@ -3,7 +3,7 @@ - PyXA.apps.Calculator — PyXA 0.0.9 documentation + PyXA.apps.Calculator — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -232,7 +234,7 @@

Source code for PyXA.apps.Calculator

         :Example:
 
         >>> import PyXA
-        >>> app = PyXA.application("Calculator")
+        >>> app = PyXA.Application("Calculator")
         >>> app.input("c2*3.14*5*5=")
         34.54
 
diff --git a/docs/_modules/PyXA/apps/Calendar 2.html b/docs/_modules/PyXA/apps/Calendar 2.html
new file mode 100644
index 0000000..d2a3f01
--- /dev/null
+++ b/docs/_modules/PyXA/apps/Calendar 2.html	
@@ -0,0 +1,1918 @@
+
+
+
+  
+  
+  PyXA.apps.Calendar — PyXA 0.0.9 documentation
+      
+      
+      
+  
+  
+        
+        
+        
+        
+        
+        
+    
+    
+     
+
+
+ 
+  
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Calendar

+from datetime import date, datetime, timedelta
+from enum import Enum
+from typing import List, Tuple, Union
+
+import EventKit
+import AppKit
+
+from PyXA import XABase
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath
+
+
[docs]class XACalendarApplication(XABaseScriptable.XASBApplication, XACanOpenPath): + """A class for managing and interacting with scripting elements of the macOS Calendar application. + + .. seealso:: Classes :class:`XACalendarCalendar`, :class:`XACalendarEvent` + + .. versionadded:: 0.0.1 + """ +
[docs] class ParticipationStatus(Enum): + """Event participation statuses. + """ + UNKNOWN = XABase.OSType('E6na') #: No answer yet + ACCEPTED = XABase.OSType('E6ap') #: Invitation has been accepted + DECLINED = XABase.OSType('E6dp') #: Invitation has been declined + TENTATIVE = XABase.OSType('E6tp') #: Invitation has been tentatively accepted
+ +
[docs] class EventStatus(Enum): + """Event confirmation statuses. + """ + CANCELLED = XABase.OSType('E4ca') #: A cancelled event + CONFIRMED = XABase.OSType('E4cn') #: A confirmed event + NONE = XABase.OSType('E4no') #: An event without a status + TENTATIVE = XABase.OSType('E4te') #: A tentative event
+ +
[docs] class Priority(Enum): + """Event priorities. + """ + NONE = XABase.OSType('tdp0') #: No priority assigned + LOW = XABase.OSType('tdp9') #: Low priority + MEDIUM = XABase.OSType('tdp5') #: Medium priority + HIGH = XABase.OSType('tdp1') #: High priority
+ +
[docs] class ViewType(Enum): + """Views in Calendar.app. + """ + DAY = XABase.OSType('E5da') #: The iCal day view + WEEK = XABase.OSType('E5we') #: The iCal week view + MONTH = XABase.OSType('E5mo') #: The iCal month view + YEAR = XABase.OSType('E5ye') #: The iCal year view
+ + def __init__(self, properties: dict): + super().__init__(properties) + self.xa_wcls = XACalendarWindow + self.xa_estr = self._exec_suppresed(EventKit.EKEventStore.alloc().init) + + self.properties: dict #: All properties of the application + self.name: str #: The name of the application + self.frontmost: bool #: Whether Calendar is the frontmost application + self.version: str #: The version of the Calendar application + self.default_calendar: XACalendarCalendar #: The calendar that events are added to by default + + @property + def properties(self) -> dict: + return self.xa_scel.properties() + + @property + def name(self) -> str: + return self.xa_scel.name() + + @property + def frontmost(self) -> bool: + return self.xa_scel.frontmost() + + @frontmost.setter + def frontmost(self, frontmost: bool): + self.set_property('frontmost', frontmost) + + @property + def version(self) -> str: + return self.xa_scel.version() + + @property + def default_calendar(self) -> 'XACalendarCalendar': + calendar_obj = self.xa_estr.defaultCalendarForNewEvents() + return self.calendars().by_name(calendar_obj.title()) + +
[docs] def reload_calendars(self) -> 'XACalendarApplication': + """Reloads the contents of all calendars. + + :return: The application object + :rtype: XACalendarApplication + + .. versionadded:: 0.0.1 + """ + self.xa_scel.reloadCalendars() + return self
+ +
[docs] def switch_view_to(self, view: 'XACalendarApplication.ViewType') -> 'XACalendarApplication': + """Switches to the target calendar view. + + :param view: The view to switch to. + :type view: XACalendarApplication.ViewType + :return: The application object + :rtype: XACalendarApplication + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> app.switch_view_to(app.ViewType.WEEK) + >>> app.switch_view_to(app.ViewType.DAY) + >>> app.switch_view_to(app.ViewType.MONTH) + >>> app.switch_view_to(app.ViewType.YEAR) + + .. versionadded:: 0.0.1 + """ + if view == XACalendarApplication.ViewType.YEAR: + self.xa_estr.showDateInCalendar_inView_(0, 3) + else: + self.xa_scel.switchViewTo_(view.value) + return self
+ +
[docs] def view_calendar_at(self, date: datetime, view: Union[None, 'XACalendarApplication.ViewType'] = None) -> 'XACalendarApplication': + """Displays the calendar at the provided date. + + :param date: The date to display. + :type date: datetime + :return: A reference to the Calendar application object. + :rtype: XACalendarApplication + + :Example: + + >>> import PyXA + >>> from datetime import date + >>> app = PyXA.application("Calendar") + >>> date1 = date(2022, 7, 20) + >>> app.view_calendar_at(date1) + + .. versionadded:: 0.0.1 + """ + if view is None: + self.xa_estr.showDateInCalendar_inView_(date, 1) + elif view == XACalendarApplication.ViewType.YEAR: + self.xa_estr.showDateInCalendar_inView_(date, 3) + else: + self.xa_estr.showDateInCalendar_inView_(date, 0) + self.xa_scel.switchViewTo_(view.value) + return self
+ +
[docs] def subscribe_to(self, url: str) -> 'XACalendarCalendar': + """Subscribes to the calendar at the specified URL. + + :param url: The URL of the calendar (in iCal format) to subscribe to + :type url: str + :return: The newly created calendar object + :rtype: XACalendarCalendar + + .. versionadded:: 0.0.1 + """ + self.xa_scel.GetURL_(url) + return self.calendars()[-1]
+ +
[docs] def documents(self, filter: Union[dict, None] = None) -> 'XACalendarDocumentList': + """Returns a list of documents, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned documents will have, or None + :type filter: Union[dict, None] + :return: The list of documents + :rtype: XARemindersDocumentList + + .. versionadded:: 0.0.6 + """ + return self._new_element(self.xa_scel.documents(), XACalendarDocumentList, filter)
+ +
[docs] def calendars(self, filter: Union[dict, None] = None) -> 'XACalendarCalendarList': + """Returns a list of calendars, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned calendars will have, or None + :type filter: Union[dict, None] + :return: The list of calendars + :rtype: XACalendarCalendarList + + :Example 1: Get all calendars + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> print(app.calendars()) + <<class 'PyXA.apps.Calendar.XACalendarCalendarList'>['Calendar', 'Calendar2', 'Calendar3', ...]> + + :Example 2: Get calendars using a filter + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> print(app.calendars({"name": "Calendar"})[0]) + <<class 'PyXA.apps.Calendar.XACalendarCalendar'>Calendar> + + :Example 3: Get calendars using list methods + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> print(app.calendars().by_name("Calendar")) + <<class 'PyXA.apps.Calendar.XACalendarCalendar'>Calendar> + + .. versionadded:: 0.0.6 + """ + return self._new_element(self.xa_scel.calendars(), XACalendarCalendarList, filter)
+ +
[docs] def new_calendar(self, name: str = "New Calendar") -> 'XACalendarCalendar': + """Creates a new calendar with the given name. + + :param name: The name of the calendar, defaults to "New Calendar" + :type name: str, optional + :return: The newly created calendar object + :rtype: XACalendarCalendar + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> app.new_calendar("PyXA Development") + + .. versionadded:: 0.0.1 + """ + new_calendar = self.make("calendar", {"name": name}) + self.calendars().push(new_calendar) + + desc = new_calendar.xa_elem.description() + id = desc[desc.index("id") + 4: desc.index("of app") - 3] + return reversed(self.calendars()).by_name(name)
+ +
[docs] def new_event(self, summary: str, start_date: datetime, end_date: datetime, calendar: Union['XACalendarCalendar', None] = None) -> 'XACalendarEvent': + """Creates a new event with the given name and start/end dates in the specified calendar. If no calendar is specified, the default calendar is used. + + :param name: The name of the event + :type name: str + :param start_date: The start date and time of the event. + :type start_date: datetime + :param end_date: The end date and time of the event. + :type end_date: datetime + :return: A reference to the newly created event. + :rtype: XACalendarEvent + + :Example: Create event on the default calendar + + >>> from datetime import datetime, timedelta + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> start_date = datetime.now() + >>> end_date = start_date + timedelta(hours = 1) + >>> app.new_event("Learn about PyXA", start_date, end_date) + + :Example: Create event on a specific calendar + + >>> from datetime import datetime, timedelta + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> start_date = datetime.now() + >>> end_date = start_date + timedelta(hours = 1) + >>> calendar = app.calendars()[-1] + >>> app.new_event("Learn about PyXA", start_date, end_date, calendar) + + .. versionadded:: 0.0.1 + """ + if calendar is None: + calendar = self.default_calendar + new_event = self.make("event", {"summary": summary, "startDate": start_date, "endDate": end_date}) + calendar.events().push(new_event) + return calendar.events().by_uid(new_event.uid)
+ +
[docs] def make(self, specifier: str, properties: dict = None): + """Creates a new element of the given specifier class without adding it to any list. + + Use :func:`XABase.XAList.push` to push the element onto a list. + + :param specifier: The classname of the object to create + :type specifier: str + :param properties: The properties to give the object + :type properties: dict + :return: A PyXA wrapped form of the object + :rtype: XABase.XAObject + + :Example 1: Make a new calendar + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> new_calendar = app.make("calendar", {"name": "PyXA Development"}) + >>> app.calendars().push(new_calendar) + + :Example 2: Make a new event + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> start_date = datetime.now() + >>> end_date = start_date + timedelta(hours = 1) + >>> new_event = app.make("event", {"summary": "Work on PyXA", "startDate": start_date, "endDate": end_date}) + >>> app.default_calendar.events().push(new_event) + + .. versionadded:: 0.0.6 + """ + if properties is None: + properties = {} + + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + + if specifier == "document": + return self._new_element(obj, XACalendarDocument) + elif specifier == "calendar": + return self._new_element(obj, XACalendarCalendar) + elif specifier == "event": + return self._new_element(obj, XACalendarEvent) + elif specifier == "displayAlarm": + return self._new_element(obj, XACalendarDisplayAlarm) + elif specifier == "mailAlarm": + return self._new_element(obj, XACalendarMailAlarm) + elif specifier == "soundAlarm": + return self._new_element(obj, XACalendarSoundAlarm) + elif specifier == "openFileAlarm": + return self._new_element(obj, XACalendarOpenFileAlarm)
+ + + + +
[docs]class XACalendarWindow(XABaseScriptable.XASBWindow): + """A window of Calendar.app. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.properties: dict #: All properties of the window + self.name: str #: The name of the window + self.id: str #: The unique identifier for the window + self.index: int #: The index of the window in the front-to-back ordering + self.bounds: Tuple[int, int, int, int] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.miniaturizable: bool #: Whether the window can be minimized + self.miniaturized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window can be zoomed + self.zoomed: bool #: Whether the window is currently zoomed + self.document: XACalendarDocument #: The current document displayed in the window + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property('index', index) + + @property + def bounds(self) -> Tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: Tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def miniaturizable(self) -> bool: + return self.xa_elem.miniaturizable() + + @property + def miniaturized(self) -> bool: + return self.xa_elem.miniaturized() + + @miniaturized.setter + def miniaturized(self, miniaturized: bool): + self.set_property('miniaturized', miniaturized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property('visible', visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property('zoomed', zoomed) + + @property + def document(self) -> 'XACalendarDocument': + return self._new_element(self.xa_elem.document(), XACalendarDocument)
+ + + + +
[docs]class XACalendarDocumentList(XABase.XAList): + """A wrapper around lists of documents that employs fast enumeration techniques. + + All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XACalendarDocument, filter) + +
[docs] def properties(self) -> List[dict]: + """Gets the properties of each document in the list. + + :return: A list of document properties dictionaries + :rtype: List[dict] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("properties"))
+ +
[docs] def name(self) -> List[str]: + """Gets the name of each document in the list. + + :return: A list of document names + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def modified(self) -> List[bool]: + """Gets the modified status of each document in the list. + + :return: A list of document modified status boolean values + :rtype: List[bool] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modified"))
+ +
[docs] def file(self) -> List[XABase.XAPath]: + """Gets the file path of each document in the list. + + :return: A list of document file paths + :rtype: List[XABase.XAPath] + + .. versionadded:: 0.0.6 + """ + ls = self.xa_elem.arrayByApplyingSelector_("file") + return [XABase.XAPath(x) for x in ls]
+ +
[docs] def by_properties(self, properties: dict) -> Union['XACalendarDocument', None]: + """Retrieves the document whose properties matches the given properties dictionary, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACalendarDocument, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("properties", properties)
+ +
[docs] def by_name(self, name: str) -> Union['XACalendarDocument', None]: + """Retrieves the document whose name matches the given name, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACalendarDocument, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("name", name)
+ +
[docs] def by_modified(self, modified: bool) -> Union['XACalendarDocument', None]: + """Retrieves the document whose modified status matches the given boolean value, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACalendarDocument, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("modified", modified)
+ +
[docs] def by_file(self, file: XABase.XAPath) -> Union['XACalendarDocument', None]: + """Retrieves the document whose file matches the given file path, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACalendarDocument, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("file", file.xa_elem)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XACalendarDocument(XABase.XAObject): + """A document in Calendar.app. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.properties: dict #: All properties of the document + self.name: str #: The name of the document + self.modified: bool #: Whether the document has been modified since it was last saved + self.file: XABase.XAPath #: The location of the document on disk, if it has one + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def modified(self) -> bool: + return self.xa_elem.modified() + + @property + def file(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.file())
+ + + + +
[docs]class XACalendarCalendarList(XABase.XAList): + """A wrapper around lists of calendars that employs fast enumeration techniques. + + All properties of calendars can be called as methods on the wrapped list, returning a list containing each calendar's value for the property. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XACalendarCalendar, filter) + +
[docs] def properties(self) -> List[dict]: + """Gets the properties of each calendar in the list. + + :return: A list of calendar properties dictionaries + :rtype: List[dict] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("properties"))
+ +
[docs] def name(self) -> List[str]: + """Gets the name of each calendar in the list. + + :return: A list of calendar names + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def color(self) -> List[str]: + """Gets the color of each calendar in the list. + + :return: A list of calendar color strings + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + ls = self.xa_elem.arrayByApplyingSelector_("color") + return [XABase.XAColor(x) for x in ls]
+ +
[docs] def calendar_identifier(self) -> List[str]: + """Gets the ID of each calendar in the list. + + :return: A list of calendar IDs + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("calendarIdentifier"))
+ +
[docs] def writable(self) -> List[bool]: + """Gets the writable status of each calendar in the list. + + :return: A list of calendar writable status boolean values + :rtype: List[bool] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("writable"))
+ +
[docs] def description(self) -> List[str]: + """Gets the description of each calendar in the list. + + :return: A list of calendar descriptions + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("description"))
+ +
[docs] def events(self) -> 'XACalendarEventList': + """Gets the events of each calendar in the list. + + :return: A list of calendar events + :rtype: XACalendarEventList + + .. versionadded:: 0.0.6 + """ + ls = self.xa_elem.arrayByApplyingSelector_("events") + return self._new_element(ls, XACalendarEventList)
+ +
[docs] def by_properties(self, properties: dict) -> Union['XACalendarCalendar', None]: + """Retrieves the calendar whose properties matches the given properties dictionary, if one exists. + + :return: The desired calendar, if it is found + :rtype: Union[XACalendarCalendar, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("properties", properties)
+ +
[docs] def by_name(self, name: str) -> Union['XACalendarCalendar', None]: + """Retrieves the calendar whose name matches the given name, if one exists. + + :return: The desired calendar, if it is found + :rtype: Union[XACalendarCalendar, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("name", name)
+ +
[docs] def by_color(self, color: XABase.XAColor) -> Union['XACalendarCalendar', None]: + """Retrieves the first calendar whose color matches the given color string, if one exists. + + :return: The desired calendar, if it is found + :rtype: Union[XACalendarCalendar, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("color", color.xa_elem)
+ +
[docs] def by_calendar_identifier(self, calendar_identifier: str) -> Union['XACalendarCalendar', None]: + """Retrieves the calendar whose ID matches the given ID, if one exists. + + :return: The desired calendar, if it is found + :rtype: Union[XACalendarCalendar, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("calendarIdentifier", calendar_identifier)
+ +
[docs] def by_writable(self, writable: bool) -> Union['XACalendarCalendar', None]: + """Retrieves the first calendar whose writable status matches the given boolean value, if one exists. + + :return: The desired calendar, if it is found + :rtype: Union[XACalendarCalendar, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("writable", writable)
+ +
[docs] def by_description(self, description: str) -> Union['XACalendarCalendar', None]: + """Retrieves the calendar whose description matches the given description string, if one exists. + + :return: The desired calendar, if it is found + :rtype: Union[XACalendarCalendar, None] + + .. versionadded:: 0.0.6 + """ + for calendar in self: + if calendar.description == description: + return calendar
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XACalendarCalendar(XABase.XAObject): + """A calendar in Calendar.app. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.xa_estr = self._exec_suppresed(EventKit.EKEventStore.alloc().init) + + self.properties: dict #: All properties of the calendar + self.name: str #: The name of the calendar + self.color: XABase.XAColor #: The color of the calendar + self.calendar_identifier: str #: The unique identifier for the calendar + self.writable: bool #: Whether the calendar is writable + self.description: str #: The description of the calendar + + if hasattr(self.xa_elem, "name"): + calendars = self.xa_estr.allCalendars() + predicate = XABase.XAPredicate() + predicate.add_eq_condition("title", self.name) + self.calendar_obj = predicate.evaluate(calendars) + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property('name', name) + + @property + def color(self) -> XABase.XAColor: + return XABase.XAColor(self.xa_elem.color()) + + @color.setter + def color(self, color: XABase.XAColor): + self.set_property('color', color.xa_elem) + + @property + def calendar_identifier(self) -> str: + return self.xa_elem.calendarIdentifier() + + @property + def writable(self) -> bool: + return self.xa_elem.writable() + + @property + def description(self) -> str: + return self.xa_elem.description() + + @description.setter + def description(self, description: str): + self.set_property('description', description) + +
[docs] def delete(self) -> 'XACalendarEvent': + """Deletes the calendar. + + .. versionadded:: 0.0.2 + """ + self.xa_estr.requestAccessToEntityType_completion_(EventKit.EKEntityTypeEvent, None) + self.xa_calendar.markAsDeleted() + self.xa_estr.deleteCalendar_forEntityType_error_(self.xa_calendar, EventKit.EKEntityTypeEvent, None)
+ +
[docs] def events_in_range(self, start_date: datetime, end_date: datetime) -> 'XACalendarEventList': + """Gets a list of events occurring between the specified start and end datetimes. + + :param start_date: The earliest date an event in the list should begin. + :type start_date: datetime + :param end_date: The latest date an event in the list should end. + :type end_date: datetime + :return: The list of events. + :rtype: XACalendarEventList + + :Example: + + >>> from datetime import date + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> calendar = app.default_calendar + >>> start_date = date(2022, 6, 4) + >>> end_date = date(2022, 6, 6) + >>> print(calendar.events_in_range(start_date, end_date)) + [<PyXA.apps.Calendar.XACalendarEvent object at 0x105b83d90>, <PyXA.apps.Calendar.XACalendarEvent object at 0x105b90bb0>, <PyXA.apps.Calendar.XACalendarEvent object at 0x105b90dc0>] + + .. note:: + + Querying events from a wide date range can take significant time. If you are looking for a specific subset of events within a large date range, it *might* be faster to use :func:`events` with a well-constructed filter and then iterate through the resulting array of objects, parsing out events outside of the desired date range. + + .. versionadded:: 0.0.2 + """ + predicate = XABase.XAPredicate() + predicate.add_geq_condition("startDate", start_date) + predicate.add_leq_condition("endDate", end_date) + events_in_range = predicate.evaluate(self.xa_elem.events()) + return self._new_element(events_in_range, XACalendarEventList)
+ +
[docs] def events_today(self) -> 'XACalendarEventList': + """Gets a list of all events in the next 24 hours. + + :return: The list of events. + :rtype: XACalendarEventList + + .. seealso:: :func:`week_events` + + .. versionadded:: 0.0.2 + """ + start_date = datetime.now() + end_date = start_date + timedelta(days = 1) + return self.events_in_range(start_date, end_date)
+ +
[docs] def week_events(self) -> 'XACalendarEventList': + """Gets a list of events occurring in the next 7 days. + + :return: The list of events. + :rtype: XACalendarEventList + + .. seealso:: :func:`events_today` + + .. versionadded:: 0.0.2 + """ + start_date = datetime.now() + end_date = start_date + timedelta(days = 7) + return self.events_in_range(start_date, end_date)
+ +
[docs] def new_event(self, name: str, start_date: datetime, end_date: datetime) -> 'XACalendarEvent': + """Creates a new event and pushes it onto this calendar's events array. + + :param name: The name of the event. + :type name: str + :param start_date: The start date and time of the event. + :type start_date: datetime + :param end_date: The end date and time of the event. + :type end_date: datetime + :return: A reference to the newly created event. + :rtype: XACalendarEvent + + .. versionadded:: 0.0.1 + """ + return self.xa_prnt.xa_prnt.new_event(name, start_date, end_date, self)
+ +
[docs] def events(self, filter: Union[dict, None] = None) -> 'XACalendarEventList': + """Returns a list of events, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned events will have, or None + :type filter: Union[dict, None] + :return: The list of events + :rtype: XACalendarEventList + + .. versionadded:: 0.0.6 + """ + return self._new_element(self.xa_elem.events(), XACalendarEventList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + self.name + ">"
+ + + + +
[docs]class XACalendarAlarm(XABase.XAObject): + """An event alarm in Calendar.app. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.properties: dict #: All properties of the alarm + self.trigger_interval: int #: The interval in minutes between the event and the alarm + self.trigger_date: datetime #: The date of the alarm + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def trigger_interval(self) -> int: + return self.xa_elem.triggerInterval() + + @trigger_interval.setter + def trigger_interval(self, trigger_interval: int): + self.set_property('triggerInterval', trigger_interval) + + @property + def trigger_date(self) -> datetime: + return self.xa_elem.triggerDate() + + @trigger_date.setter + def trigger_date(self, trigger_date: datetime): + self.set_property('triggerDate', trigger_date)
+ + + + +
[docs]class XACalendarDisplayAlarm(XACalendarAlarm): + """A display alarm in Calendar.app. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict): + super().__init__(properties)
+ + + + +
[docs]class XACalendarMailAlarm(XACalendarAlarm): + """A mail alarm in Calendar.app. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict): + super().__init__(properties)
+ + + + +
[docs]class XACalendarSoundAlarm(XACalendarAlarm): + """A sound alarm in Calendar.app. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.sound_name: str #: The system sound name to be used for the alarm + self.sound_file: str #: The path to the sound file to be used for the alarm + + @property + def sound_name(self) -> str: + return self.xa_elem.soundName() + + @sound_name.setter + def sound_name(self, sound_name: str): + self.set_property('soundName', sound_name) + + @property + def sound_file(self) -> str: + return self.xa_elem.soundFile() + + @sound_file.setter + def sound_file(self, sound_file: str): + self.set_property('soundFile', sound_file)
+ + + + +
[docs]class XACalendarOpenFileAlarm(XACalendarAlarm): + """An open file alarm in Calendar.app. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.file_path: str #: The path to be opened by the alarm + + @property + def file_path(self) -> str: + return self.xa_elem.filePath() + + @file_path.setter + def file_path(self, file_path: str): + self.set_property('filePath', file_path)
+ + + + +
[docs]class XACalendarAttendeeList(XABase.XAList): + """A wrapper around lists of attendees that employs fast enumeration techniques. + + All properties of attendees can be called as methods on the wrapped list, returning a list containing each attendee's value for the property. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XACalendarEvent, filter) + +
[docs] def properties(self) -> List[dict]: + """Gets the properties of each attendee in the list. + + :return: A list of attendee properties dictionaries + :rtype: List[dict] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("properties"))
+ +
[docs] def display_name(self) -> List[str]: + """Gets the display name of each attendee in the list. + + :return: A list of attendee first and last names + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("displayName"))
+ +
[docs] def email(self) -> List[str]: + """Gets the email address of each attendee in the list. + + :return: A list of attendee email addresses + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("email"))
+ +
[docs] def participation_status(self) -> List[XACalendarApplication.ParticipationStatus]: + """Gets the participation status of each attendee in the list. + + :return: A list of attendee participation statuses + :rtype: List[XACalendarApplication.ParticipationStatus] + + .. versionadded:: 0.0.6 + """ + ls = self.xa_elem.arrayByApplyingSelector_("participationStatus") + return [XACalendarApplication.ParticipationStatus(XABase.OSType(x.stringValue())) for x in ls]
+ +
[docs] def by_properties(self, properties: dict) -> Union['XACalendarAttendee', None]: + """Retrieves the attendee whose properties matches the given properties dictionary, if one exists. + + :return: The desired attendee, if it is found + :rtype: Union[XACalendarAttendee, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("properties", properties)
+ +
[docs] def by_display_name(self, display_name: str) -> Union['XACalendarAttendee', None]: + """Retrieves the attendee whose display name matches the given first and last names, if one exists. + + :return: The desired attendee, if it is found + :rtype: Union[XACalendarAttendee, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("displayName", display_name)
+ +
[docs] def by_email(self, email: str) -> Union['XACalendarAttendee', None]: + """Retrieves the attendee whose email address matches the given email address, if one exists. + + :return: The desired attendee, if it is found + :rtype: Union[XACalendarAttendee, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("email", email)
+ +
[docs] def by_participation_status(self, participation_status: XACalendarApplication.ParticipationStatus) -> Union['XACalendarAttendee', None]: + """Retrieves the attendee whose participation status matches the given status, if one exists. + + :return: The desired attendee, if it is found + :rtype: Union[XACalendarAttendee, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("participationStatus", participation_status.value)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.display_name()) + ">"
+ +
[docs]class XACalendarAttendee(XABase.XAObject): + """An event attendee in Calendar.app. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.properties: dict #: All properties of the attendee + self.display_name: str #: The first and last name of the attendee + self.email: str #: The email address of the attendee + self.participation_status: XACalendarApplication.ParticipationStatus #: The invitation status for the attendee + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def display_name(self) -> str: + return self.xa_elem.displayName() + + @property + def email(self) -> str: + return self.xa_elem.email() + + @property + def participation_status(self) -> XACalendarApplication.ParticipationStatus: + return XACalendarApplication.ParticipationStatus(self.xa_elem.participationStatus())
+ + + + +
[docs]class XACalendarEventList(XABase.XAList): + """A wrapper around lists of events that employs fast enumeration techniques. + + All properties of events can be called as methods on the wrapped list, returning a list containing each event's value for the property. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XACalendarEvent, filter) + + def properties(self) -> List[dict]: + """Gets the properties of each event in the list. + + :return: A list of event properties dictionaries + :rtype: List[dict] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("properties")) + +
[docs] def description(self) -> List[str]: + """Gets the description of each event in the list. + + :return: A list of event descriptions + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("description"))
+ +
[docs] def start_date(self) -> List[datetime]: + """Gets the start date of each event in the list. + + :return: A list of event start dates + :rtype: List[datetime] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("startDate"))
+ +
[docs] def properties(self) -> List[datetime]: + """Gets the end date of each event in the list. + + :return: A list of event end dates + :rtype: List[datetime] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("endDate"))
+ +
[docs] def allday_event(self) -> List[bool]: + """Gets the all-day status of each event in the list. + + :return: A list of event all-day status boolean values + :rtype: List[bool] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("alldayEvent"))
+ +
[docs] def recurrence(self) -> List[str]: + """Gets the recurrence string of each event in the list. + + :return: A list of event recurrence strings + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("recurrence"))
+ +
[docs] def sequence(self) -> List[int]: + """Gets the version of each event in the list. + + :return: A list of event versions + :rtype: List[int] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("sequence"))
+ +
[docs] def stamp_date(self) -> List[datetime]: + """Gets the modification date of each event in the list. + + :return: A list of event modification dates + :rtype: List[datetime] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("stampDate"))
+ +
[docs] def excluded_dates(self) -> List[List[datetime]]: + """Gets the excluded dates of each event in the list. + + :return: A list of event excluded dates + :rtype: List[List[datetime]] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("excludedDates"))
+ +
[docs] def status(self) -> List[XACalendarApplication.EventStatus]: + """Gets the status of each event in the list. + + :return: A list of event statuses + :rtype: List[XACalendarApplication.EventStatus] + + .. versionadded:: 0.0.6 + """ + ls = self.xa_elem.arrayByApplyingSelector_("status") + return [XACalendarApplication.EventStatus(XABase.OSType(x.stringValue())) for x in ls]
+ +
[docs] def summary(self) -> List[str]: + """Gets the summary of each event in the list. + + :return: A list of event summaries + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("summary"))
+ +
[docs] def location(self) -> List[str]: + """Gets the location string of each event in the list. + + :return: A list of event locations + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("location"))
+ +
[docs] def uid(self) -> List[str]: + """Gets the unique identifier of each event in the list. + + :return: A list of event IDs + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("uid"))
+ +
[docs] def url(self) -> List[str]: + """Gets the URL associated to each event in the list. + + :return: A list of event URLs + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("URL"))
+ +
[docs] def by_properties(self, properties: dict) -> Union['XACalendarEvent', None]: + """Retrieves the event whose properties matches the given properties dictionary, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("properties", properties)
+ +
[docs] def by_description(self, description: str) -> Union['XACalendarEvent', None]: + """Retrieves the event whose description matches the given description string, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("description", description)
+ +
[docs] def by_start_date(self, start_date: datetime) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose start date matches the given date, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("startDate", start_date)
+ +
[docs] def by_end_date(self, end_date: datetime) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose end date matches the given date, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("endDate", end_date)
+ +
[docs] def by_allday_event(self, allday_event: bool) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose all-day even status matches the given boolean value, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("alldayEvent", allday_event)
+ +
[docs] def by_recurrence(self, recurrence: str) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose recurrence string matches the given string, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("recurrence", recurrence)
+ +
[docs] def by_sequence(self, sequence: int) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose sequence (version) matches the given sequence number, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("sequence", sequence)
+ +
[docs] def by_stamp_date(self, stamp_date: datetime) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose stamp date matches the given date, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("stampDate", stamp_date)
+ +
[docs] def by_excluded_dates(self, excluded_dates: List[datetime]) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose excluded dates date matches the given list of dates, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("excludedDates", excluded_dates)
+ +
[docs] def by_status(self, status: XACalendarApplication.EventStatus) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose status matches the given status, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + + .. versionadded:: 0.0.6 + """ + return self.by_property("status", status.value)
+ +
[docs] def by_summary(self, summary: str) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose summary matches the given string, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("summary", summary)
+ +
[docs] def by_location(self, location: str) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose location string matches the given location, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("location", location)
+ +
[docs] def by_uid(self, uid: str) -> Union['XACalendarEvent', None]: + """Retrieves the event whose unique identifier matches the given ID string, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("uid", uid)
+ +
[docs] def by_url(self, url: str) -> Union['XACalendarEvent', None]: + """Retrieves the first event whose associated URL matches the given URL string, if one exists. + + :return: The desired event, if it is found + :rtype: Union[XACalendarEvent, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("URL", url)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.summary()) + ">"
+ +
[docs]class XACalendarEvent(XABase.XAObject): + """An event in Calendar.app. + + .. versionadded:: 0.0.1 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.xa_estr = self._exec_suppresed(EventKit.EKEventStore.alloc().init) + + self.properties: dict #: All properties of the event + self.description: str #: The event's notes + self.start_date: datetime #: The start date and time of the event + self.end_date: datetime #: The end date and time of the event + self.allday_event: bool #: Whether the event is an all-day event + self.recurrence: str #: A string describing the event recurrence + self.sequence: int #: The event version + self.stamp_date: date #: The date the event was last modified + self.excluded_dates: List[datetime] #: The exception dates for the event + self.status: XACalendarApplication.EventStatus #: The status of the event + self.summary: str #: The summary (title) of the event + self.location: str #: The location of the event + self.uid: str #: A unique identifier for the event + self.url: str #: The URL associated with the event + + if hasattr(self.xa_elem, "uid"): + events = AppKit.NSMutableArray.arrayWithArray_([]) + for year in range(2006, datetime.now().year + 4, 4): + start_date = date(year, 1, 1) + end_date = start_date + timedelta(days = 365 * 4) + predicate = self.xa_estr.predicateForEventsWithStartDate_endDate_calendars_(start_date, end_date, None) + events.addObjectsFromArray_(self.xa_estr.eventsMatchingPredicate_(predicate)) + self.xa_event_obj = XABase.XAPredicate.evaluate_with_dict(events, {"calendarItemIdentifier": self.uid})[0] + + @property + def properties(self) -> dict: + return self.xa_elem.properties() + + @property + def description(self) -> str: + return self.xa_elem.description() + + @description.setter + def description(self, description: str): + self.set_property('description', description) + + @property + def start_date(self) -> datetime: + return self.xa_elem.startDate() + + @start_date.setter + def start_date(self, start_date: datetime): + self.set_property('startDate', start_date) + + @property + def end_date(self) -> datetime: + return self.xa_elem.endDate() + + @end_date.setter + def end_date(self, end_date: datetime): + self.set_property('endDate', end_date) + + @property + def allday_event(self) -> bool: + return self.xa_elem.alldayEvent() + + @allday_event.setter + def allday_event(self, allday_event: bool): + self.set_property('alldayEvent', allday_event) + + @property + def recurrence(self) -> str: + return self.xa_elem.recurrence() + + @recurrence.setter + def recurrence(self, recurrence: str): + self.set_property('recurrence', recurrence) + + @property + def sequence(self) -> int: + return self.xa_elem.sequence() + + @property + def stamp_date(self) -> datetime: + return self.xa_elem.stampDate() + + @stamp_date.setter + def stamp_date(self, stamp_date: datetime): + self.set_property('stampDate', stamp_date) + + @property + def excluded_dates(self) -> List[datetime]: + return self.xa_elem.excludedDates() + + @excluded_dates.setter + def excluded_dates(self, excluded_dates: List[datetime]): + self.set_property('excludedDates', excluded_dates) + + @property + def status(self) -> XACalendarApplication.EventStatus: + return XACalendarApplication.EventStatus(XABase.OSType(self.xa_elem.status().stringValue())) + + @status.setter + def status(self, status: XACalendarApplication.EventStatus): + self.set_property('status', status.value) + + @property + def summary(self) -> str: + return self.xa_elem.summary() + + @summary.setter + def summary(self, summary: str): + self.set_property('summary', summary) + + @property + def location(self) -> str: + return self.xa_elem.location() + + @location.setter + def location(self, location: str): + self.set_property('location', location) + + @property + def uid(self) -> str: + return self.xa_elem.uid() + + @property + def url(self) -> str: + return self.xa_elem.URL() + + @url.setter + def url(self, url: str): + self.set_property('URL', url) + +
[docs] def show(self) -> 'XACalendarEvent': + """Shows the event in the front calendar window. + + :return: The event object. + :rtype: XACalendarEvent + + .. versionadded:: 0.0.1 + """ + self.xa_elem.show() + return self
+ +
[docs] def delete(self): + """Deletes the event. + + .. versionadded:: 0.0.1 + """ + self.xa_estr.removeEvent_span_error_(self.xa_event_obj, EventKit.EKSpanThisEvent, None)
+ +
[docs] def duplicate(self) -> 'XACalendarEvent': + """Duplicates the event, placing the copy on the same calendar. + + :return:The newly created event object. + :rtype: XACalendarEvent + + .. versionadded:: 0.0.1 + """ + new_event = self.xa_event_obj.duplicate() + self.xa_estr.saveEvent_span_error_(new_event, EventKit.EKSpanThisEvent, None)
+ +
[docs] def duplicate_to(self, calendar: XACalendarCalendar) -> 'XACalendarEvent': + """Duplicates the event, placing the copy on the same calendar. + + :return: The event object that this method was called from + :rtype: XACalendarEvent + + :Example: Copy today's event to another calendar + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> calendar = app.default_calendar + >>> calendar2 = app.calendars()[2] + >>> event = calendar.events_today()[0] + >>> event.duplicate_to(calendar2) + + .. seealso:: :func:`duplicate`, :func:`move_to` + + .. versionadded:: 0.0.1 + """ + calendars = self.xa_estr.allCalendars() + calendar_obj = XABase.XAPredicate.evaluate_with_dict(calendars, {"title": calendar.name})[0] + + self.xa_event_obj.copyToCalendar_withOptions_(calendar_obj, 1) + self.xa_estr.saveCalendar_commit_error_(calendar_obj, True, None) + return self
+ +
[docs] def move_to(self, calendar: XACalendarCalendar) -> 'XACalendarEvent': + """Moves this event to the specified calendar. + + :param calendar: The calendar to move the event to. + :type calendar: XACalendar + :return: A reference to the moved event object. + :rtype: XACalendarEvent + + :Example: Move today's event to another calendar + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> calendar = app.default_calendar + >>> calendar2 = app.calendars()[2] + >>> event = calendar.events_today()[0] + >>> event.move_to(calendar2) + + .. seealso:: :func:`duplicate_to` + + .. versionadded:: 0.0.2 + """ + self.duplicate_to(calendar) + self.delete()
+ +
[docs] def add_attachment(self, path: str) -> 'XACalendarEvent': + """Adds the file at the specified path as an attachment to the event. + + :param path: The path of the file to attach to the event. + :type path: str + :return: A reference to this event object. + :rtype: XACalendarEvent + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> calendar = app.default_calendar + >>> calendar2 = app.calendars()[1] + >>> event = calendar.events_today()[0] + >>> event.add_attachment("/Users/exampleuser/Image.png") + + .. versionadded:: 0.0.2 + """ + file_url = XABase.XAPath(path).xa_elem + attachment = EventKit.EKAttachment.alloc().initWithFilepath_(file_url) + self.xa_elem.addAttachment_(attachment) + self.xa_estr.saveEvent_span_error_(self.xa_event_obj, EventKit.EKSpanThisEvent, None) + return self
+ +
[docs] def attendees(self, filter: Union[dict, None] = None) -> 'XACalendarAttendeeList': + """Returns a list of attendees, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned attendees will have, or None + :type filter: Union[dict, None] + :return: The list of attendees + :rtype: XACalendarAttendeeList + + .. versionadded:: 0.0.6 + """ + return self._new_element(self.xa_elem.attendees(), XACalendarAttendeeList, filter)
+ +
[docs] def attachments(self, filter: dict = None) -> 'XACalendarAttachmentList': + """"Returns a list of attachments, as PyXA objects, matching the given filter. + + :return: The list of attachments. + :rtype: XACalendarAttachmentList + + .. versionadded:: 0.0.2 + """ + return self._new_element(self.xa_event_obj.attachments(), XACalendarAttachmentList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.summary) + ">"
+ + + + +
[docs]class XACalendarAttachmentList(XABase.XAList): + """A wrapper around lists of event attachments that employs fast enumeration techniques. + + All properties of attachments can be called as methods on the wrapped list, returning a list containing each attachment's value for the property. + + .. versionadded:: 0.0.6 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XACalendarAttachment, filter) + +
[docs] def type(self) -> List[str]: + """Gets the type of each attachment in the list. + + :return: A list of attachment types + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("type"))
+ +
[docs] def file_name(self) -> List[str]: + """Gets the file name of each attachment in the list. + + :return: A list of attachment file names + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("filename"))
+ +
[docs] def file(self) -> List[XABase.XAPath]: + """Gets the file path of each attachment in the list. + + :return: A list of attachment file paths + :rtype: List[XABase.XAPath] + + .. versionadded:: 0.0.6 + """ + ls = self.xa_elem.arrayByApplyingSelector_("file") + return [XABase.XAPath(x) for x in ls]
+ +
[docs] def url(self) -> List[XABase.XAURL]: + """Gets the URL of each attachment in the list. + + :return: A list of attachment file URLs + :rtype: List[XABase.XAURL] + + .. versionadded:: 0.0.6 + """ + ls = self.xa_elem.arrayByApplyingSelector_("URL") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def uuid(self) -> List[str]: + """Gets the UUID of each attachment in the list. + + :return: A list of attachment UUIDs + :rtype: List[str] + + .. versionadded:: 0.0.6 + """ + return list(self.xa_elem.arrayByApplyingSelector_("uuid"))
+ +
[docs] def by_type(self, type: str) -> Union['XACalendarAttachment', None]: + """Retrieves the first attachment whose type matches the given type, if one exists. + + :return: The desired attachment, if it is found + :rtype: Union[XACalendarAttachment, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("type", type)
+ +
[docs] def by_file_name(self, file_name: str) -> Union['XACalendarAttachment', None]: + """Retrieves the first attachment whose file name matches the given file name, if one exists. + + :return: The desired attachment, if it is found + :rtype: Union[XACalendarAttachment, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("filename", file_name)
+ +
[docs] def by_file(self, file: XABase.XAPath) -> Union['XACalendarAttachment', None]: + """Retrieves the first attachment whose file path matches the given path, if one exists. + + :return: The desired attachment, if it is found + :rtype: Union[XACalendarAttachment, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("file", file.xa_elem)
+ + def by_url(self, url: XABase.XAURL) -> Union['XACalendarAttachment', None]: + """Retrieves the first attachment whose URL matches the given URL, if one exists. + + :return: The desired attachment, if it is found + :rtype: Union[XACalendarAttachment, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("URL", url.xa_elem) + +
[docs] def by_url(self, uuid: str) -> Union['XACalendarAttachment', None]: + """Retrieves the attachment whose UUID matches the given UUID, if one exists. + + :return: The desired attachment, if it is found + :rtype: Union[XACalendarAttachment, None] + + .. versionadded:: 0.0.6 + """ + return self.by_property("uuid", uuid)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.file_name()) + ">"
+ +
[docs]class XACalendarAttachment(XABase.XAObject): + """A class for interacting with calendar event attachments. + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties: dict): + super().__init__(properties) + self.type: str #: The content type of the attachment, e.g. `image/png` + self.file_name: str#: The filename of the original document + self.file: XABase.XAPath #: The location of the attachment on the local disk + self.url: XABase.XAURL #: The iCloud URL of the attachment + self.uuid: str #: A unique identifier for the attachment + + @property + def type(self) -> str: + return self.xa_elem.contentType() + + @property + def file_name(self) -> str: + return self.xa_elem.filenameSuggestedByServer() + + @property + def file(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.urlOnDisk()) + + @property + def url(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.urlOnServer()) + + @property + def uuid(self) -> str: + return self.xa_elem.uuid() + +
[docs] def open(self) -> 'XACalendarAttachment': + """Opens the attachment in its default application. + + :return: A reference to the attachment object. + :rtype: XACalendarAttachment + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Calendar") + >>> calendar = app.default_calendar + >>> event = calendar.events_today()[0] + >>> event.attachments()[0].open() + + .. versionadded:: 0.0.2 + """ + self.xa_wksp.openURL_(self.file) + return self
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Calendar.html b/docs/_modules/PyXA/apps/Calendar.html index 882063c..e96d6a7 100644 --- a/docs/_modules/PyXA/apps/Calendar.html +++ b/docs/_modules/PyXA/apps/Calendar.html @@ -3,7 +3,7 @@ - PyXA.apps.Calendar — PyXA 0.0.9 documentation + PyXA.apps.Calendar — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -70,10 +72,10 @@

Source code for PyXA.apps.Calendar

 from datetime import date, datetime, timedelta
 from enum import Enum
-from typing import List, Tuple, Union
+from typing import Union
 
 import EventKit
-from AppKit import NSMutableArray
+import AppKit
 
 from PyXA import XABase
 from PyXA import XABaseScriptable
@@ -86,19 +88,6 @@ 

Source code for PyXA.apps.Calendar

 
     .. versionadded:: 0.0.1
     """
-
[docs] class SaveOption(Enum): - """Options for what to do when calling a save event. - """ - SAVE_FILE = XABase.OSType('yes ') #: Save the file. - DONT_SAVE = XABase.OSType('no ') #: Do not save the file. - ASK = XABase.OSType('ask ') #: Ask the user whether or not to save the file.
- -
[docs] class PrintSetting(Enum): - """Options to use when printing. - """ - STANDARD_ERROR_HANDLING = XABase.OSType('lwst') #: Standard PostScript error handling - DETAILED_ERROR_HANDLING = XABase.OSType('lwdt') #: print a detailed report of PostScript errors
-
[docs] class ParticipationStatus(Enum): """Event participation statuses. """ @@ -154,6 +143,10 @@

Source code for PyXA.apps.Calendar

     def frontmost(self) -> bool:
         return self.xa_scel.frontmost()
 
+    @frontmost.setter
+    def frontmost(self, frontmost: bool):
+        self.set_property('frontmost', frontmost)
+
     @property
     def version(self) -> str:
         return self.xa_scel.version()
@@ -410,7 +403,7 @@ 

Source code for PyXA.apps.Calendar

         self.name: str #: The name of the window
         self.id: str #: The unique identifier for the window
         self.index: int #: The index of the window in the front-to-back ordering
-        self.bounds: Tuple[Tuple[int, int], Tuple[int, int]] #: The bounding rectangle of the window
+        self.bounds: tuple[int, int, int, int] #: The bounding rectangle of the window
         self.closeable: bool #: Whether the window has a close button
         self.miniaturizable: bool #: Whether the window can be minimized
         self.miniaturized: bool #: Whether the window is currently minimized
@@ -436,9 +429,25 @@ 

Source code for PyXA.apps.Calendar

     def index(self) -> int:
         return self.xa_elem.index()
 
+    @index.setter
+    def index(self, index: int):
+        self.set_property('index', index)
+
     @property
-    def bounds(self) -> Tuple[Tuple[int, int], Tuple[int, int]]:
-        return self.xa_elem.bounds()
+    def bounds(self) -> tuple[int, int, int, int]:
+        rect = self.xa_elem.bounds()
+        origin = rect.origin
+        size = rect.size
+        return (origin.x, origin.y, size.width, size.height)
+
+    @bounds.setter
+    def bounds(self, bounds: tuple[int, int, int, int]):
+        x = bounds[0]
+        y = bounds[1]
+        w = bounds[2]
+        h = bounds[3]
+        value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h))
+        self.set_property("bounds", value)
 
     @property
     def closeable(self) -> bool:
@@ -452,6 +461,10 @@ 

Source code for PyXA.apps.Calendar

     def miniaturized(self) -> bool:
         return self.xa_elem.miniaturized()
 
+    @miniaturized.setter
+    def miniaturized(self, miniaturized: bool):
+        self.set_property('miniaturized', miniaturized)
+
     @property
     def resizable(self) -> bool:
         return self.xa_elem.resizable()
@@ -460,6 +473,10 @@ 

Source code for PyXA.apps.Calendar

     def visible(self) -> bool:
         return self.xa_elem.visible()
 
+    @visible.setter
+    def visible(self, visible: bool):
+        self.set_property('visible', visible)
+
     @property
     def zoomable(self) -> bool:
         return self.xa_elem.zoomable()
@@ -468,6 +485,10 @@ 

Source code for PyXA.apps.Calendar

     def zoomed(self) -> bool:
         return self.xa_elem.zoomed()
 
+    @zoomed.setter
+    def zoomed(self, zoomed: bool):
+        self.set_property('zoomed', zoomed)
+
     @property
     def document(self) -> 'XACalendarDocument':
         return self._new_element(self.xa_elem.document(), XACalendarDocument)
@@ -485,41 +506,41 @@

Source code for PyXA.apps.Calendar

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XACalendarDocument, filter)
 
-
[docs] def properties(self) -> List[dict]: +
[docs] def properties(self) -> list[dict]: """Gets the properties of each document in the list. :return: A list of document properties dictionaries - :rtype: List[dict] + :rtype: list[dict] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("properties"))
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Gets the name of each document in the list. :return: A list of document names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def modified(self) -> List[bool]: +
[docs] def modified(self) -> list[bool]: """Gets the modified status of each document in the list. :return: A list of document modified status boolean values - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("modified"))
-
[docs] def file(self) -> List[XABase.XAPath]: +
[docs] def file(self) -> list[XABase.XAPath]: """Gets the file path of each document in the list. :return: A list of document file paths - :rtype: List[XABase.XAPath] + :rtype: list[XABase.XAPath] .. versionadded:: 0.0.6 """ @@ -610,62 +631,62 @@

Source code for PyXA.apps.Calendar

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XACalendarCalendar, filter)
 
-
[docs] def properties(self) -> List[dict]: +
[docs] def properties(self) -> list[dict]: """Gets the properties of each calendar in the list. :return: A list of calendar properties dictionaries - :rtype: List[dict] + :rtype: list[dict] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("properties"))
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Gets the name of each calendar in the list. :return: A list of calendar names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def color(self) -> List[str]: +
[docs] def color(self) -> list[str]: """Gets the color of each calendar in the list. :return: A list of calendar color strings - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ ls = self.xa_elem.arrayByApplyingSelector_("color") return [XABase.XAColor(x) for x in ls]
-
[docs] def calendar_identifier(self) -> List[str]: +
[docs] def calendar_identifier(self) -> list[str]: """Gets the ID of each calendar in the list. :return: A list of calendar IDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("calendarIdentifier"))
-
[docs] def writable(self) -> List[bool]: +
[docs] def writable(self) -> list[bool]: """Gets the writable status of each calendar in the list. :return: A list of calendar writable status boolean values - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("writable"))
-
[docs] def description(self) -> List[str]: +
[docs] def description(self) -> list[str]: """Gets the description of each calendar in the list. :return: A list of calendar descriptions - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ @@ -777,10 +798,18 @@

Source code for PyXA.apps.Calendar

     def name(self) -> str:
         return self.xa_elem.name()
 
+    @name.setter
+    def name(self, name: str):
+        self.set_property('name', name)
+
     @property
     def color(self) -> XABase.XAColor:
         return XABase.XAColor(self.xa_elem.color())
 
+    @color.setter
+    def color(self, color: XABase.XAColor):
+        self.set_property('color', color.xa_elem)
+
     @property
     def calendar_identifier(self) -> str:
         return self.xa_elem.calendarIdentifier()
@@ -793,6 +822,10 @@ 

Source code for PyXA.apps.Calendar

     def description(self) -> str:
         return self.xa_elem.description()
 
+    @description.setter
+    def description(self, description: str):
+        self.set_property('description', description)
+
 
[docs] def delete(self) -> 'XACalendarEvent': """Deletes the calendar. @@ -916,9 +949,17 @@

Source code for PyXA.apps.Calendar

     def trigger_interval(self) -> int:
         return self.xa_elem.triggerInterval()
 
+    @trigger_interval.setter
+    def trigger_interval(self, trigger_interval: int):
+        self.set_property('triggerInterval', trigger_interval)
+
     @property
     def trigger_date(self) -> datetime:
-        return self.xa_elem.triggerDate()
+ return self.xa_elem.triggerDate() + + @trigger_date.setter + def trigger_date(self, trigger_date: datetime): + self.set_property('triggerDate', trigger_date)
@@ -959,9 +1000,17 @@

Source code for PyXA.apps.Calendar

     def sound_name(self) -> str:
         return self.xa_elem.soundName()
 
+    @sound_name.setter
+    def sound_name(self, sound_name: str):
+        self.set_property('soundName', sound_name)
+
     @property
     def sound_file(self) -> str:
-        return self.xa_elem.soundFile()
+ return self.xa_elem.soundFile() + + @sound_file.setter + def sound_file(self, sound_file: str): + self.set_property('soundFile', sound_file)
@@ -977,7 +1026,11 @@

Source code for PyXA.apps.Calendar

 
     @property
     def file_path(self) -> str:
-        return self.xa_elem.filePath()
+ return self.xa_elem.filePath() + + @file_path.setter + def file_path(self, file_path: str): + self.set_property('filePath', file_path)
@@ -992,41 +1045,41 @@

Source code for PyXA.apps.Calendar

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XACalendarEvent, filter)
 
-
[docs] def properties(self) -> List[dict]: +
[docs] def properties(self) -> list[dict]: """Gets the properties of each attendee in the list. :return: A list of attendee properties dictionaries - :rtype: List[dict] + :rtype: list[dict] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("properties"))
-
[docs] def display_name(self) -> List[str]: +
[docs] def display_name(self) -> list[str]: """Gets the display name of each attendee in the list. :return: A list of attendee first and last names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("displayName"))
-
[docs] def email(self) -> List[str]: +
[docs] def email(self) -> list[str]: """Gets the email address of each attendee in the list. :return: A list of attendee email addresses - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("email"))
-
[docs] def participation_status(self) -> List[XACalendarApplication.ParticipationStatus]: +
[docs] def participation_status(self) -> list[XACalendarApplication.ParticipationStatus]: """Gets the participation status of each attendee in the list. :return: A list of attendee participation statuses - :rtype: List[XACalendarApplication.ParticipationStatus] + :rtype: list[XACalendarApplication.ParticipationStatus] .. versionadded:: 0.0.6 """ @@ -1117,142 +1170,142 @@

Source code for PyXA.apps.Calendar

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XACalendarEvent, filter)
 
-    def properties(self) -> List[dict]:
+    def properties(self) -> list[dict]:
         """Gets the properties of each event in the list.
 
         :return: A list of event properties dictionaries
-        :rtype: List[dict]
+        :rtype: list[dict]
         
         .. versionadded:: 0.0.6
         """
         return list(self.xa_elem.arrayByApplyingSelector_("properties"))
 
-
[docs] def description(self) -> List[str]: +
[docs] def description(self) -> list[str]: """Gets the description of each event in the list. :return: A list of event descriptions - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("description"))
-
[docs] def start_date(self) -> List[datetime]: +
[docs] def start_date(self) -> list[datetime]: """Gets the start date of each event in the list. :return: A list of event start dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("startDate"))
-
[docs] def properties(self) -> List[datetime]: +
[docs] def properties(self) -> list[datetime]: """Gets the end date of each event in the list. :return: A list of event end dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("endDate"))
-
[docs] def allday_event(self) -> List[bool]: +
[docs] def allday_event(self) -> list[bool]: """Gets the all-day status of each event in the list. :return: A list of event all-day status boolean values - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("alldayEvent"))
-
[docs] def recurrence(self) -> List[str]: +
[docs] def recurrence(self) -> list[str]: """Gets the recurrence string of each event in the list. :return: A list of event recurrence strings - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("recurrence"))
-
[docs] def sequence(self) -> List[int]: +
[docs] def sequence(self) -> list[int]: """Gets the version of each event in the list. :return: A list of event versions - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("sequence"))
-
[docs] def stamp_date(self) -> List[datetime]: +
[docs] def stamp_date(self) -> list[datetime]: """Gets the modification date of each event in the list. :return: A list of event modification dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("stampDate"))
-
[docs] def excluded_dates(self) -> List[List[datetime]]: +
[docs] def excluded_dates(self) -> list[list[datetime]]: """Gets the excluded dates of each event in the list. :return: A list of event excluded dates - :rtype: List[List[datetime]] + :rtype: list[list[datetime]] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("excludedDates"))
-
[docs] def status(self) -> List[XACalendarApplication.EventStatus]: +
[docs] def status(self) -> list[XACalendarApplication.EventStatus]: """Gets the status of each event in the list. :return: A list of event statuses - :rtype: List[XACalendarApplication.EventStatus] + :rtype: list[XACalendarApplication.EventStatus] .. versionadded:: 0.0.6 """ ls = self.xa_elem.arrayByApplyingSelector_("status") return [XACalendarApplication.EventStatus(XABase.OSType(x.stringValue())) for x in ls]
-
[docs] def summary(self) -> List[str]: +
[docs] def summary(self) -> list[str]: """Gets the summary of each event in the list. :return: A list of event summaries - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("summary"))
-
[docs] def location(self) -> List[str]: +
[docs] def location(self) -> list[str]: """Gets the location string of each event in the list. :return: A list of event locations - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("location"))
-
[docs] def uid(self) -> List[str]: +
[docs] def uid(self) -> list[str]: """Gets the unique identifier of each event in the list. :return: A list of event IDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("uid"))
-
[docs] def url(self) -> List[str]: +
[docs] def url(self) -> list[str]: """Gets the URL associated to each event in the list. :return: A list of event URLs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ @@ -1338,7 +1391,7 @@

Source code for PyXA.apps.Calendar

         """
         return self.by_property("stampDate", stamp_date)
-
[docs] def by_excluded_dates(self, excluded_dates: List[datetime]) -> Union['XACalendarEvent', None]: +
[docs] def by_excluded_dates(self, excluded_dates: list[datetime]) -> Union['XACalendarEvent', None]: """Retrieves the first event whose excluded dates date matches the given list of dates, if one exists. :return: The desired event, if it is found @@ -1419,7 +1472,7 @@

Source code for PyXA.apps.Calendar

         self.recurrence: str #: A string describing the event recurrence
         self.sequence: int #: The event version
         self.stamp_date: date #: The date the event was last modified
-        self.excluded_dates: List[datetime] #: The exception dates for the event
+        self.excluded_dates: list[datetime] #: The exception dates for the event
         self.status: XACalendarApplication.EventStatus #: The status of the event
         self.summary: str #: The summary (title) of the event
         self.location: str #: The location of the event
@@ -1427,7 +1480,7 @@ 

Source code for PyXA.apps.Calendar

         self.url: str #: The URL associated with the event
 
         if hasattr(self.xa_elem, "uid"):
-            events = NSMutableArray.arrayWithArray_([])
+            events = AppKit.NSMutableArray.arrayWithArray_([])
             for year in range(2006, datetime.now().year + 4, 4):
                 start_date = date(year, 1, 1)
                 end_date = start_date + timedelta(days = 365 * 4)
@@ -1443,22 +1496,42 @@ 

Source code for PyXA.apps.Calendar

     def description(self) -> str:
         return self.xa_elem.description()
 
+    @description.setter
+    def description(self, description: str):
+        self.set_property('description', description)
+
     @property
     def start_date(self) -> datetime:
         return self.xa_elem.startDate()
 
+    @start_date.setter
+    def start_date(self, start_date: datetime):
+        self.set_property('startDate', start_date)
+
     @property
     def end_date(self) -> datetime:
         return self.xa_elem.endDate()
 
+    @end_date.setter
+    def end_date(self, end_date: datetime):
+        self.set_property('endDate', end_date)
+
     @property
     def allday_event(self) -> bool:
         return self.xa_elem.alldayEvent()
 
+    @allday_event.setter
+    def allday_event(self, allday_event: bool):
+        self.set_property('alldayEvent', allday_event)
+
     @property
     def recurrence(self) -> str:
         return self.xa_elem.recurrence()
 
+    @recurrence.setter
+    def recurrence(self, recurrence: str):
+        self.set_property('recurrence', recurrence)
+
     @property
     def sequence(self) -> int:
         return self.xa_elem.sequence()
@@ -1467,22 +1540,42 @@ 

Source code for PyXA.apps.Calendar

     def stamp_date(self) -> datetime:
         return self.xa_elem.stampDate()
 
+    @stamp_date.setter
+    def stamp_date(self, stamp_date: datetime):
+        self.set_property('stampDate', stamp_date)
+
     @property
-    def excluded_dates(self) -> List[datetime]:
+    def excluded_dates(self) -> list[datetime]:
         return self.xa_elem.excludedDates()
 
+    @excluded_dates.setter
+    def excluded_dates(self, excluded_dates: list[datetime]):
+        self.set_property('excludedDates', excluded_dates)
+
     @property
     def status(self) -> XACalendarApplication.EventStatus:
-        return XACalendarApplication.EventStatus(XABase.OSType(self.xa_elem.status().stringValue())) 
+        return XACalendarApplication.EventStatus(XABase.OSType(self.xa_elem.status().stringValue()))
+
+    @status.setter
+    def status(self, status: XACalendarApplication.EventStatus):
+        self.set_property('status', status.value)
 
     @property
     def summary(self) -> str:
         return self.xa_elem.summary()
 
+    @summary.setter
+    def summary(self, summary: str):
+        self.set_property('summary', summary)
+
     @property
     def location(self) -> str:
         return self.xa_elem.location()
 
+    @location.setter
+    def location(self, location: str):
+        self.set_property('location', location)
+
     @property
     def uid(self) -> str:
         return self.xa_elem.uid()
@@ -1491,6 +1584,10 @@ 

Source code for PyXA.apps.Calendar

     def url(self) -> str:
         return self.xa_elem.URL()
 
+    @url.setter
+    def url(self, url: str):
+        self.set_property('URL', url)
+
 
[docs] def show(self) -> 'XACalendarEvent': """Shows the event in the front calendar window. @@ -1633,53 +1730,53 @@

Source code for PyXA.apps.Calendar

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XACalendarAttachment, filter)
 
-
[docs] def type(self) -> List[str]: +
[docs] def type(self) -> list[str]: """Gets the type of each attachment in the list. :return: A list of attachment types - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("type"))
-
[docs] def file_name(self) -> List[str]: +
[docs] def file_name(self) -> list[str]: """Gets the file name of each attachment in the list. :return: A list of attachment file names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ return list(self.xa_elem.arrayByApplyingSelector_("filename"))
-
[docs] def file(self) -> List[XABase.XAPath]: +
[docs] def file(self) -> list[XABase.XAPath]: """Gets the file path of each attachment in the list. :return: A list of attachment file paths - :rtype: List[XABase.XAPath] + :rtype: list[XABase.XAPath] .. versionadded:: 0.0.6 """ ls = self.xa_elem.arrayByApplyingSelector_("file") return [XABase.XAPath(x) for x in ls]
-
[docs] def url(self) -> List[XABase.XAURL]: +
[docs] def url(self) -> list[XABase.XAURL]: """Gets the URL of each attachment in the list. :return: A list of attachment file URLs - :rtype: List[XABase.XAURL] + :rtype: list[XABase.XAURL] .. versionadded:: 0.0.6 """ ls = self.xa_elem.arrayByApplyingSelector_("URL") return [XABase.XAURL(x) for x in ls]
-
[docs] def uuid(self) -> List[str]: +
[docs] def uuid(self) -> list[str]: """Gets the UUID of each attachment in the list. :return: A list of attachment UUIDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.6 """ diff --git a/docs/_modules/PyXA/apps/Cardhop 2.html b/docs/_modules/PyXA/apps/Cardhop 2.html new file mode 100644 index 0000000..e805215 --- /dev/null +++ b/docs/_modules/PyXA/apps/Cardhop 2.html @@ -0,0 +1,382 @@ + + + + + + PyXA.apps.Cardhop — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Cardhop

+""".. versionadded:: 0.1.0
+
+Control Cardhop using JXA-like syntax.
+"""
+
+from datetime import datetime
+from enum import Enum
+from typing import List, Tuple, Union
+
+import AppKit
+from ScriptingBridge import SBElementArray
+
+from PyXA import XABase
+from PyXA.XABase import OSType
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath, XACanPrintPath, XAClipboardCodable, XADeletable, XAPrintable, XAShowable
+
+
[docs]class XACardhopAppplication(XABaseScriptable.XASBApplication, XACanOpenPath, XACanPrintPath): + """A class for interacting with Cardhop.app. + + .. versionadded:: 0.1.0 + """ +
[docs] class ZoomType(Enum): + """Options for zoom type to use when opening a new document. + """ + NO_VARY = 0 + FIT_PAGE = 1 + FIT_WIDTH = 2 + FIT_HEIGHT = 3 + FIT_VISIBLE_WIDTH = 4
+ + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XACardhopWindow + + self.name: str #: The name of the application + self.frontmost: bool #: Whether Cardhop is the active application + self.version: str #: The version of Cardhop.app + +
[docs] def parse_sentence(self, sentence: str, add_immediately: bool = True): + """Parses the given sentence and carries out the corresponding actions. + + :param sentence: The sentence to parse + :type sentence: str + :param add_immediately: Whether to immediately parse the sentence and save resulting changes, instead of having the user confirm changes via the GUI, defaults to True + :type add_immediately: bool, optional + """ + self.xa_scel.parseSentence_addImmediately_(sentence, add_immediately)
+ +
[docs] def documents(self, filter: dict = None) -> 'XACardhopDocumentList': + """Returns a list of documents, as PyXA objects, matching the filter. + + :param filter: A dictionary specifying property-value pairs that all returned documents will have + :type filter: dict + :return: The list of documents + :rtype: XACardhopDocumentList + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_scel.documents(), XACardhopDocumentList, filter)
+ + + + +
[docs]class XACardhopWindow(XABaseScriptable.XASBWindow, XAPrintable): + """A window of Cardhop.app. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The title of the window + self.id: int #: The unique identifier of the window + self.index: int #: The index of the window, ordered front to back + self.bounds: Tuple[int, int, int, int] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.miniaturizable: bool #: Whether the window has a minimize button + self.miniaturized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window has a zoom button + self.zoomed: bool #;; Whether the window is currently zoomed + self.document: XACardhopDocument #: The document whose contents are displayed in the window + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property('index', index) + + @property + def bounds(self) -> Tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: Tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def miniaturizable(self) -> bool: + return self.xa_elem.miniaturizable() + + @property + def miniaturized(self) -> bool: + return self.xa_elem.miniaturized() + + @miniaturized.setter + def miniaturized(self, miniaturized: bool): + self.set_property('miniaturized', miniaturized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property('visible', visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property('zoomed', zoomed) + + @property + def document(self) -> 'XACardhopDocument': + return self._new_element(self.xa_elem.document(), XACardhopDocument)
+ + + + +
[docs]class XACardhopDocumentList(XABase.XAList, XAPrintable, XAClipboardCodable): + """A wrapper around lists of documents that employs fast enumeration techniques. + + All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XACardhopDocument, filter) + +
[docs] def name(self) -> List[str]: + """Gets the name of each document in the list. + + :return: A list of document names + :rtype: List[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def modified(self) -> List[bool]: + """Gets the modified status of each document in the list. + + :return: A list of document modified status booleans + :rtype: List[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modified"))
+ +
[docs] def file(self) -> List[XABase.XAPath]: + """Gets the file path of each document in the list. + + :return: A list of document file paths + :rtype: List[XABase.XAPath] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("file") + return [XABase.XAPath(x) for x in ls]
+ +
[docs] def by_name(self, name: str) -> Union['XACardhopDocument', None]: + """Retrieves the first document whose name matches the given name, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACardhopDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_modified(self, modified: bool) -> Union['XACardhopDocument', None]: + """Retrieves the first document whose modified status matches the given boolean value, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACardhopDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("modified", modified)
+ +
[docs] def by_file(self, file: Union[XABase.XAPath, str]) -> Union['XACardhopDocument', None]: + """Retrieves the first document whose file path matches the given path, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACardhopDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(file, str): + file = XABase.XAPath(file) + return self.by_property("file", file.xa_elem)
+ +
[docs] def get_clipboard_representation(self) -> List[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of each document in the list. + + When the clipboard content is set to a list of documents, each documents's file URL and name are added to the clipboard. + + :return: A list of each document's file URL and name + :rtype: List[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + items = [] + names = self.name() + paths = self.file() + for index, text in enumerate(names): + items.append(str(text), paths[index].xa_elem) + return items
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XACardhopDocument(XABase.XAObject): + """A document of Cardhop.app. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The title of the document + self.modified: bool #: Whether the document has been modified since its last save + self.file: XABase.XAPath #: The location of the document on disk, if it has one + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def modified(self) -> bool: + return self.xa_elem.modified() + + @property + def file(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.file())
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Cardhop.html b/docs/_modules/PyXA/apps/Cardhop.html new file mode 100644 index 0000000..2a90d06 --- /dev/null +++ b/docs/_modules/PyXA/apps/Cardhop.html @@ -0,0 +1,380 @@ + + + + + + PyXA.apps.Cardhop — PyXA 0.1.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Cardhop

+""".. versionadded:: 0.1.0
+
+Control Cardhop using JXA-like syntax.
+"""
+
+from enum import Enum
+from typing import Union
+
+import AppKit
+from ScriptingBridge import SBElementArray
+
+from PyXA import XABase
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath, XACanPrintPath, XAClipboardCodable, XADeletable, XAPrintable, XAShowable
+
+
[docs]class XACardhopAppplication(XABaseScriptable.XASBApplication, XACanOpenPath, XACanPrintPath): + """A class for interacting with Cardhop.app. + + .. versionadded:: 0.1.0 + """ +
[docs] class ZoomType(Enum): + """Options for zoom type to use when opening a new document. + """ + NO_VARY = 0 + FIT_PAGE = 1 + FIT_WIDTH = 2 + FIT_HEIGHT = 3 + FIT_VISIBLE_WIDTH = 4
+ + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XACardhopWindow + + self.name: str #: The name of the application + self.frontmost: bool #: Whether Cardhop is the active application + self.version: str #: The version of Cardhop.app + +
[docs] def parse_sentence(self, sentence: str, add_immediately: bool = True): + """Parses the given sentence and carries out the corresponding actions. + + :param sentence: The sentence to parse + :type sentence: str + :param add_immediately: Whether to immediately parse the sentence and save resulting changes, instead of having the user confirm changes via the GUI, defaults to True + :type add_immediately: bool, optional + """ + self.xa_scel.parseSentence_addImmediately_(sentence, add_immediately)
+ +
[docs] def documents(self, filter: dict = None) -> 'XACardhopDocumentList': + """Returns a list of documents, as PyXA objects, matching the filter. + + :param filter: A dictionary specifying property-value pairs that all returned documents will have + :type filter: dict + :return: The list of documents + :rtype: XACardhopDocumentList + + .. versionadded:: 0.1.0 + """ + return self._new_element(self.xa_scel.documents(), XACardhopDocumentList, filter)
+ + + + +
[docs]class XACardhopWindow(XABaseScriptable.XASBWindow, XAPrintable): + """A window of Cardhop.app. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The title of the window + self.id: int #: The unique identifier of the window + self.index: int #: The index of the window, ordered front to back + self.bounds: tuple[int, int, int, int] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.miniaturizable: bool #: Whether the window has a minimize button + self.miniaturized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window has a zoom button + self.zoomed: bool #;; Whether the window is currently zoomed + self.document: XACardhopDocument #: The document whose contents are displayed in the window + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property('index', index) + + @property + def bounds(self) -> tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def miniaturizable(self) -> bool: + return self.xa_elem.miniaturizable() + + @property + def miniaturized(self) -> bool: + return self.xa_elem.miniaturized() + + @miniaturized.setter + def miniaturized(self, miniaturized: bool): + self.set_property('miniaturized', miniaturized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property('visible', visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property('zoomed', zoomed) + + @property + def document(self) -> 'XACardhopDocument': + return self._new_element(self.xa_elem.document(), XACardhopDocument)
+ + + + +
[docs]class XACardhopDocumentList(XABase.XAList, XAPrintable, XAClipboardCodable): + """A wrapper around lists of documents that employs fast enumeration techniques. + + All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XACardhopDocument, filter) + +
[docs] def name(self) -> list[str]: + """Gets the name of each document in the list. + + :return: A list of document names + :rtype: list[str] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def modified(self) -> list[bool]: + """Gets the modified status of each document in the list. + + :return: A list of document modified status booleans + :rtype: list[bool] + + .. versionadded:: 0.1.0 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modified"))
+ +
[docs] def file(self) -> list[XABase.XAPath]: + """Gets the file path of each document in the list. + + :return: A list of document file paths + :rtype: list[XABase.XAPath] + + .. versionadded:: 0.1.0 + """ + ls = self.xa_elem.arrayByApplyingSelector_("file") + return [XABase.XAPath(x) for x in ls]
+ +
[docs] def by_name(self, name: str) -> Union['XACardhopDocument', None]: + """Retrieves the first document whose name matches the given name, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACardhopDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("name", name)
+ +
[docs] def by_modified(self, modified: bool) -> Union['XACardhopDocument', None]: + """Retrieves the first document whose modified status matches the given boolean value, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACardhopDocument, None] + + .. versionadded:: 0.1.0 + """ + return self.by_property("modified", modified)
+ +
[docs] def by_file(self, file: Union[XABase.XAPath, str]) -> Union['XACardhopDocument', None]: + """Retrieves the first document whose file path matches the given path, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XACardhopDocument, None] + + .. versionadded:: 0.1.0 + """ + if isinstance(file, str): + file = XABase.XAPath(file) + return self.by_property("file", file.xa_elem)
+ +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of each document in the list. + + When the clipboard content is set to a list of documents, each documents's file URL and name are added to the clipboard. + + :return: A list of each document's file URL and name + :rtype: list[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + items = [] + names = self.name() + paths = self.file() + for index, text in enumerate(names): + items.append(str(text), paths[index].xa_elem) + return items
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XACardhopDocument(XABase.XAObject): + """A document of Cardhop.app. + + .. versionadded:: 0.1.0 + """ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The title of the document + self.modified: bool #: Whether the document has been modified since its last save + self.file: XABase.XAPath #: The location of the document on disk, if it has one + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def modified(self) -> bool: + return self.xa_elem.modified() + + @property + def file(self) -> XABase.XAPath: + return XABase.XAPath(self.xa_elem.file())
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Chromium 2.html b/docs/_modules/PyXA/apps/Chromium 2.html new file mode 100644 index 0000000..ecf09d3 --- /dev/null +++ b/docs/_modules/PyXA/apps/Chromium 2.html @@ -0,0 +1,1116 @@ + + + + + + PyXA.apps.Chromium — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Chromium

+""".. versionadded:: 0.0.3
+
+Control Chromium using JXA-like syntax.
+"""
+
+from typing import Any, List, Tuple, Union
+
+import AppKit
+
+from PyXA import XABase
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath, XAClipboardCodable
+
+
[docs]class XAChromiumApplication(XABaseScriptable.XASBApplication, XACanOpenPath): + """A class for managing and interacting with Chromium.app. + + .. seealso:: :class:`XAChromiumWindow`, :class:`XAChromiumBookmarkFolder`, :class:`XAChromiumBookmarkItem`, :class:`XAChromiumTab` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties): + super().__init__(properties) + self.xa_wcls = XAChromiumWindow + + self.name: str #: The name of the application + self.frontmost: bool #: Whether Chromium is the active application + self.version: str #: The version of Chromium + self.bookmarks_bar: XAChromiumBookmarkFolder #: The bookmarks bar bookmark folder + self.other_bookmarks: XAChromiumBookmarkFolder #: The other bookmarks bookmark folder + + @property + def name(self) -> str: + return self.xa_scel.name() + + @property + def frontmost(self) -> bool: + return self.xa_scel.frontmost() + + @frontmost.setter + def frontmost(self, frontmost: bool): + self.set_property("frontmost", frontmost) + + @property + def version(self) -> str: + return self.xa_scel.version() + + @property + def bookmarks_bar(self) -> 'XAChromiumBookmarkFolder': + return self._new_element(self.xa_scel.bookmarksBar(), XAChromiumBookmarkFolder) + + @property + def other_bookmarks(self) -> 'XAChromiumBookmarkFolder': + return self._new_element(self.xa_scel.otherBookmarks(), XAChromiumBookmarkFolder) + +
[docs] def open(self, url: Union[str, XABase.XAURL] = "https://google.com") -> 'XAChromiumApplication': + """Opens a URL in a new tab. + + :param url: _description_, defaults to "http://google.com" + :type url: str, optional + :return: A reference to the Chromium application object. + :rtype: XAChromiumApplication + + :Example 1: Open a local or external URL + + >>> import PyXA + >>> app = PyXA.application("Chromium") + >>> app.open("https://www.google.com") + >>> app.open("google.com") + >>> app.open("/Users/exampleuser/Documents/WebPage.html") + + .. versionadded:: 0.0.3 + """ + if isinstance(url, str): + if url.startswith("/"): + # URL is a path to file + self.xa_wksp.openFile_application_(url, self.xa_scel) + return self + # Otherwise, URL is web address + elif not url.startswith("http"): + url = "http://" + url + url = XABase.XAURL(url) + self.xa_wksp.openURLs_withAppBundleIdentifier_options_additionalEventParamDescriptor_launchIdentifiers_([url.xa_elem], self.xa_elem.bundleIdentifier(), 0, None, None) + return self
+ +
[docs] def bookmark_folders(self, filter: Union[dict, None] = None) -> 'XAChromiumBookmarkFolderList': + """Returns a list of bookmark folders, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter folders by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of bookmark folders + :rtype: XAChromiumBookmarkFolderList + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_scel.bookmarkFolders(), XAChromiumBookmarkFolderList, filter)
+ +
[docs] def new_window(self, url: Union[str, XABase.XAURL, None] = None) -> 'XAChromiumWindow': + """Opens a new window at the specified URL. + + + :param url: The URL to open in a new window, or None to open the window at the homepage, defaults to None + :type url: Union[str, XABase.XAURL, None], optional + :return: The newly created window object + :rtype: XAChromiumWindow + + .. seealso:: :func:`new_tab`, :func:`make` + + .. versionadded:: 0.0.5 + """ + new_window = self.make("window") + self.windows().push(new_window) + + if isinstance(url, str): + if url.startswith("/"): + # URL is a path to file + self.xa_wksp.openFile_application_(url, self.xa_scel) + return self + # Otherwise, URL is web address + elif not url.startswith("http"): + url = "http://" + url + url = XABase.XAURL(url) + new_window.active_tab.set_property("URL", url.xa_elem) + return new_window
+ +
[docs] def new_tab(self, url: Union[str, XABase.XAURL, None] = None) -> 'XAChromiumTab': + """Opens a new tab at the specified URL. + + :param url: The URL to open in a new tab, or None to open the tab at the homepage, defaults to None + :type url: Union[str, XABase.XAURL, None], optional + :return: The newly created tab object + :rtype: XAChromiumTab + + .. seealso:: :func:`new_window`, :func:`make` + + .. versionadded:: 0.0.5 + """ + new_tab = None + if url is None: + new_tab = self.make("tab") + else: + new_tab = self.make("tab", {"URL": url}) + self.front_window.tabs().push(new_tab) + return new_tab
+ +
[docs] def make(self, specifier: str, properties: dict = None): + """Creates a new element of the given specifier class without adding it to any list. + + Use :func:`XABase.XAList.push` to push the element onto a list. + + :param specifier: The classname of the object to create + :type specifier: str + :param properties: The properties to give the object + :type properties: dict + :return: A PyXA wrapped form of the object + :rtype: XABase.XAObject + + .. seealso:: :func:`new_window`, :func:`new_tab` + + .. versionadded:: 0.0.4 + """ + if properties is None: + properties = {} + + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + + if specifier == "tab": + return self._new_element(obj, XAChromiumTab) + elif specifier == "window": + return self._new_element(obj, XAChromiumWindow)
+ + + + +
[docs]class XAChromiumWindow(XABaseScriptable.XASBWindow): + """A class for managing and interacting with Chromium windows. + + .. seealso:: :class:`XAChromiumApplication`, :class:`XAChromiumTab` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties): + super().__init__(properties) + self.given_name: str #: The given name of the window + self.name: str #: The full title of the window + self.id: int #: The unique identifier for the window + self.index: int #: The index of the window in the front-to-back ordering + self.bounds: Tuple[int, int, int, int] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.minimizable: bool #: Whether the window can be minimized + self.minimized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window can be zoomed + self.zoomed: bool #: Whether the window is currently zoomed + self.mode: str #: The mode of the window, either "normal" or "incognito" + self.active_tab_index: int #: The index of the active tab + self.active_tab: XAChromiumTab #: The currently selected tab + + @property + def given_name(self) -> str: + return self.xa_elem.givenName() + + @given_name.setter + def given_name(self, given_name: str): + self.set_property("givenName", given_name) + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property("index", index) + + @property + def bounds(self) -> Tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: Tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def minimizable(self) -> bool: + return self.xa_elem.minimizable() + + @property + def minimized(self) -> bool: + return self.xa_elem.minimized() + + @minimized.setter + def minimized(self, minimized: bool): + self.set_property("minimized", minimized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property("visible", visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property("zoomed", zoomed) + + @property + def mode(self) -> str: + return self.xa_elem.mode() + + @mode.setter + def mode(self, mode: str): + self.set_property("mode", mode) + + @property + def active_tab_index(self) -> int: + return self.xa_elem.activeTabIndex() + + @active_tab_index.setter + def active_tab_index(self, active_tab_index: int): + self.set_property("activeTabIndex", active_tab_index) + + @property + def active_tab(self) -> 'XAChromiumTab': + return self._new_element(self.xa_elem.activeTab(), XAChromiumTab) + + @active_tab.setter + def active_tab(self, active_tab: 'XAChromiumTab'): + self.set_property("activeTab", active_tab.xa_elem) + +
[docs] def new_tab(self, url: Union[str, XABase.XAURL, None] = None) -> 'XAChromiumTab': + """Opens a new tab at the specified URL. + + :param url: The URL to open in a new tab, or None to open the tab at the homepage, defaults to None + :type url: Union[str, XABase.XAURL, None], optional + :return: The newly created tab object + :rtype: XAChromiumTab + + .. versionadded:: 0.0.5 + """ + new_tab = None + if url is None: + new_tab = self.xa_prnt.xa_prnt.make("tab") + else: + new_tab = self.xa_prnt.xa_prnt.make("tab", {"URL": url}) + self.tabs().push(new_tab) + return new_tab
+ +
[docs] def tabs(self, filter: Union[dict, None] = None) -> 'XAChromiumTabList': + """Returns a list of tabs, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter tabs by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of tabs + :rtype: XAChromiumTabList + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_elem.tabs(), XAChromiumTabList, filter)
+ + + + +
[docs]class XAChromiumTabList(XABase.XAList, XAClipboardCodable): + """A wrapper around a list of tabs. + + .. seealso:: :class:`XAChromiumTab` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAChromiumTab, filter) + +
[docs] def id(self) -> List[int]: + """Gets the ID of each tab in the list. + + :return: A list of tab IDs + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def title(self) -> List[str]: + """Gets the title of each tab in the list. + + :return: A list of tab titles + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("title"))
+ +
[docs] def url(self) -> List[XABase.XAURL]: + """Gets the URL of each tab in the list. + + :return: A list of tab URLS + :rtype: List[XABase.XAURL] + + .. versionadded:: 0.0.4 + """ + ls = self.xa_elem.arrayByApplyingSelector_("URL") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def loading(self) -> List[bool]: + """Gets the loading state of each tab in the list. + + :return: A list of loading states; a list of booleans. + :rtype: List[bool] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("loading"))
+ +
[docs] def by_id(self, id: int) -> Union['XAChromiumTab', None]: + """Retrieves the tab whose ID matches the given ID, if one exists. + + :return: The desired tab, if it is found + :rtype: Union[XAChromiumTab, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("id", id)
+ +
[docs] def by_title(self, title: str) -> Union['XAChromiumTab', None]: + """Retrieves the first tab whose title matches the given title, if one exists. + + :return: The desired tab, if it is found + :rtype: Union[XAChromiumTab, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("title", title)
+ +
[docs] def by_url(self, url: XABase.XAURL) -> Union['XAChromiumTab', None]: + """Retrieves the first tab whose URL matches the given URL, if one exists. + + :return: The desired tab, if it is found + :rtype: Union[XAChromiumTab, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("url", str(url.xa_elem))
+ +
[docs] def by_loading(self, loading: bool) -> Union['XAChromiumTab', None]: + """Retrieves the first tab whose loading state matches the given boolean value, if one exists. + + :return: The desired tab, if it is found + :rtype: Union[XAChromiumTab, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("loading", loading)
+ +
[docs] def get_clipboard_representation(self) -> List[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of each tab in the list. + + When the clipboard content is set to a list of Chromium tabs, each tab's URL is added to the clipboard. + + :return: A list of tab URLs + :rtype: List[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + items = [] + titles = self.title() + urls = self.url() + for index, title in enumerate(titles): + items.append(title) + items.append(urls[index].xa_elem) + return items
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title()) + ">"
+ +
[docs]class XAChromiumTab(XABase.XAObject, XAClipboardCodable): + """A class for managing and interacting with Chromium tabs. + + .. seealso:: :class:`XAChromiumWindow`, :class:`XAChromiumTabList`, :class:`XAChromiumWindow` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties): + super().__init__(properties) + self.id: int + self.title: str + self.url: XABase.XAURL + self.loading: bool + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def title(self) -> str: + return self.xa_elem.title() + + @property + def url(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.URL()) + + @url.setter + def url(self, url: XABase.XAURL): + self.set_property("URL", url.url) + + @property + def loading(self) -> bool: + return self.xa_elem.loading() + +
[docs] def undo(self) -> 'XAChromiumTab': + """Undoes the last action done on the tab. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.undo() + return self
+ +
[docs] def redo(self) -> 'XAChromiumTab': + """Redoes the last action done on the tab. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.redo() + return self
+ +
[docs] def cut_selection(self) -> 'XAChromiumTab': + """Attempts to cut the selected content and copy it to the clipboard. If the content cannot be deleted, then it is only copied to the clipboard. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.cutSelection() + return self
+ +
[docs] def copy_selection(self) -> 'XAChromiumTab': + """Copies the selected element to the clipboard. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.copySelection() + return self
+ +
[docs] def paste_selection(self) -> 'XAChromiumTab': + """Attempts to paste the clipboard into the selected element. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.pasteSelection() + return self
+ +
[docs] def select_all(self) -> 'XAChromiumTab': + """Selects all text content within the tab. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.selectAll() + return self
+ +
[docs] def go_back(self) -> 'XAChromiumTab': + """Goes to the previous URL in the tab's history. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.goBack() + return self
+ +
[docs] def go_forward(self) -> 'XAChromiumTab': + """Goes to the next URL in the tab's history, or does nothing if the current document is the most recent URL. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.goForward() + return self
+ +
[docs] def reload(self) -> 'XAChromiumTab': + """Reloads the tab. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.reload() + return self
+ +
[docs] def stop(self) -> 'XAChromiumTab': + """Forces the tab to stop loading. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.stop() + return self
+ +
[docs] def print(self) -> 'XAChromiumTab': + """Opens the print dialog for the tab. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.print() + return self
+ +
[docs] def view_source(self) -> 'XAChromiumTab': + """Opens the source HTML of the tab's document in a separate tab. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.viewSource() + return self
+ +
[docs] def save(self, file_path: Union[str, AppKit.NSURL], save_assets: bool = True) -> 'XAChromiumTab': + if isinstance(file_path, str): + file_path = AppKit.NSURL.alloc().initFileURLWithPath_(file_path) + if save_assets: + self.xa_elem.saveIn_as_(file_path, "complete html") + else: + self.xa_elem.saveIn_as_(file_path, "only html") + return self
+ +
[docs] def close(self) -> 'XAChromiumTab': + """Closes the tab. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.close() + return self
+ +
[docs] def execute(self, script: str) -> Any: + """Executes JavaScript in the tab. + + .. versionadded:: 0.0.4 + """ + return self.xa_elem.executeJavascript_(script)
+ +
[docs] def move_to(self, window: 'XAChromiumWindow') -> 'XAChromiumWindow': + """Moves the tab to the specified window. After, the tab will exist in only one location. + + :param window: The window to move the tab to. + :type window: XASafariWindow + :return: A reference to the tab object. + :rtype: XASafariGeneric + + :Example 1: Move the current tab to the second window + + >>> import PyXA + >>> app = PyXA.application("Chromium") + >>> tab = app.front_window.active_tab + >>> window2 = app.window(1) + >>> tab.move_to(window2) + + .. seealso:: :func:`duplicate_to` + + .. versionadded:: 0.0.1 + """ + current = self.xa_elem.get() + properties = {"URL": self.url} + if isinstance(self.xa_prnt, XABase.XAList): + new_tab = self.xa_prnt.xa_prnt.xa_prnt.make("tab", properties) + else: + new_tab = self.xa_prnt.xa_prnt.make("tab", properties) + window.tabs().push(new_tab) + current.close() + return self
+ +
[docs] def duplicate_to(self, window: 'XAChromiumWindow') -> 'XAChromiumWindow': + """Duplicates the tab in the specified window. The tab will then exist in two locations. + + :param window: The window to duplicate the tab in. + :type window: XASafariWindow + :return: A reference to the tab object. + :rtype: XASafariTab + + :Example 1: Duplicate the current tab in the second window + + >>> import PyXA + >>> app = PyXA.application("Chromium") + >>> tab = app.front_window.active_tab + >>> window2 = app.window(1) + >>> tab.duplicate_to(window2) + + .. seealso:: :func:`move_to` + + .. versionadded:: 0.0.1 + """ + properties = {"URL": self.url} + + new_tab = None + print(self.xa_prnt) + if isinstance(self.xa_prnt, XABase.XAList): + new_tab = self.xa_prnt.xa_prnt.xa_prnt.make("tab", properties) + else: + new_tab = self.xa_prnt.xa_prnt.make("tab", properties) + window.tabs().push(new_tab) + return self
+ +
[docs] def get_clipboard_representation(self) -> List[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of the tab. + + When the clipboard content is set to a Chromium tab, the tab's title and URL are added to the clipboard. + + :return: The tab's title and URL + :rtype: List[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + return [self.title, self.url.xa_elem]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title) + ">"
+ + + + +
[docs]class XAChromiumBookmarkFolderList(XABase.XAList, XAClipboardCodable): + """A wrapper around a list of bookmark folders. + + .. seealso:: :class:`XAChromiumBookmarkFolder` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAChromiumBookmarkFolder, filter) + +
[docs] def id(self) -> List[int]: + """Gets the ID of each bookmark folder in the list. + + :return: A list of bookmark folder IDs + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def title(self) -> List[str]: + """Gets the title of each bookmark folder in the list. + + :return: A list of bookmark folder titles + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("title"))
+ +
[docs] def index(self) -> List[int]: + """Gets the index of each bookmark folder in the list. + + :return: A list of indexes + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("index"))
+ +
[docs] def by_id(self, id: int) -> Union['XAChromiumBookmarkFolder', None]: + """Retrieves the bookmark folder whose ID matches the given ID, if one exists. + + :return: The desired bookmark folder, if it is found + :rtype: Union[XAChromiumBookmarkFolder, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("id", id)
+ +
[docs] def by_title(self, title: str) -> Union['XAChromiumBookmarkFolder', None]: + """Retrieves the first bookmark folder whose title matches the given title, if one exists. + + :return: The desired bookmark folder, if it is found + :rtype: Union[XAChromiumBookmarkFolder, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("title", title)
+ +
[docs] def by_index(self, index: int) -> Union['XAChromiumBookmarkFolder', None]: + """Retrieves the bookmark folder whose index matches the given index, if one exists. + + :return: The desired bookmark folder, if it is found + :rtype: Union[XAChromiumBookmarkFolder, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("index", index)
+ +
[docs] def get_clipboard_representation(self) -> List[str]: + """Gets a clipboard-codable representation of each bookmark folder in the list. + + When the clipboard content is set to a list of bookmark folders, each folder's title is added to the clipboard. + + :return: The list of each bookmark folder's title + :rtype: List[str] + + .. versionadded:: 0.0.8 + """ + return self.title()
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title()) + ">"
+ +
[docs]class XAChromiumBookmarkFolder(XABase.XAObject, XAClipboardCodable): + """A class for managing and interacting with bookmark folders in Chromium.app. + + .. seealso:: :class:`XAChromiumApplication`, :class:`XAChromiumBookmarkFolderList` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties): + super().__init__(properties) + self.id: int #: The unique identifier for the bookmark folder + self.title: str #: The name of the bookmark folder + self.index: int #: The index of the bookmark folder with respect to its parent folder + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def title(self) -> str: + return self.xa_elem.title() + + @title.setter + def title(self, title: str): + self.set_property("title", title) + + @property + def index(self) -> int: + return self.xa_elem.index() + +
[docs] def bookmark_folders(self, filter: Union[dict, None] = None) -> 'XAChromiumBookmarkFolderList': + """Returns a list of bookmark folders, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter folders by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of bookmark folders + :rtype: XAChromiumBookmarkFolderList + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_elem.bookmarkFolders(), XAChromiumBookmarkFolderList, filter)
+ +
[docs] def bookmark_items(self, filter: Union[dict, None] = None) -> 'XAChromiumBookmarkItemList': + """Returns a list of bookmark items, as PyXA objects, matching the given filter. + + :param filter: Keys and values to filter items by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of bookmark items + :rtype: XAChromiumBookmarkItemList + + .. versionadded:: 0.0.3 + """ + return self._new_element(self.xa_elem.bookmarkItems(), XAChromiumBookmarkItemList, filter)
+ +
[docs] def delete(self): + """Permanently deletes the bookmark folder. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.delete()
+ +
[docs] def get_clipboard_representation(self) -> str: + """Gets a clipboard-codable representation of the bookmark folder. + + When the clipboard content is set to a bookmark folder, the folders's title is added to the clipboard. + + :return: The bookmark folders's title + :rtype: str + + .. versionadded:: 0.0.8 + """ + return self.title
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title) + ">"
+ + + + +
[docs]class XAChromiumBookmarkItemList(XABase.XAList, XAClipboardCodable): + """A wrapper around a list of bookmark items. + + .. seealso:: :class:`XAChromiumBookmarkItem` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAChromiumBookmarkItem, filter) + +
[docs] def id(self) -> List[int]: + """Gets the ID of each item in the list. + + :return: A list of bookmark item IDs + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def title(self) -> List[str]: + """Gets the title of each item in the list. + + :return: A list of bookmark item titles + :rtype: List[str] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("title"))
+ +
[docs] def url(self) -> List[XABase.XAURL]: + """Gets the url of each item in the list. + + :return: A list of bookmark item URLs + :rtype: List[XABase.XAURL] + + .. versionadded:: 0.0.4 + """ + ls = self.xa_elem.arrayByApplyingSelector_("URL") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def index(self) -> List[int]: + """Gets the index of each item in the list. + + :return: A list of indexes + :rtype: List[int] + + .. versionadded:: 0.0.4 + """ + return list(self.xa_elem.arrayByApplyingSelector_("index"))
+ +
[docs] def by_id(self, id: int) -> Union['XAChromiumBookmarkItem', None]: + """Retrieves the bookmark item whose ID matches the given ID, if one exists. + + :return: The desired bookmark item, if it is found + :rtype: Union[XAChromiumBookmarkItem, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("id", id)
+ +
[docs] def by_title(self, title: str) -> Union['XAChromiumBookmarkItem', None]: + """Retrieves the first bookmark item whose title matches the given title, if one exists. + + :return: The desired bookmark item, if it is found + :rtype: Union[XAChromiumBookmarkItem, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("title", title)
+ +
[docs] def by_url(self, url: XABase.XAURL) -> Union['XAChromiumBookmarkItem', None]: + """Retrieves the first bookmark item whose URL matches the given URL, if one exists. + + :return: The desired bookmark item, if it is found + :rtype: Union[XAChromiumBookmarkItem, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("URL", str(url.xa_elem))
+ +
[docs] def by_index(self, index: int) -> Union['XAChromiumBookmarkItem', None]: + """Retrieves the bookmark item whose index matches the given index, if one exists. + + :return: The desired bookmark item, if it is found + :rtype: Union[XAChromiumBookmarkItem, None] + + .. versionadded:: 0.0.4 + """ + return self.by_property("index", index)
+ +
[docs] def get_clipboard_representation(self) -> List[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of each bookmark item in the list. + + When the clipboard content is set to a list of bookmark items, each item's title and URL are added to the clipboard. + + :return: The list of each bookmark items's title and URL + :rtype: List[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + items = [] + titles = self.title() + urls = self.url() + for index, title in enumerate(titles): + items.append(title) + items.append(urls[index].xa_elem) + return items
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title()) + ">"
+ +
[docs]class XAChromiumBookmarkItem(XABase.XAObject, XAClipboardCodable): + """A class for managing and interacting with bookmarks in Chromium.app. + + .. seealso:: :class:`XAChromiumApplication`, :class:`XAChromiumBookmarkItemList` + + .. versionadded:: 0.0.3 + """ + def __init__(self, properties): + super().__init__(properties) + self.id: int #: The unique identifier for the bookmark item + self.title: str #: The title of the bookmark item + self.url: XABase.XAURL #: The URL of the bookmark + self.index: int #: The index of the item with respect to its parent folder + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def title(self) -> str: + return self.xa_elem.title() + + @title.setter + def title(self, title: str): + self.set_property("title", title) + + @property + def url(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.URL()) + + @url.setter + def url(self, url: XABase.XAURL): + self.set_property("URL", url.url) + + @property + def index(self) -> int: + return self.xa_elem.index() + +
[docs] def delete(self): + """Permanently deletes the bookmark. + + .. versionadded:: 0.0.4 + """ + self.xa_elem.delete()
+ +
[docs] def get_clipboard_representation(self) -> List[Union[str, AppKit.NSURL]]: + """Gets a clipboard-codable representation of the bookmark item. + + When the clipboard content is set to a bookmark item, the item's title and URL are added to the clipboard. + + :return: The bookmark items's title and URL + :rtype: List[Union[str, AppKit.NSURL]] + + .. versionadded:: 0.0.8 + """ + return [self.title, self.url.xa_elem]
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.title) + ">"
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Chromium.html b/docs/_modules/PyXA/apps/Chromium.html index 9979bf9..3f50212 100644 --- a/docs/_modules/PyXA/apps/Chromium.html +++ b/docs/_modules/PyXA/apps/Chromium.html @@ -3,7 +3,7 @@ - PyXA.apps.Chromium — PyXA 0.0.9 documentation + PyXA.apps.Chromium — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -73,8 +75,9 @@

Source code for PyXA.apps.Chromium

 Control Chromium using JXA-like syntax.
 """
 
-from typing import Any, List, Tuple, Union
-from AppKit import NSURL
+from typing import Any, Union
+
+import AppKit
 
 from PyXA import XABase
 from PyXA import XABaseScriptable
@@ -105,6 +108,10 @@ 

Source code for PyXA.apps.Chromium

     def frontmost(self) -> bool:
         return self.xa_scel.frontmost()
 
+    @frontmost.setter
+    def frontmost(self, frontmost: bool):
+        self.set_property("frontmost", frontmost)
+
     @property
     def version(self) -> str:
         return self.xa_scel.version()
@@ -128,7 +135,7 @@ 

Source code for PyXA.apps.Chromium

         :Example 1: Open a local or external URL
 
            >>> import PyXA
-           >>> app = PyXA.application("Chromium")
+           >>> app = PyXA.Application("Chromium")
            >>> app.open("https://www.google.com")
            >>> app.open("google.com")
            >>> app.open("/Users/exampleuser/Documents/WebPage.html")
@@ -249,7 +256,7 @@ 

Source code for PyXA.apps.Chromium

         self.name: str #: The full title of the window
         self.id: int #: The unique identifier for the window
         self.index: int #: The index of the window in the front-to-back ordering
-        self.bounds: Tuple[Tuple[int, int], Tuple[int, int]] #: The bounding rectangle of the window
+        self.bounds: tuple[int, int, int, int] #: The bounding rectangle of the window
         self.closeable: bool #: Whether the window has a close button
         self.minimizable: bool #: Whether the window can be minimized
         self.minimized: bool #: Whether the window is currently minimized
@@ -265,6 +272,10 @@ 

Source code for PyXA.apps.Chromium

     def given_name(self) -> str:
         return self.xa_elem.givenName()
 
+    @given_name.setter
+    def given_name(self, given_name: str):
+        self.set_property("givenName", given_name)
+
     @property
     def name(self) -> str:
         return self.xa_elem.name()
@@ -277,9 +288,25 @@ 

Source code for PyXA.apps.Chromium

     def index(self) -> int:
         return self.xa_elem.index()
 
+    @index.setter
+    def index(self, index: int):
+        self.set_property("index", index)
+
     @property
-    def bounds(self) -> Tuple[Tuple[int, int], Tuple[int, int]]:
-        return self.xa_elem.bounds()
+    def bounds(self) -> tuple[int, int, int, int]:
+        rect = self.xa_elem.bounds()
+        origin = rect.origin
+        size = rect.size
+        return (origin.x, origin.y, size.width, size.height)
+
+    @bounds.setter
+    def bounds(self, bounds: tuple[int, int, int, int]):
+        x = bounds[0]
+        y = bounds[1]
+        w = bounds[2]
+        h = bounds[3]
+        value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h))
+        self.set_property("bounds", value)
 
     @property
     def closeable(self) -> bool:
@@ -293,6 +320,10 @@ 

Source code for PyXA.apps.Chromium

     def minimized(self) -> bool:
         return self.xa_elem.minimized()
 
+    @minimized.setter
+    def minimized(self, minimized: bool):
+        self.set_property("minimized", minimized)
+
     @property
     def resizable(self) -> bool:
         return self.xa_elem.resizable()
@@ -301,6 +332,10 @@ 

Source code for PyXA.apps.Chromium

     def visible(self) -> bool:
         return self.xa_elem.visible()
 
+    @visible.setter
+    def visible(self, visible: bool):
+        self.set_property("visible", visible)
+
     @property
     def zoomable(self) -> bool:
         return self.xa_elem.zoomable()
@@ -309,18 +344,34 @@ 

Source code for PyXA.apps.Chromium

     def zoomed(self) -> bool:
         return self.xa_elem.zoomed()
 
+    @zoomed.setter
+    def zoomed(self, zoomed: bool):
+        self.set_property("zoomed", zoomed)
+
     @property
     def mode(self) -> str:
         return self.xa_elem.mode()
 
+    @mode.setter
+    def mode(self, mode: str):
+        self.set_property("mode", mode)
+
     @property
     def active_tab_index(self) -> int:
         return self.xa_elem.activeTabIndex() 
 
+    @active_tab_index.setter
+    def active_tab_index(self, active_tab_index: int):
+        self.set_property("activeTabIndex", active_tab_index)
+
     @property
     def active_tab(self) -> 'XAChromiumTab':
         return self._new_element(self.xa_elem.activeTab(), XAChromiumTab)
 
+    @active_tab.setter
+    def active_tab(self, active_tab: 'XAChromiumTab'):
+        self.set_property("activeTab", active_tab.xa_elem)
+
 
[docs] def new_tab(self, url: Union[str, XABase.XAURL, None] = None) -> 'XAChromiumTab': """Opens a new tab at the specified URL. @@ -364,42 +415,42 @@

Source code for PyXA.apps.Chromium

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAChromiumTab, filter)
 
-
[docs] def id(self) -> List[int]: +
[docs] def id(self) -> list[int]: """Gets the ID of each tab in the list. :return: A list of tab IDs - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def title(self) -> List[str]: +
[docs] def title(self) -> list[str]: """Gets the title of each tab in the list. :return: A list of tab titles - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("title"))
-
[docs] def url(self) -> List[XABase.XAURL]: +
[docs] def url(self) -> list[XABase.XAURL]: """Gets the URL of each tab in the list. :return: A list of tab URLS - :rtype: List[XABase.XAURL] + :rtype: list[XABase.XAURL] .. versionadded:: 0.0.4 """ ls = self.xa_elem.arrayByApplyingSelector_("URL") return [XABase.XAURL(x) for x in ls]
-
[docs] def loading(self) -> List[bool]: +
[docs] def loading(self) -> list[bool]: """Gets the loading state of each tab in the list. :return: A list of loading states; a list of booleans. - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.4 """ @@ -445,13 +496,13 @@

Source code for PyXA.apps.Chromium

         """
         return self.by_property("loading", loading)
-
[docs] def get_clipboard_representation(self) -> List[Union[str, NSURL]]: +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: """Gets a clipboard-codable representation of each tab in the list. When the clipboard content is set to a list of Chromium tabs, each tab's URL is added to the clipboard. :return: A list of tab URLs - :rtype: List[Union[str, NSURL]] + :rtype: list[Union[str, AppKit.NSURL]] .. versionadded:: 0.0.8 """ @@ -492,6 +543,10 @@

Source code for PyXA.apps.Chromium

     def url(self) -> XABase.XAURL:
         return XABase.XAURL(self.xa_elem.URL())
 
+    @url.setter
+    def url(self, url: XABase.XAURL):
+        self.set_property("URL", url.url)
+
     @property
     def loading(self) -> bool:
         return self.xa_elem.loading()
@@ -592,9 +647,9 @@ 

Source code for PyXA.apps.Chromium

         self.xa_elem.viewSource()
         return self
-
[docs] def save(self, file_path: Union[str, NSURL], save_assets: bool = True) -> 'XAChromiumTab': +
[docs] def save(self, file_path: Union[str, AppKit.NSURL], save_assets: bool = True) -> 'XAChromiumTab': if isinstance(file_path, str): - file_path = NSURL.alloc().initFileURLWithPath_(file_path) + file_path = AppKit.NSURL.alloc().initFileURLWithPath_(file_path) if save_assets: self.xa_elem.saveIn_as_(file_path, "complete html") else: @@ -627,9 +682,9 @@

Source code for PyXA.apps.Chromium

         :Example 1: Move the current tab to the second window
 
         >>> import PyXA
-        >>> app = PyXA.application("Chromium")
+        >>> app = PyXA.Application("Chromium")
         >>> tab = app.front_window.active_tab
-        >>> window2 = app.window(1)
+        >>> window2 = app.windows()[1]
         >>> tab.move_to(window2)
 
         .. seealso:: :func:`duplicate_to`
@@ -657,9 +712,9 @@ 

Source code for PyXA.apps.Chromium

         :Example 1: Duplicate the current tab in the second window
 
         >>> import PyXA
-        >>> app = PyXA.application("Chromium")
+        >>> app = PyXA.Application("Chromium")
         >>> tab = app.front_window.active_tab
-        >>> window2 = app.window(1)
+        >>> window2 = app.windows()[1]
         >>> tab.duplicate_to(window2)
 
         .. seealso:: :func:`move_to`
@@ -677,13 +732,13 @@ 

Source code for PyXA.apps.Chromium

         window.tabs().push(new_tab)
         return self
-
[docs] def get_clipboard_representation(self) -> List[Union[str, NSURL]]: +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: """Gets a clipboard-codable representation of the tab. When the clipboard content is set to a Chromium tab, the tab's title and URL are added to the clipboard. :return: The tab's title and URL - :rtype: List[Union[str, NSURL]] + :rtype: list[Union[str, AppKit.NSURL]] .. versionadded:: 0.0.8 """ @@ -705,31 +760,31 @@

Source code for PyXA.apps.Chromium

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAChromiumBookmarkFolder, filter)
 
-
[docs] def id(self) -> List[int]: +
[docs] def id(self) -> list[int]: """Gets the ID of each bookmark folder in the list. :return: A list of bookmark folder IDs - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def title(self) -> List[str]: +
[docs] def title(self) -> list[str]: """Gets the title of each bookmark folder in the list. :return: A list of bookmark folder titles - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("title"))
-
[docs] def index(self) -> List[int]: +
[docs] def index(self) -> list[int]: """Gets the index of each bookmark folder in the list. :return: A list of indexes - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ @@ -765,13 +820,13 @@

Source code for PyXA.apps.Chromium

         """
         return self.by_property("index", index)
-
[docs] def get_clipboard_representation(self) -> List[str]: +
[docs] def get_clipboard_representation(self) -> list[str]: """Gets a clipboard-codable representation of each bookmark folder in the list. When the clipboard content is set to a list of bookmark folders, each folder's title is added to the clipboard. :return: The list of each bookmark folder's title - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.8 """ @@ -801,6 +856,10 @@

Source code for PyXA.apps.Chromium

     def title(self) -> str:
         return self.xa_elem.title()
 
+    @title.setter
+    def title(self, title: str):
+        self.set_property("title", title)
+
     @property
     def index(self) -> int:
         return self.xa_elem.index()
@@ -864,42 +923,42 @@ 

Source code for PyXA.apps.Chromium

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAChromiumBookmarkItem, filter)
 
-
[docs] def id(self) -> List[int]: +
[docs] def id(self) -> list[int]: """Gets the ID of each item in the list. :return: A list of bookmark item IDs - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def title(self) -> List[str]: +
[docs] def title(self) -> list[str]: """Gets the title of each item in the list. :return: A list of bookmark item titles - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.4 """ return list(self.xa_elem.arrayByApplyingSelector_("title"))
-
[docs] def url(self) -> List[XABase.XAURL]: +
[docs] def url(self) -> list[XABase.XAURL]: """Gets the url of each item in the list. :return: A list of bookmark item URLs - :rtype: List[XABase.XAURL] + :rtype: list[XABase.XAURL] .. versionadded:: 0.0.4 """ ls = self.xa_elem.arrayByApplyingSelector_("URL") return [XABase.XAURL(x) for x in ls]
-
[docs] def index(self) -> List[int]: +
[docs] def index(self) -> list[int]: """Gets the index of each item in the list. :return: A list of indexes - :rtype: List[int] + :rtype: list[int] .. versionadded:: 0.0.4 """ @@ -945,13 +1004,13 @@

Source code for PyXA.apps.Chromium

         """
         return self.by_property("index", index)
-
[docs] def get_clipboard_representation(self) -> List[Union[str, NSURL]]: +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: """Gets a clipboard-codable representation of each bookmark item in the list. When the clipboard content is set to a list of bookmark items, each item's title and URL are added to the clipboard. :return: The list of each bookmark items's title and URL - :rtype: List[Union[str, NSURL]] + :rtype: list[Union[str, AppKit.NSURL]] .. versionadded:: 0.0.8 """ @@ -988,10 +1047,18 @@

Source code for PyXA.apps.Chromium

     def title(self) -> str:
         return self.xa_elem.title()
 
+    @title.setter
+    def title(self, title: str):
+        self.set_property("title", title)
+
     @property
     def url(self) -> XABase.XAURL:
         return XABase.XAURL(self.xa_elem.URL())
 
+    @url.setter
+    def url(self, url: XABase.XAURL):
+        self.set_property("URL", url.url)
+
     @property
     def index(self) -> int:
         return self.xa_elem.index()
@@ -1003,13 +1070,13 @@ 

Source code for PyXA.apps.Chromium

         """
         self.xa_elem.delete()
-
[docs] def get_clipboard_representation(self) -> List[Union[str, NSURL]]: +
[docs] def get_clipboard_representation(self) -> list[Union[str, AppKit.NSURL]]: """Gets a clipboard-codable representation of the bookmark item. When the clipboard content is set to a bookmark item, the item's title and URL are added to the clipboard. :return: The bookmark items's title and URL - :rtype: List[Union[str, NSURL]] + :rtype: list[Union[str, AppKit.NSURL]] .. versionadded:: 0.0.8 """ diff --git a/docs/_modules/PyXA/apps/Console 2.html b/docs/_modules/PyXA/apps/Console 2.html new file mode 100644 index 0000000..d7244db --- /dev/null +++ b/docs/_modules/PyXA/apps/Console 2.html @@ -0,0 +1,124 @@ + + + + + + PyXA.apps.Console — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Console

+""".. versionadded:: 0.0.5
+
+Control the macOS Console application using JXA-like syntax.
+"""
+
+from enum import Enum
+from typing import List, Tuple, Union
+from AppKit import NSFileManager, NSURL
+
+from PyXA import XABase
+from PyXA.XABase import OSType
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath
+
+
[docs]class XAConsoleApplication(XABaseScriptable.XASBApplication): + """A class for managing and interacting with Console.app. + + .. versionadded:: 0.0.5 + """ +
[docs] def select_device(self, uuid: str) -> 'XAConsoleApplication': + self.xa_scel.selectDevice_(uuid) + return self
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Console.html b/docs/_modules/PyXA/apps/Console.html index 7a8a84a..d7244db 100644 --- a/docs/_modules/PyXA/apps/Console.html +++ b/docs/_modules/PyXA/apps/Console.html @@ -14,7 +14,9 @@ + + diff --git a/docs/_modules/PyXA/apps/Contacts 2.html b/docs/_modules/PyXA/apps/Contacts 2.html new file mode 100644 index 0000000..e266693 --- /dev/null +++ b/docs/_modules/PyXA/apps/Contacts 2.html @@ -0,0 +1,2256 @@ + + + + + + PyXA.apps.Contacts — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Contacts

+""".. versionadded:: 0.0.2
+
+Control the macOS Contacts application using JXA-like syntax.
+"""
+from datetime import datetime
+from enum import Enum
+from typing import Any, List, Tuple, Union
+
+import AppKit
+
+from PyXA import XABase, XAEvents
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath
+
+
+
[docs]class XAContactsApplication(XABaseScriptable.XASBApplication, XACanOpenPath): + """A class for managing and interacting with Contacts.app. + + .. seealso:: :class:`XAContactsGroup`, :class:`XAContactsPerson` + + .. versionadded:: 0.0.2 + """ +
[docs] class Format(Enum): + """Format options when saving documents. + """ + ARCHIVE = XABase.OSType('abbu') #: The native Address Book file format
+ +
[docs] class ServiceType(Enum): + """Service types for social media accounts. + """ + AIM = XABase.OSType('az85') + GADU_GADU = XABase.OSType('az86') + GOOGLE_TALK = XABase.OSType('az87') + ICQ = XABase.OSType('az88') + JABBER = XABase.OSType('az89') + MSN = XABase.OSType('az90') + QQ = XABase.OSType('az91') + SKYPE = XABase.OSType('az92') + YAHOO = XABase.OSType('az93') + FACEBOOK = XABase.OSType('az94')
+ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The name of the application + self.frontmost: bool #: Whether Contacts is the frontmost application + self.version: str #: The version of Contacts.app + self.my_card: XAContactsPerson #: The user's contact card + self.unsaved: bool #: Whether there are any unsaved changed + self.selection: XAContactsPersonList #: The currently selected entries + self.default_country_code: str #: The default country code for addresses + + @property + def name(self) -> str: + return self.xa_scel.name() + + @property + def frontmost(self) -> bool: + return self.xa_scel.frontmost() + + @frontmost.setter + def frontmost(self, frontmost: bool): + self.set_property('frontmost', frontmost) + + @property + def version(self) -> str: + return self.xa_scel.version() + + @property + def my_card(self) -> 'XAContactsPerson': + return self._new_element(self.xa_scel.myCard(), XAContactsPerson) + + @my_card.setter + def my_card(self, my_card: 'XAContactsPerson'): + self.set_property('myCard', my_card) + + @property + def unsaved(self) -> bool: + return self.xa_scel.unsaved() + + @property + def selection(self) -> 'XAContactsPersonList': + return self._new_element(self.xa_scel.selection(), XAContactsPersonList) + + @selection.setter + def selection(self, selection: Union['XAContactsPersonList', List['XAContactsPerson']]): + if isinstance(selection, list): + selection = [x.xa_elem for x in selection] + self.set_property("selection", selection) + else: + self.set_property('selection', selection.xa_elem) + + @property + def default_country_code(self) -> str: + return self.xa_scel.defaultCountryCode() + +
[docs] def open(self, file_path: str): + """Opens a document and prompts whether to import the contact(s) contained in the document. + + :param file_path: The path to the file to import + :type file_path: str + + .. versionadded:: 0.0.7 + """ + self.xa_scel.open_(file_path)
+ +
[docs] def save(self): + """Saves all changes to the address book. + + .. versionadded:: 0.0.7 + """ + self.xa_scel.save()
+ +
[docs] def documents(self, filter: Union[dict, None] = None) -> 'XAContactsDocumentList': + """Returns a list of documents, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned documents will have, or None + :type filter: Union[dict, None] + :return: The list of documents + :rtype: XAContactsDocumentList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_scel.documents(), XAContactsDocumentList, filter)
+ +
[docs] def groups(self, filter: Union[dict, None] = None) -> 'XAContactsGroupList': + """Returns a list of groups, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned groups will have, or None + :type filter: Union[dict, None] + :return: The list of groups + :rtype: XAContactsGroupList + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Contacts") + >>> print(app.groups()) + <<class 'PyXA.apps.Contacts.XAContactsGroupList'>['Example Group 1', 'Example Group 2', ...]> + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_scel.groups(), XAContactsGroupList, filter)
+ +
[docs] def people(self, filter: Union[dict, None] = None) -> 'XAContactsPersonList': + """Returns a list of people, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned people will have, or None + :type filter: Union[dict, None] + :return: The list of people + :rtype: XAContactsPersonList + + :Example: + + >>> import PyXA + >>> app = PyXA.application("Contacts") + >>> print(app.people()) + <<class 'PyXA.apps.Contacts.XAContactsPersonList'>['Example Contact 1', 'Example Contact 2', ...]> + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_scel.people(), XAContactsPersonList, filter)
+ +
[docs] def make(self, specifier: str, properties: dict = None): + """Creates a new element of the given specifier class without adding it to any list. + + Use :func:`XABase.XAList.push` to push the element onto a list. + + :param specifier: The classname of the object to create + :type specifier: str + :param properties: The properties to give the object + :type properties: dict + :return: A PyXA wrapped form of the object + :rtype: XABase.XAObject + + :Example 1: Add a URL to a contact + + >>> import PyXA + >>> app = PyXA.application("Contacts") + >>> contact = app.people().by_name("Example Contact") + >>> new_url = app.make("url", {"label": "Google", "value": "www.google.com"}) + >>> contact.urls().push(new_url) + >>> app.save() + + .. versionadded:: 0.0.7 + """ + if properties is None: + properties = {} + + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + + if specifier == "document": + return self._new_element(obj, XAContactsDocument) + elif specifier == "person": + return self._new_element(obj, XAContactsPerson) + elif specifier == "group": + return self._new_element(obj, XAContactsGroup) + elif specifier == "url": + return self._new_element(obj, XAContactsURL)
+ + + + +
[docs]class XAContactsWindow(XABaseScriptable.XASBWindow): + """A window of Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.name: str #: The title of the window + self.id: int #: The unique identifier for the window + self.index: int #: The index of the window in the front-to-back ordering + self.bounds: Tuple[int, int, int, int] #: The bounding rectangle of the window + self.closeable: bool #: Whether the window has a close button + self.miniaturizable: bool #: Whether the window can be minimized + self.miniaturized: bool #: Whether the window is currently minimized + self.resizable: bool #: Whether the window can be resized + self.visible: bool #: Whether the window is currently visible + self.zoomable: bool #: Whether the window can be zoomed + self.zoomed: bool #: Whether the window is currently zoomed + self.document: XAContactsDocument #: The documents currently displayed in the window + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def id(self) -> int: + return self.xa_elem.id() + + @property + def index(self) -> int: + return self.xa_elem.index() + + @index.setter + def index(self, index: int): + self.set_property('index', index) + + @property + def bounds(self) -> Tuple[int, int, int, int]: + rect = self.xa_elem.bounds() + origin = rect.origin + size = rect.size + return (origin.x, origin.y, size.width, size.height) + + @bounds.setter + def bounds(self, bounds: Tuple[int, int, int, int]): + x = bounds[0] + y = bounds[1] + w = bounds[2] + h = bounds[3] + value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h)) + self.set_property("bounds", value) + + @property + def closeable(self) -> bool: + return self.xa_elem.closeable() + + @property + def miniaturizable(self) -> bool: + return self.xa_elem.miniaturizable() + + @property + def miniaturized(self) -> bool: + return self.xa_elem.miniaturized() + + @miniaturized.setter + def miniaturized(self, miniaturized: bool): + self.set_property('miniaturized', miniaturized) + + @property + def resizable(self) -> bool: + return self.xa_elem.resizable() + + @property + def visible(self) -> bool: + return self.xa_elem.visible() + + @visible.setter + def visible(self, visible: bool): + self.set_property('visible', visible) + + @property + def zoomable(self) -> bool: + return self.xa_elem.zoomable() + + @property + def zoomed(self) -> bool: + return self.xa_elem.zoomed() + + @zoomed.setter + def zoomed(self, zoomed: bool): + self.set_property('zoomed', zoomed) + + @property + def document(self) -> 'XAContactsDocument': + return self._new_element(self.xa_elem.document(), XAContactsDocument)
+ + + + +
[docs]class XAContactsDocumentList(XABase.XAList): + """A wrapper around lists of documents that employs fast enumeration techniques. + + All properties of documents can be called as methods on the wrapped list, returning a list containing each document's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAContactsDocument, filter) + +
[docs] def name(self) -> List[str]: + """Gets the name of each document in the list. + + :return: A list of document names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def modified(self) -> List[bool]: + """Gets the modified status of each document in the list. + + :return: A list of document modified statuses + :rtype: List[bool] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modified"))
+ +
[docs] def file(self) -> List[XABase.XAURL]: + """Gets the file of each document in the list. + + :return: A list of document files + :rtype: List[XABase.XAURL] + + .. versionadded:: 0.0.7 + """ + ls = self.xa_elem.arrayByApplyingSelector_("file") + return [XABase.XAURL(x) for x in ls]
+ +
[docs] def by_name(self, name: str) -> Union['XAContactsDocument', None]: + """Retrieves the document whose name matches the given name, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XAContactsDocument, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("name", name)
+ +
[docs] def by_modified(self, modified: bool) -> Union['XAContactsDocument', None]: + """Retrieves the document whose modified status matches the given boolean value, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XAContactsDocument, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("modified", modified)
+ +
[docs] def by_file(self, file: XABase.XAURL) -> Union['XAContactsDocument', None]: + """Retrieves the document whose file matches the given file, if one exists. + + :return: The desired document, if it is found + :rtype: Union[XAContactsDocument, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("file", file.xa_elem)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAContactsDocument(XABase.XAObject): + """A document in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.name: str #: The name of the document + self.modified: bool #: Whether the document has been modified since it was last saved + self.file: XABase.XAURL #: The location of the document of the disk, if one exists + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def modified(self) -> bool: + return self.xa_elem.modified() + + @property + def file(self) -> XABase.XAURL: + return XABase.XAURL(self.xa_elem.file())
+ + + + +
[docs]class XAContactsAddressList(XABase.XAList): + """A wrapper around lists of addresses that employs fast enumeration techniques. + + All properties of addresses can be called as methods on the wrapped list, returning a list containing each address' value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAContactsAddress, filter) + +
[docs] def city(self) -> List[str]: + """Gets the city of each address in the list. + + :return: A list of address cities + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("city"))
+ +
[docs] def formatted_address(self) -> List[str]: + """Gets the formatted address representation of each address in the list. + + :return: A list of address formatted representations + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("formattedAddress"))
+ +
[docs] def street(self) -> List[str]: + """Gets the street of each address in the list. + + :return: A list of address streets + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("street"))
+ +
[docs] def id(self) -> List[str]: + """Gets the ID of each address in the list. + + :return: A list of address IDs + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def zip(self) -> List[str]: + """Gets the ZIP code of each address in the list. + + :return: A list of address ZIP codes + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("zip"))
+ +
[docs] def country(self) -> List[str]: + """Gets the country of each address in the list. + + :return: A list of address countries + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("country"))
+ +
[docs] def label(self) -> List[str]: + """Gets the label of each address in the list. + + :return: A list of address labels + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("label"))
+ +
[docs] def country_code(self) -> List[str]: + """Gets the country code of each address in the list. + + :return: A list of address country codes + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("countryCode"))
+ +
[docs] def state(self) -> List[str]: + """Gets the state of each address in the list. + + :return: A list of address states + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("state"))
+ +
[docs] def by_city(self, city: str) -> Union['XAContactsAddress', None]: + """Retrieves the first address whose city matches the given city, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("city", city)
+ +
[docs] def by_formatted_address(self, formatted_address: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose formatted address matches the given string, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("formattedAddress", formatted_address)
+ +
[docs] def by_street(self, street: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose street matches the given street, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("street", street)
+ +
[docs] def by_id(self, id: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose ID matches the given ID, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("id", id)
+ +
[docs] def by_zip(self, zip: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose ZIP code matches the given ZIP code, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("zip", zip)
+ +
[docs] def by_country(self, country: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose country matches the given country, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("country", country)
+ +
[docs] def by_label(self, label: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose label matches the given label, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("label", label)
+ +
[docs] def by_country_code(self, country_code: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose country code matches the given country code, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("countryCode", country_code)
+ +
[docs] def by_state(self, state: str) -> Union['XAContactsAddress', None]: + """Retrieves the address whose state matches the given state, if one exists. + + :return: The desired address, if it is found + :rtype: Union[XAContactsAddress, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("state", state)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.label()) + ">"
+ +
[docs]class XAContactsAddress(XABase.XAObject): + """An address associated with a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.city: str #: The city part of the address + self.formatted_address: str #: The formatted string for the address + self.street: str #: The street part of the address + self.id: str #: The unique identifier for the address + self.zip: str #: The zip code or postal code part of the address + self.country: str #: The country part of the address + self.label: str #: The label associated with the address + self.country_code: str #: The country code part of the address + self.state: str #: The state, province, or region part of the address + + @property + def city(self) -> str: + return self.xa_elem.city() + + @city.setter + def city(self, city: str): + self.set_property('city', city) + + @property + def formatted_address(self) -> str: + return self.xa_elem.formattedAddress() + + @property + def street(self) -> str: + return self.xa_elem.street() + + @street.setter + def street(self, street: str): + self.set_property('street', street) + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def zip(self) -> str: + return self.xa_elem.zip() + + @zip.setter + def zip(self, zip: str): + self.set_property('zip', zip) + + @property + def country(self) -> str: + return self.xa_elem.country() + + @country.setter + def country(self, country: str): + self.set_property('country', country) + + @property + def label(self) -> str: + return self.xa_elem.label() + + @label.setter + def label(self, label: str): + self.set_property('label', label) + + @property + def country_code(self) -> str: + return self.xa_elem.countryCode() + + @country_code.setter + def country_code(self, country_code: str): + self.set_property('countryCode', country_code) + + @property + def state(self) -> str: + return self.xa_elem.state() + + @state.setter + def state(self, state: str): + self.set_property('state', state)
+ + + + +
[docs]class XAContactsContactInfoList(XABase.XAList): + """A wrapper around lists of contact information entries that employs fast enumeration techniques. + + All properties of contact information entries can be called as methods on the wrapped list, returning a list containing each entry's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XAContactsContactInfo + super().__init__(properties, obj_class, filter) + +
[docs] def label(self) -> List[str]: + """Gets the label of each information entry in the list. + + :return: A list of information entry labels + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("label"))
+ +
[docs] def value(self) -> List[Any]: + """Gets the value of each information entry in the list. + + :return: A list of information entry values + :rtype: List[Any] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("value"))
+ +
[docs] def id(self) -> List[str]: + """Gets the ID of each information entry in the list. + + :return: A list of information entry IDs + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def by_label(self, label: str) -> Union['XAContactsContactInfo', None]: + """Retrieves the information entry whose label matches the given label, if one exists. + + :return: The desired information entry, if it is found + :rtype: Union[XAContactsContactInfo, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("label", label)
+ +
[docs] def by_value(self, value: Any) -> Union['XAContactsContactInfo', None]: + """Retrieves the first information entry whose value matches the given value, if one exists. + + :return: The desired information entry, if it is found + :rtype: Union[XAContactsContactInfo, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("value", value)
+ +
[docs] def by_id(self, id: str) -> Union['XAContactsContactInfo', None]: + """Retrieves the information entry whose ID matches the given ID, if one exists. + + :return: The desired information entry, if it is found + :rtype: Union[XAContactsContactInfo, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("id", id)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.label()) + "::" + str(self.value()) + ">"
+ +
[docs]class XAContactsContactInfo(XABase.XAObject): + """Contact information associated with a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.label: str #: The label associated with the information entry + self.value: Union[str, datetime, None] #: The value of the information entry + self.id: str #: The persistent unique identifier for the information entry + + @property + def label(self) -> str: + return self.xa_elem.label() + + @label.setter + def label(self, label: str): + self.set_property('label', label) + + @property + def value(self) -> Union[str, datetime, None]: + return self.xa_elem.value() + + @value.setter + def value(self, value: Union[str, datetime, None]): + self.set_property('value', value) + + @property + def id(self) -> str: + return self.xa_elem.id()
+ + + + +
[docs]class XAContactsCustomDateList(XAContactsContactInfoList): + """A wrapper around lists of contact custom dates that employs fast enumeration techniques. + + All properties of contact custom dates can be called as methods on the wrapped list, returning a list containing each custom date's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsCustomDate)
+ +
[docs]class XAContactsCustomDate(XAContactsContactInfo): + """A custom date associated with a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAContactsEmailList(XAContactsContactInfoList): + """A wrapper around lists of contact email addresses that employs fast enumeration techniques. + + All properties of contact email addresses can be called as methods on the wrapped list, returning a list containing each email address's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsEmail)
+ +
[docs]class XAContactsEmail(XAContactsContactInfo): + """A document in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAContactsEntryList(XABase.XAList): + """A wrapper around lists of contact entries that employs fast enumeration techniques. + + All properties of contact entries can be called as methods on the wrapped list, returning a list containing each entry's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None, obj_class = None): + if obj_class is None: + obj_class = XAContactsEntry + super().__init__(properties, obj_class, filter) + +
[docs] def modification_date(self) -> List[datetime]: + """Gets the last modification date of each contact entry in the list. + + :return: A list of contact entry modification dates + :rtype: List[datetime] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modificationDate"))
+ +
[docs] def creation_date(self) -> List[datetime]: + """Gets the creation date of each contact entry in the list. + + :return: A list of contact entry creation dates + :rtype: List[datetime] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("creationDate"))
+ +
[docs] def id(self) -> List[str]: + """Gets the ID of each contact entry in the list. + + :return: A list of contact entry IDs + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def selected(self) -> List[bool]: + """Gets the selected status of each contact entry in the list. + + :return: A list of contact entry selected statuses + :rtype: List[bool] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("selected"))
+ +
[docs] def by_modification_date(self, modification_date: datetime) -> Union['XAContactsEntry', None]: + """Retrieves the first contact entry whose last modification date matches the given date, if one exists. + + :return: The desired contact entry, if it is found + :rtype: Union[XAContactsEntry, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("modificationDate", modification_date)
+ +
[docs] def by_creation_date(self, creation_date: datetime) -> Union['XAContactsEntry', None]: + """Retrieves the first contact entry whose creation date matches the given date, if one exists. + + :return: The desired contact entry, if it is found + :rtype: Union[XAContactsEntry, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("creationDate", creation_date)
+ +
[docs] def by_id(self, id: str) -> Union['XAContactsEntry', None]: + """Retrieves the contact entry whose ID matches the given ID, if one exists. + + :return: The desired contact entry, if it is found + :rtype: Union[XAContactsEntry, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("id", id)
+ +
[docs] def by_selected(self, selected: bool) -> Union['XAContactsEntry', None]: + """Retrieves the contact entry whose selected status matches the given boolean value, if one exists. + + :return: The desired contact entry, if it is found + :rtype: Union[XAContactsEntry, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("selected", selected)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.id()) + ">"
+ +
[docs]class XAContactsEntry(XABase.XAObject): + """An entry in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.modification_date: datetime #: The last modification date of the contact entry + self.creation_date: datetime #: The creation date of the contact entry + self.id: str #: The unique persistent identifier for the entry + self.selected: bool #: Whether the entry is selected + + @property + def modification_date(self) -> datetime: + return self.xa_elem.modificationDate() + + @property + def creation_date(self) -> datetime: + return self.xa_elem.creationDate() + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def selected(self) -> bool: + return self.xa_elem.selected() + + @selected.setter + def selected(self, selected: bool): + self.set_property('selected', selected) + +
[docs] def add_to(self, parent: XABase.XAObject) -> 'XAContactsPerson': + """Adds a child object to an entry. + + :param parent: The entry to add this entry as a child to + :type parent: XABase.XAObject + + :Example 1: Add a contact to a group + + >>> import PyXA + >>> app = PyXA.application("Contacts") + >>> group = app.groups().by_name("Example Group") + >>> app.people()[0].add_to(group) + >>> app.save() + + .. versionadded:: 0.0.7 + """ + person = self.xa_elem.addTo_(parent.xa_elem) + return self._new_element(person, XAContactsPerson)
+ +
[docs] def remove_from(self, elem) -> 'XAContactsPerson': + """Removes a child object from an entry. + + :param parent: The entry to removes this entry as a child from + :type parent: XABase.XAObject + + :Example 1: Remove a contact from a group + + >>> import PyXA + >>> app = PyXA.application("Contacts") + >>> group = app.groups().by_name("Example Group") + >>> app.people()[0].add_to(group) + >>> app.people()[0].remove_from(group) + >>> app.save() + + .. versionadded:: 0.0.7 + """ + person = self.xa_elem.removeFrom_(elem.xa_elem) + return self._new_element(person, XAContactsPerson)
+ +
[docs] def delete(self): + """Deletes the entry. Only entries creates in the current session can be deleted. + + .. versionadded:: 0.0.7 + """ + self.xa_elem.delete()
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.id) + ">"
+ + + + +
[docs]class XAContactsGroupList(XAContactsEntryList): + """A wrapper around lists of contact groups that employs fast enumeration techniques. + + All properties of contact groups can be called as methods on the wrapped list, returning a list containing each group's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsGroup) + +
[docs] def name(self) -> List[str]: + """Gets the name of each contact group in the list. + + :return: A list of contact group names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def by_name(self, name: str) -> Union['XAContactsGroup', None]: + """Retrieves the first contact group whose name matches the given name, if one exists. + + :return: The desired contact group, if it is found + :rtype: Union[XAContactsGroup, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("name", name)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAContactsGroup(XAContactsEntry): + """A group in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.name: str #: The name of the group + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property('name', name) + +
[docs] def groups(self, filter: Union[dict, None] = None) -> 'XAContactsGroupList': + """Returns a list of groups, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned groups will have, or None + :type filter: Union[dict, None] + :return: The list of groups + :rtype: XAContactsGroupList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.groups(), XAContactsGroupList, filter)
+ +
[docs] def people(self, filter: Union[dict, None] = None) -> 'XAContactsPersonList': + """Returns a list of people, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned people will have, or None + :type filter: Union[dict, None] + :return: The list of people + :rtype: XAContactsPersonList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.people(), XAContactsPersonList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + + +
[docs]class XAContactsInstantMessageList(XAContactsContactInfoList): + """A wrapper around lists of IM addresses that employs fast enumeration techniques. + + All properties of IM addresses can be called as methods on the wrapped list, returning a list containing each IM address's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsInstantMessage) + +
[docs] def service_name(self) -> List[str]: + """Gets the service name of each IM address in the list. + + :return: A list of IM address service names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("serviceName"))
+ +
[docs] def service_type(self) -> List[XAContactsApplication.ServiceType]: + """Gets the service type of each IM address in the list. + + :return: A list of IM address service types + :rtype: List[XAContactsApplication.ServiceType] + + .. versionadded:: 0.0.7 + """ + ls = self.xa_elem.arrayByApplyingSelector_("serviceType") + return [XAContactsApplication.ServiceType(XABase.OSType(x.stringValue())) for x in ls]
+ +
[docs] def user_name(self) -> List[str]: + """Gets the user name of each IM address in the list. + + :return: A list of IM address user names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("userName"))
+ +
[docs] def by_service_name(self, service_name: str) -> Union['XAContactsInstantMessage', None]: + """Retrieves the first IM address whose service name matches the given service name, if one exists. + + :return: The desired IM address, if it is found + :rtype: Union[XAContactsInstantMessage, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("serviceName", service_name)
+ +
[docs] def by_service_type(self, service_type: XAContactsApplication.ServiceType) -> Union['XAContactsInstantMessage', None]: + """Retrieves the first IM address whose service type matches the given service type, if one exists. + + :return: The desired IM address, if it is found + :rtype: Union[XAContactsInstantMessage, None] + + .. versionadded:: 0.0.7 + """ + event = XAEvents.event_from_int(service_type.value) + return self.by_property("serviceType", event)
+ +
[docs] def by_user_name(self, user_name: str) -> Union['XAContactsInstantMessage', None]: + """Retrieves the first IM address whose user name matches the given user name, if one exists. + + :return: The desired IM address, if it is found + :rtype: Union[XAContactsInstantMessage, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("userName", user_name)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.service_name()) + ">"
+ +
[docs]class XAContactsInstantMessage(XAContactsContactInfo): + """An instant message (IM) address associated with a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.service_name: str #: The service name of the IM address + self.service_type: XAContactsApplication.ServiceType #: The service type of the IM address + self.user_name: str #: The user name of the the IM address + + @property + def service_name(self) -> str: + return self.xa_elem.serviceName().get() + + @property + def service_type(self) -> XAContactsApplication.ServiceType: + return XAContactsApplication.ServiceType(self.xa_elem.serviceType()) + + @service_type.setter + def service_type(self, service_type: XAContactsApplication.ServiceType): + self.set_property('serviceType', service_type.value) + + @property + def user_name(self) -> str: + return self.xa_elem.userName().get() + + @user_name.setter + def user_name(self, user_name: str): + self.set_property('userName', user_name) + + def __repr__(self): + return "<" + str(type(self)) + str(self.service_name) + ">"
+ + + + +
[docs]class XAContactsPersonList(XAContactsEntryList): + """A wrapper around lists of people that employs fast enumeration techniques. + + All properties of people can be called as methods on the wrapped list, returning a list containing each IM person's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsPerson) + +
[docs] def nickname(self) -> List[str]: + """Gets the nickname of each person in the list. + + :return: A list of contact person nicknames + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("nickname"))
+ +
[docs] def organization(self) -> List[str]: + """Gets the organization of each person in the list. + + :return: A list of contact person organizations + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("organization"))
+ +
[docs] def maiden_name(self) -> List[str]: + """Gets the maiden name of each person in the list. + + :return: A list of contact person maiden names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("maidenName"))
+ +
[docs] def suffix(self) -> List[str]: + """Gets the suffix of each person in the list. + + :return: A list of contact person suffixes + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("suffix"))
+ +
[docs] def vcard(self) -> List[str]: + """Gets the vCard representation of each person in the list. + + :return: A list of contact person vCard representations + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("vcard"))
+ +
[docs] def home_page(self) -> List[str]: + """Gets the home page of each person in the list. + + :return: A list of contact person home pages + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("homePage"))
+ +
[docs] def birth_date(self) -> List[datetime]: + """Gets the birthdate of each person in the list. + + :return: A list of contact person birthdates + :rtype: List[datetime] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("birthdate"))
+ +
[docs] def phonetic_last_name(self) -> List[str]: + """Gets the phonetic last name of each person in the list. + + :return: A list of contact person phonetic last names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("phoneticLastName"))
+ +
[docs] def title(self) -> List[str]: + """Gets the title of each person in the list. + + :return: A list of contact person titles + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("title"))
+ +
[docs] def phonetic_middle_name(self) -> List[str]: + """Gets the phonetic middle name of each person in the list. + + :return: A list of contact person phonetic middle names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("phoneticMiddleName"))
+ +
[docs] def department(self) -> List[str]: + """Gets the department of each person in the list. + + :return: A list of contact person departments + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("department"))
+ +
[docs] def image(self) -> List[XABase.XAImage]: + """Gets the image of each person in the list. + + :return: A list of contact person images + :rtype: List[XABase.XAImage] + + .. versionadded:: 0.0.7 + """ + ls = self.xa_elem.arrayByApplyingSelector_("image") + return [XABase.XAImage(x) for x in ls]
+ +
[docs] def name(self) -> List[str]: + """Gets the name of each person in the list. + + :return: A list of contact person names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def note(self) -> List[str]: + """Gets the notes of each person in the list. + + :return: A list of contact person notes + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("note"))
+ +
[docs] def company(self) -> List[bool]: + """Gets the company status of each "person" in the list. + + :return: A list of contact company statuses + :rtype: List[bool] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("company"))
+ +
[docs] def middle_name(self) -> List[str]: + """Gets the middle name of each person in the list. + + :return: A list of contact person middle names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("middleName"))
+ +
[docs] def phonetic_first_name(self) -> List[str]: + """Gets the phonetic first name of each person in the list. + + :return: A list of contact person phonetic first names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("phoneticFirstName"))
+ +
[docs] def job_title(self) -> List[str]: + """Gets the job title of each person in the list. + + :return: A list of contact person job titles + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("jobTitle"))
+ +
[docs] def last_name(self) -> List[str]: + """Gets the last name of each person in the list. + + :return: A list of contact person last names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("lastName"))
+ +
[docs] def first_name(self) -> List[str]: + """Gets the first name of each person in the list. + + :return: A list of contact person first names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("firstName"))
+ +
[docs] def by_nickname(self, nickname: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose nickname matches the given nickname, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("nickname", nickname)
+ +
[docs] def by_organization(self, organization: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose organization matches the given organization, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("organization", organization)
+ +
[docs] def by_maiden_name(self, maiden_name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose maiden name matches the given maiden name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("maidenName", maiden_name)
+ +
[docs] def by_suffix(self, suffix: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose suffix matches the given suffix, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("suffix", suffix)
+ +
[docs] def by_vcard(self, vcard: str) -> Union['XAContactsPerson', None]: + """Retrieves the person whose vCard representation matches the given string, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("vcard", vcard)
+ +
[docs] def by_home_page(self, home_page: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose home page URL matches the given URL, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + # TODO - URL? + return self.by_property("homePage", home_page)
+ +
[docs] def by_birth_date(self, birth_date: datetime) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose birthdate matches the given date, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("birthDate", birth_date)
+ +
[docs] def by_phonetic_last_name(self, phonetic_last_name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose phonetic last name matches the given phonetic last name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("phoneticLastName", phonetic_last_name)
+ +
[docs] def by_title(self, title: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose title matches the given title, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("title", title)
+ +
[docs] def by_phonetic_middle_name(self, phonetic_middle_name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose phonetic middle name matches the given phonetic middle name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("phoneticMiddleName", phonetic_middle_name)
+ +
[docs] def by_department(self, department: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose department matches the given department, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("department", department)
+ +
[docs] def by_image(self, image: XABase.XAImage) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose image matches the given image, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("image", image.xa_elem)
+ +
[docs] def by_name(self, name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose name matches the given name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("name", name)
+ +
[docs] def by_note(self, note: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose notes matches the given notes, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("note", note)
+ +
[docs] def by_company(self, company: bool) -> Union['XAContactsPerson', None]: + """Retrieves the first "person" whose company status matches the given boolean value, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("company", company)
+ +
[docs] def by_middle_name(self, middle_name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose middle name matches the given middle name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("middleName", middle_name)
+ +
[docs] def by_phonetic_first_name(self, phonetic_first_name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose phonetic first name matches the given phonetic first name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("phoneticFirstName", phonetic_first_name)
+ +
[docs] def by_job_title(self, job_title: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose job title matches the given job title, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("jobTitle", job_title)
+ +
[docs] def by_last_name(self, last_name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose last name matches the given last name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("lastName", last_name)
+ +
[docs] def by_first_name(self, first_name: str) -> Union['XAContactsPerson', None]: + """Retrieves the first person whose first name matches the given first name, if one exists. + + :return: The desired person, if it is found + :rtype: Union[XAContactsPerson, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("firstName", first_name)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name()) + ">"
+ +
[docs]class XAContactsPerson(XAContactsEntry): + """A person in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.nickname: str #: The nickname of the person + self.organization: str #: The organization that employs the person + self.maiden_name: str #: The maiden name of the person + self.suffix: str #: The suffix of the person's name + self.vcard: str #: The person's information in vCard format + self.home_page: str #: The homepage of the person + self.birth_date: datetime #: The birthdate of the person + self.phonetic_last_name: str #: The phonetic version of the person's last name + self.title: str #: The title of the person + self.phonetic_middle_name: str #: The phonetic version of the person's middle name + self.department: str #: The department that the person works for + self.image: XABase.XAImage #: The image for the person + self.name: str #: The first and last name of the person + self.note: str #: The notes for the person + self.company: bool #: Whether the record is for a company or not (if not, the record is for a person) + self.middle_name: str #: The middle name of the person + self.phonetic_first_name: str #: The phonetic version of the person's first name + self.job_title: str #: The job title of the person + self.last_name: str #: The last name of the person + self.first_name: str #: The first name of the person + + @property + def nickname(self) -> str: + return self.xa_elem.nickname().get() + + @nickname.setter + def nickname(self, nickname: str): + self.set_property('nickname', nickname) + + @property + def organization(self) -> str: + return self.xa_elem.organization().get() + + @organization.setter + def organization(self, organization: str): + self.set_property('organization', organization) + + @property + def maiden_name(self) -> str: + return self.xa_elem.maidenName().get() + + @maiden_name.setter + def maiden_name(self, maiden_name: str): + self.set_property('maidenName', maiden_name) + + @property + def suffix(self) -> str: + return self.xa_elem.suffix().get() + + @suffix.setter + def suffix(self, suffix: str): + self.set_property('suffix', suffix) + + @property + def vcard(self) -> str: + return self.xa_elem.vcard().get() + + @property + def home_page(self) -> str: + return self.xa_elem.homePage().get() + + @home_page.setter + def home_page(self, home_page: str): + self.set_property('homePage', home_page) + + @property + def birth_date(self) -> datetime: + return self.xa_elem.birthDate().get() + + @birth_date.setter + def birth_date(self, birth_date: datetime): + self.set_property('birthDate', birth_date) + + @property + def phonetic_last_name(self) -> str: + return self.xa_elem.phoneticLastName().get() + + @phonetic_last_name.setter + def phonetic_last_name(self, phonetic_last_name: str): + self.set_property('phoneticLastName', phonetic_last_name) + + @property + def title(self) -> str: + return self.xa_elem.title().get() + + @title.setter + def title(self, title: str): + self.set_property('title', title) + + @property + def phonetic_middle_name(self) -> str: + return self.xa_elem.phoneticMiddleNamne().get() + + @phonetic_middle_name.setter + def phonetic_middle_name(self, phonetic_middle_name: str): + self.set_property('phoneticMiddleName', phonetic_middle_name) + + @property + def department(self) -> str: + return self.xa_elem.department().get() + + @department.setter + def department(self, department: str): + self.set_property('department', department) + + @property + def image(self) -> XABase.XAImage: + return XABase.XAImage(self.xa_elem.image().get()) + + @image.setter + def image(self, image: XABase.XAImage): + self.set_property('image', image.xa_elem) + + @property + def name(self) -> str: + return self.xa_elem.name() + + @name.setter + def name(self, name: str): + self.set_property('name', name) + + @property + def note(self) -> str: + return self.xa_elem.note().get() + + @note.setter + def note(self, note: str): + self.set_property('note', note) + + @property + def company(self) -> str: + return self.xa_elem.company().get() + + @company.setter + def company(self, company: str): + self.set_property('company', company) + + @property + def middle_name(self) -> str: + return self.xa_elem.middleName().get() + + @middle_name.setter + def middle_name(self, middle_name: str): + self.set_property('middleName', middle_name) + + @property + def phonetic_first_name(self) -> str: + return self.xa_elem.phoneticFirstName().get() + + @phonetic_first_name.setter + def phonetic_first_name(self, phonetic_first_name: str): + self.set_property('phoneticFirstName', phonetic_first_name) + + @property + def job_title(self) -> str: + return self.xa_elem.jobTitle().get() + + @job_title.setter + def job_title(self, job_title: str): + self.set_property('jobTitle', job_title) + + @property + def last_name(self) -> str: + return self.xa_elem.lastName().get() + + @last_name.setter + def last_name(self, last_name: str): + self.set_property('lastName', last_name) + + @property + def first_name(self) -> str: + return self.xa_elem.firstName().get() + + @first_name.setter + def first_name(self, first_name: str): + self.set_property('firstName', first_name) + +
[docs] def show(self) -> 'XAContactsPerson': + """Shows the contact card for this contact in Contacts.app. + + :return: The contact person object + :rtype: XAContactsPerson + + .. versionadded:: 0.0.7 + """ + vcard = self.vcard + id = vcard[vcard.index("X-ABUID") + 8: vcard.index(":ABPerson")] + "%3AABPerson" + XABase.XAURL("addressbook://" + id).open() + return self
+ +
[docs] def urls(self, filter: Union[dict, None] = None) -> 'XAContactsURLList': + """Returns a list of URLs, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned URLs will have, or None + :type filter: Union[dict, None] + :return: The list of URLs + :rtype: XAContactsURLList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.urls(), XAContactsURLList, filter)
+ +
[docs] def addresses(self, filter: Union[dict, None] = None) -> 'XAContactsAddressList': + """Returns a list of addresses, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned addresses will have, or None + :type filter: Union[dict, None] + :return: The list of addresses + :rtype: XAContactsAddressList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.addresses(), XAContactsAddressList, filter)
+ +
[docs] def phones(self, filter: Union[dict, None] = None) -> 'XAContactsPhoneList': + """Returns a list of phone numbers, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned phone numbers will have, or None + :type filter: Union[dict, None] + :return: The list of phone numbers + :rtype: XAContactsPhoneList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.phones(), XAContactsPhoneList, filter)
+ +
[docs] def groups(self, filter: Union[dict, None] = None) -> 'XAContactsGroupList': + """Returns a list of groups, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned groups will have, or None + :type filter: Union[dict, None] + :return: The list of groups + :rtype: XAContactsGroupList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.phones(), XAContactsGroupList, filter)
+ +
[docs] def custom_dates(self, filter: Union[dict, None] = None) -> 'XAContactsCustomDateList': + """Returns a list of groups, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned groups will have, or None + :type filter: Union[dict, None] + :return: The list of groups + :rtype: XAContactsCustomDateList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.customDates(), XAContactsCustomDateList, filter)
+ +
[docs] def instant_messages(self, filter: Union[dict, None] = None) -> 'XAContactsInstantMessageList': + """Returns a list of IM addresses, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned IM addresses will have, or None + :type filter: Union[dict, None] + :return: The list of IM addresses + :rtype: XAContactsInstantMessageList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.instantMessages(), XAContactsInstantMessageList, filter)
+ +
[docs] def social_profiles(self, filter: Union[dict, None] = None) -> 'XAContactsSocialProfileList': + """Returns a list of social profiles, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned social profiles will have, or None + :type filter: Union[dict, None] + :return: The list of social profiles + :rtype: XAContactsSocialProfileList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.socialProfiles(), XAContactsSocialProfileList, filter)
+ +
[docs] def related_names(self, filter: Union[dict, None] = None) -> 'XAContactsRelatedNameList': + """Returns a list of related names, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned related names will have, or None + :type filter: Union[dict, None] + :return: The list of related names + :rtype: XAContactsRelatedNameList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.relatedNames(), XAContactsRelatedNameList, filter)
+ +
[docs] def emails(self, filter: Union[dict, None] = None) -> 'XAContactsEmailList': + """Returns a list of email addresses, as PyXA objects, matching the given filter. + + :param filter: A dictionary specifying property-value pairs that all returned email addresses will have, or None + :type filter: Union[dict, None] + :return: The list of email addresses + :rtype: XAContactsEmailList + + .. versionadded:: 0.0.7 + """ + return self._new_element(self.xa_elem.emails(), XAContactsEmailList, filter)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+ + + + +
[docs]class XAContactsPhoneList(XAContactsContactInfoList): + """A wrapper around lists of contact phone numbers that employs fast enumeration techniques. + + All properties of contact phone numbers can be called as methods on the wrapped list, returning a list containing each phone numbers's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsPhone)
+ +
[docs]class XAContactsPhone(XAContactsContactInfo): + """A phone number associated with a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAContactsRelatedNameList(XAContactsContactInfoList): + """A wrapper around lists of contact related names that employs fast enumeration techniques. + + All properties of contact related names can be called as methods on the wrapped list, returning a list containing each related names's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsRelatedName)
+ +
[docs]class XAContactsRelatedName(XAContactsContactInfo): + """A related name of a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties)
+ + + + +
[docs]class XAContactsSocialProfileList(XABase.XAList): + """A wrapper around lists of contact social profiles that employs fast enumeration techniques. + + All properties of contact social profiles can be called as methods on the wrapped list, returning a list containing each social profile's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XAContactsSocialProfile, filter) + +
[docs] def id(self) -> List[str]: + """Gets the ID of each social profile in the list. + + :return: A list of social profile IDs + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def service_name(self) -> List[str]: + """Gets the service name of each social profile in the list. + + :return: A list of social profile service names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("serviceName"))
+ +
[docs] def user_name(self) -> List[str]: + """Gets the user name of each social profile in the list. + + :return: A list of social profile user names + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("userName"))
+ +
[docs] def user_identifier(self) -> List[str]: + """Gets the user identifier of each social profile in the list. + + :return: A list of social profile user identifiers + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("userIdentifier"))
+ +
[docs] def url(self) -> List[str]: + """Gets the URL of each social profile in the list. + + :return: A list of social profile URLs + :rtype: List[str] + + .. versionadded:: 0.0.7 + """ + return list(self.xa_elem.arrayByApplyingSelector_("URL"))
+ +
[docs] def by_id(self, id: str) -> Union['XAContactsSocialProfile', None]: + """Retrieves the social profile whose ID matches the given ID, if one exists. + + :return: The desired social profile, if it is found + :rtype: Union[XAContactsSocialProfile, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("id", id)
+ +
[docs] def by_service_name(self, service_name: str) -> Union['XAContactsSocialProfile', None]: + """Retrieves the first social profile whose service name matches the given service name, if one exists. + + :return: The desired social profile, if it is found + :rtype: Union[XAContactsSocialProfile, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("serviceName", service_name)
+ +
[docs] def by_user_name(self, user_name: str) -> Union['XAContactsSocialProfile', None]: + """Retrieves the first social profile whose user name matches the given user name, if one exists. + + :return: The desired social profile, if it is found + :rtype: Union[XAContactsSocialProfile, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("userName", user_name)
+ +
[docs] def by_user_identifier(self, user_identifier: str) -> Union['XAContactsSocialProfile', None]: + """Retrieves the social profile whose user identifier matches the given identifier, if one exists. + + :return: The desired social profile, if it is found + :rtype: Union[XAContactsSocialProfile, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("userIdentifier", user_identifier)
+ +
[docs] def by_url(self, url: str) -> Union['XAContactsSocialProfile', None]: + """Retrieves the social profile whose URL matches the given URL, if one exists. + + :return: The desired social profile, if it is found + :rtype: Union[XAContactsSocialProfile, None] + + .. versionadded:: 0.0.7 + """ + return self.by_property("URL", url)
+ + def __repr__(self): + return "<" + str(type(self)) + str(self.user_name()) + ">"
+ +
[docs]class XAContactsSocialProfile(XABaseScriptable.XASBWindow): + """A social profile associated with a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties) + self.id: str #: A persistent unique identifier for this profile + self.service_name: str #: The service name of this social profile + self.user_name: str #: The user named used with this social profile + self.user_identifier: str #: A service-specific identifier used with this social profile + self.url: str #: The URL of the social profile + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def service_name(self) -> str: + return self.xa_elem.serviceName().get() + + @service_name.setter + def service_name(self, service_name: str): + self.set_property('serviceName', service_name) + + @property + def user_name(self) -> str: + return self.xa_elem.userName().get() + + @user_name.setter + def user_name(self, user_name: str): + self.set_property('userName', user_name) + + @property + def user_identifier(self) -> str: + return self.xa_elem.userIdentifier().get() + + @user_identifier.setter + def user_identifier(self, user_identifier: str): + self.set_property('userIdentifier', user_identifier) + + @property + def url(self) -> str: + return self.xa_elem.url() + + @url.setter + def url(self, url: str): + self.set_property('url', url) + + def __repr__(self): + return "<" + str(type(self)) + str(self.user_name) + ">"
+ + + + +
[docs]class XAContactsURLList(XAContactsContactInfoList): + """A wrapper around lists of contact URLs that employs fast enumeration techniques. + + All properties of contact URLs can be called as methods on the wrapped list, returning a list containing each URL's value for the property. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, filter, XAContactsURL)
+ +
[docs]class XAContactsURL(XAContactsContactInfo): + """A URL associated with a contact in Contacts.app. + + .. versionadded:: 0.0.7 + """ + def __init__(self, properties): + super().__init__(properties)
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Contacts.html b/docs/_modules/PyXA/apps/Contacts.html index ba13866..2150a35 100644 --- a/docs/_modules/PyXA/apps/Contacts.html +++ b/docs/_modules/PyXA/apps/Contacts.html @@ -3,7 +3,7 @@ - PyXA.apps.Contacts — PyXA 0.0.9 documentation + PyXA.apps.Contacts — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -74,9 +76,9 @@

Source code for PyXA.apps.Contacts

 """
 from datetime import datetime
 from enum import Enum
-from typing import Any, List, Tuple, Union
-from AppKit import NSURL
+from typing import Any, Union
 
+import AppKit
 
 from PyXA import XABase, XAEvents
 from PyXA import XABaseScriptable
@@ -90,19 +92,6 @@ 

Source code for PyXA.apps.Contacts

 
     .. versionadded:: 0.0.2
     """
-
[docs] class SaveOption(Enum): - """Options for what to do when calling a save event. - """ - SAVE_FILE = XABase.OSType('yes ') #: Save the file. - DONT_SAVE = XABase.OSType('no ') #: Do not save the file. - ASK = XABase.OSType('ask ') #: Ask the user whether or not to save the file.
- -
[docs] class PrintSetting(Enum): - """Options to use when printing contacts. - """ - STANDARD_ERROR_HANDLING = XABase.OSType('lwst') #: Standard PostScript error handling - DETAILED_ERROR_HANDLING = XABase.OSType('lwdt') #: print a detailed report of PostScript errors
-
[docs] class Format(Enum): """Format options when saving documents. """ @@ -141,6 +130,10 @@

Source code for PyXA.apps.Contacts

     def frontmost(self) -> bool:
         return self.xa_scel.frontmost()
 
+    @frontmost.setter
+    def frontmost(self, frontmost: bool):
+        self.set_property('frontmost', frontmost)
+
     @property
     def version(self) -> str:
         return self.xa_scel.version()
@@ -149,6 +142,10 @@ 

Source code for PyXA.apps.Contacts

     def my_card(self) -> 'XAContactsPerson':
         return self._new_element(self.xa_scel.myCard(), XAContactsPerson)
 
+    @my_card.setter
+    def my_card(self, my_card: 'XAContactsPerson'):
+        self.set_property('myCard', my_card)
+
     @property
     def unsaved(self) -> bool:
         return self.xa_scel.unsaved()
@@ -157,6 +154,14 @@ 

Source code for PyXA.apps.Contacts

     def selection(self) -> 'XAContactsPersonList':
         return self._new_element(self.xa_scel.selection(), XAContactsPersonList)
 
+    @selection.setter
+    def selection(self, selection: Union['XAContactsPersonList', list['XAContactsPerson']]):
+        if isinstance(selection, list):
+            selection = [x.xa_elem for x in selection]
+            self.set_property("selection", selection)
+        else:
+            self.set_property('selection', selection.xa_elem)
+
     @property
     def default_country_code(self) -> str:
         return self.xa_scel.defaultCountryCode()
@@ -201,7 +206,7 @@ 

Source code for PyXA.apps.Contacts

         :Example:
 
         >>> import PyXA
-        >>> app = PyXA.application("Contacts")
+        >>> app = PyXA.Application("Contacts")
         >>> print(app.groups())
         <<class 'PyXA.apps.Contacts.XAContactsGroupList'>['Example Group 1', 'Example Group 2', ...]>
 
@@ -220,7 +225,7 @@ 

Source code for PyXA.apps.Contacts

         :Example:
 
         >>> import PyXA
-        >>> app = PyXA.application("Contacts")
+        >>> app = PyXA.Application("Contacts")
         >>> print(app.people())
         <<class 'PyXA.apps.Contacts.XAContactsPersonList'>['Example Contact 1', 'Example Contact 2', ...]>
 
@@ -243,7 +248,7 @@ 

Source code for PyXA.apps.Contacts

         :Example 1: Add a URL to a contact
 
         >>> import PyXA
-        >>> app = PyXA.application("Contacts")
+        >>> app = PyXA.Application("Contacts")
         >>> contact = app.people().by_name("Example Contact")
         >>> new_url = app.make("url", {"label": "Google", "value": "www.google.com"})
         >>> contact.urls().push(new_url)
@@ -278,7 +283,7 @@ 

Source code for PyXA.apps.Contacts

         self.name: str #: The title of the window
         self.id: int #: The unique identifier for the window
         self.index: int #: The index of the window in the front-to-back ordering
-        self.bounds: Tuple[Tuple[int, int], Tuple[int, int]] #: The bounding rectangle of the window
+        self.bounds: tuple[int, int, int, int] #: The bounding rectangle of the window
         self.closeable: bool #: Whether the window has a close button
         self.miniaturizable: bool #: Whether the window can be minimized
         self.miniaturized: bool #: Whether the window is currently minimized
@@ -300,9 +305,25 @@ 

Source code for PyXA.apps.Contacts

     def index(self) -> int:
         return self.xa_elem.index()
 
+    @index.setter
+    def index(self, index: int):
+        self.set_property('index', index)
+
     @property
-    def bounds(self) -> Tuple[Tuple[int, int], Tuple[int, int]]:
-        return self.xa_elem.bounds()
+    def bounds(self) -> tuple[int, int, int, int]:
+        rect = self.xa_elem.bounds()
+        origin = rect.origin
+        size = rect.size
+        return (origin.x, origin.y, size.width, size.height)
+
+    @bounds.setter
+    def bounds(self, bounds: tuple[int, int, int, int]):
+        x = bounds[0]
+        y = bounds[1]
+        w = bounds[2]
+        h = bounds[3]
+        value = AppKit.NSValue.valueWithRect_(AppKit.NSMakeRect(x, y, w, h))
+        self.set_property("bounds", value)
 
     @property
     def closeable(self) -> bool:
@@ -316,6 +337,10 @@ 

Source code for PyXA.apps.Contacts

     def miniaturized(self) -> bool:
         return self.xa_elem.miniaturized()
 
+    @miniaturized.setter
+    def miniaturized(self, miniaturized: bool):
+        self.set_property('miniaturized', miniaturized)
+
     @property
     def resizable(self) -> bool:
         return self.xa_elem.resizable()
@@ -324,6 +349,10 @@ 

Source code for PyXA.apps.Contacts

     def visible(self) -> bool:
         return self.xa_elem.visible()
 
+    @visible.setter
+    def visible(self, visible: bool):
+        self.set_property('visible', visible)
+
     @property
     def zoomable(self) -> bool:
         return self.xa_elem.zoomable()
@@ -332,6 +361,10 @@ 

Source code for PyXA.apps.Contacts

     def zoomed(self) -> bool:
         return self.xa_elem.zoomed()
 
+    @zoomed.setter
+    def zoomed(self, zoomed: bool):
+        self.set_property('zoomed', zoomed)
+
     @property
     def document(self) -> 'XAContactsDocument':
         return self._new_element(self.xa_elem.document(), XAContactsDocument)
@@ -349,31 +382,31 @@

Source code for PyXA.apps.Contacts

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAContactsDocument, filter)
 
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Gets the name of each document in the list. :return: A list of document names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def modified(self) -> List[bool]: +
[docs] def modified(self) -> list[bool]: """Gets the modified status of each document in the list. :return: A list of document modified statuses - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("modified"))
-
[docs] def file(self) -> List[XABase.XAURL]: +
[docs] def file(self) -> list[XABase.XAURL]: """Gets the file of each document in the list. :return: A list of document files - :rtype: List[XABase.XAURL] + :rtype: list[XABase.XAURL] .. versionadded:: 0.0.7 """ @@ -449,91 +482,91 @@

Source code for PyXA.apps.Contacts

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAContactsAddress, filter)
 
-
[docs] def city(self) -> List[str]: +
[docs] def city(self) -> list[str]: """Gets the city of each address in the list. :return: A list of address cities - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("city"))
-
[docs] def formatted_address(self) -> List[str]: +
[docs] def formatted_address(self) -> list[str]: """Gets the formatted address representation of each address in the list. :return: A list of address formatted representations - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("formattedAddress"))
-
[docs] def street(self) -> List[str]: +
[docs] def street(self) -> list[str]: """Gets the street of each address in the list. :return: A list of address streets - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("street"))
-
[docs] def id(self) -> List[str]: +
[docs] def id(self) -> list[str]: """Gets the ID of each address in the list. :return: A list of address IDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def zip(self) -> List[str]: +
[docs] def zip(self) -> list[str]: """Gets the ZIP code of each address in the list. :return: A list of address ZIP codes - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("zip"))
-
[docs] def country(self) -> List[str]: +
[docs] def country(self) -> list[str]: """Gets the country of each address in the list. :return: A list of address countries - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("country"))
-
[docs] def label(self) -> List[str]: +
[docs] def label(self) -> list[str]: """Gets the label of each address in the list. :return: A list of address labels - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("label"))
-
[docs] def country_code(self) -> List[str]: +
[docs] def country_code(self) -> list[str]: """Gets the country code of each address in the list. :return: A list of address country codes - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("countryCode"))
-
[docs] def state(self) -> List[str]: +
[docs] def state(self) -> list[str]: """Gets the state of each address in the list. :return: A list of address states - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ @@ -653,6 +686,10 @@

Source code for PyXA.apps.Contacts

     def city(self) -> str:
         return self.xa_elem.city()
 
+    @city.setter
+    def city(self, city: str):
+        self.set_property('city', city)
+
     @property
     def formatted_address(self) -> str:
         return self.xa_elem.formattedAddress()
@@ -661,6 +698,10 @@ 

Source code for PyXA.apps.Contacts

     def street(self) -> str:
         return self.xa_elem.street()
 
+    @street.setter
+    def street(self, street: str):
+        self.set_property('street', street)
+
     @property
     def id(self) -> str:
         return self.xa_elem.id()
@@ -669,21 +710,41 @@ 

Source code for PyXA.apps.Contacts

     def zip(self) -> str:
         return self.xa_elem.zip()
 
+    @zip.setter
+    def zip(self, zip: str):
+        self.set_property('zip', zip)
+
     @property
     def country(self) -> str:
         return self.xa_elem.country()
 
+    @country.setter
+    def country(self, country: str):
+        self.set_property('country', country)
+
     @property
     def label(self) -> str:
         return self.xa_elem.label()
 
+    @label.setter
+    def label(self, label: str):
+        self.set_property('label', label)
+
     @property
     def country_code(self) -> str:
         return self.xa_elem.countryCode()
 
+    @country_code.setter
+    def country_code(self, country_code: str):
+        self.set_property('countryCode', country_code)
+
     @property
     def state(self) -> str:
-        return self.xa_elem.state()
+ return self.xa_elem.state() + + @state.setter + def state(self, state: str): + self.set_property('state', state)
@@ -700,31 +761,31 @@

Source code for PyXA.apps.Contacts

             obj_class = XAContactsContactInfo
         super().__init__(properties, obj_class, filter)
 
-
[docs] def label(self) -> List[str]: +
[docs] def label(self) -> list[str]: """Gets the label of each information entry in the list. :return: A list of information entry labels - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("label"))
-
[docs] def value(self) -> List[Any]: +
[docs] def value(self) -> list[Any]: """Gets the value of each information entry in the list. :return: A list of information entry values - :rtype: List[Any] + :rtype: list[Any] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("value"))
-
[docs] def id(self) -> List[str]: +
[docs] def id(self) -> list[str]: """Gets the ID of each information entry in the list. :return: A list of information entry IDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ @@ -771,17 +832,25 @@

Source code for PyXA.apps.Contacts

     def __init__(self, properties):
         super().__init__(properties)
         self.label: str #: The label associated with the information entry
-        self.value: Any #: The value of the information entry
+        self.value: Union[str, datetime, None] #: The value of the information entry
         self.id: str #: The persistent unique identifier for the information entry
 
     @property
     def label(self) -> str:
         return self.xa_elem.label()
 
+    @label.setter
+    def label(self, label: str):
+        self.set_property('label', label)
+
     @property
-    def value(self) -> Any:
+    def value(self) -> Union[str, datetime, None]:
         return self.xa_elem.value()
 
+    @value.setter
+    def value(self, value: Union[str, datetime, None]):
+        self.set_property('value', value)
+
     @property
     def id(self) -> str:
         return self.xa_elem.id()
@@ -843,41 +912,41 @@

Source code for PyXA.apps.Contacts

             obj_class = XAContactsEntry
         super().__init__(properties, obj_class, filter)
 
-
[docs] def modification_date(self) -> List[datetime]: +
[docs] def modification_date(self) -> list[datetime]: """Gets the last modification date of each contact entry in the list. :return: A list of contact entry modification dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("modificationDate"))
-
[docs] def creation_date(self) -> List[datetime]: +
[docs] def creation_date(self) -> list[datetime]: """Gets the creation date of each contact entry in the list. :return: A list of contact entry creation dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("creationDate"))
-
[docs] def id(self) -> List[str]: +
[docs] def id(self) -> list[str]: """Gets the ID of each contact entry in the list. :return: A list of contact entry IDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def selected(self) -> List[bool]: +
[docs] def selected(self) -> list[bool]: """Gets the selected status of each contact entry in the list. :return: A list of contact entry selected statuses - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.7 """ @@ -954,6 +1023,10 @@

Source code for PyXA.apps.Contacts

     def selected(self) -> bool:
         return self.xa_elem.selected()
 
+    @selected.setter
+    def selected(self, selected: bool):
+        self.set_property('selected', selected)
+
 
[docs] def add_to(self, parent: XABase.XAObject) -> 'XAContactsPerson': """Adds a child object to an entry. @@ -963,7 +1036,7 @@

Source code for PyXA.apps.Contacts

         :Example 1: Add a contact to a group
 
         >>> import PyXA
-        >>> app = PyXA.application("Contacts")
+        >>> app = PyXA.Application("Contacts")
         >>> group = app.groups().by_name("Example Group")
         >>> app.people()[0].add_to(group)
         >>> app.save()
@@ -982,7 +1055,7 @@ 

Source code for PyXA.apps.Contacts

         :Example 1: Remove a contact from a group
 
         >>> import PyXA
-        >>> app = PyXA.application("Contacts")
+        >>> app = PyXA.Application("Contacts")
         >>> group = app.groups().by_name("Example Group")
         >>> app.people()[0].add_to(group)
         >>> app.people()[0].remove_from(group)
@@ -1016,11 +1089,11 @@ 

Source code for PyXA.apps.Contacts

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, filter, XAContactsGroup)
 
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Gets the name of each contact group in the list. :return: A list of contact group names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ @@ -1052,6 +1125,10 @@

Source code for PyXA.apps.Contacts

     def name(self) -> str:
         return self.xa_elem.name()
 
+    @name.setter
+    def name(self, name: str):
+        self.set_property('name', name)
+
 
[docs] def groups(self, filter: Union[dict, None] = None) -> 'XAContactsGroupList': """Returns a list of groups, as PyXA objects, matching the given filter. @@ -1092,32 +1169,32 @@

Source code for PyXA.apps.Contacts

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, filter, XAContactsInstantMessage)
 
-
[docs] def service_name(self) -> List[str]: +
[docs] def service_name(self) -> list[str]: """Gets the service name of each IM address in the list. :return: A list of IM address service names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("serviceName"))
-
[docs] def service_type(self) -> List[XAContactsApplication.ServiceType]: +
[docs] def service_type(self) -> list[XAContactsApplication.ServiceType]: """Gets the service type of each IM address in the list. :return: A list of IM address service types - :rtype: List[XAContactsApplication.ServiceType] + :rtype: list[XAContactsApplication.ServiceType] .. versionadded:: 0.0.7 """ ls = self.xa_elem.arrayByApplyingSelector_("serviceType") return [XAContactsApplication.ServiceType(XABase.OSType(x.stringValue())) for x in ls]
-
[docs] def user_name(self) -> List[str]: +
[docs] def user_name(self) -> list[str]: """Gets the user name of each IM address in the list. :return: A list of IM address user names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ @@ -1176,10 +1253,18 @@

Source code for PyXA.apps.Contacts

     def service_type(self) -> XAContactsApplication.ServiceType:
         return XAContactsApplication.ServiceType(self.xa_elem.serviceType())
 
+    @service_type.setter
+    def service_type(self, service_type: XAContactsApplication.ServiceType):
+        self.set_property('serviceType', service_type.value)
+
     @property
     def user_name(self) -> str:
         return self.xa_elem.userName().get()
 
+    @user_name.setter
+    def user_name(self, user_name: str):
+        self.set_property('userName', user_name)
+
     def __repr__(self):
         return "<" + str(type(self)) + str(self.service_name) + ">"
@@ -1196,202 +1281,202 @@

Source code for PyXA.apps.Contacts

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, filter, XAContactsPerson)
 
-
[docs] def nickname(self) -> List[str]: +
[docs] def nickname(self) -> list[str]: """Gets the nickname of each person in the list. :return: A list of contact person nicknames - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("nickname"))
-
[docs] def organization(self) -> List[str]: +
[docs] def organization(self) -> list[str]: """Gets the organization of each person in the list. :return: A list of contact person organizations - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("organization"))
-
[docs] def maiden_name(self) -> List[str]: +
[docs] def maiden_name(self) -> list[str]: """Gets the maiden name of each person in the list. :return: A list of contact person maiden names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("maidenName"))
-
[docs] def suffix(self) -> List[str]: +
[docs] def suffix(self) -> list[str]: """Gets the suffix of each person in the list. :return: A list of contact person suffixes - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("suffix"))
-
[docs] def vcard(self) -> List[str]: +
[docs] def vcard(self) -> list[str]: """Gets the vCard representation of each person in the list. :return: A list of contact person vCard representations - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("vcard"))
-
[docs] def home_page(self) -> List[str]: +
[docs] def home_page(self) -> list[str]: """Gets the home page of each person in the list. :return: A list of contact person home pages - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("homePage"))
-
[docs] def birth_date(self) -> List[datetime]: +
[docs] def birth_date(self) -> list[datetime]: """Gets the birthdate of each person in the list. :return: A list of contact person birthdates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("birthdate"))
-
[docs] def phonetic_last_name(self) -> List[str]: +
[docs] def phonetic_last_name(self) -> list[str]: """Gets the phonetic last name of each person in the list. :return: A list of contact person phonetic last names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("phoneticLastName"))
-
[docs] def title(self) -> List[str]: +
[docs] def title(self) -> list[str]: """Gets the title of each person in the list. :return: A list of contact person titles - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("title"))
-
[docs] def phonetic_middle_name(self) -> List[str]: +
[docs] def phonetic_middle_name(self) -> list[str]: """Gets the phonetic middle name of each person in the list. :return: A list of contact person phonetic middle names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("phoneticMiddleName"))
-
[docs] def department(self) -> List[str]: +
[docs] def department(self) -> list[str]: """Gets the department of each person in the list. :return: A list of contact person departments - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("department"))
-
[docs] def image(self) -> List[XABase.XAImage]: +
[docs] def image(self) -> list[XABase.XAImage]: """Gets the image of each person in the list. :return: A list of contact person images - :rtype: List[XABase.XAImage] + :rtype: list[XABase.XAImage] .. versionadded:: 0.0.7 """ ls = self.xa_elem.arrayByApplyingSelector_("image") return [XABase.XAImage(x) for x in ls]
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Gets the name of each person in the list. :return: A list of contact person names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def note(self) -> List[str]: +
[docs] def note(self) -> list[str]: """Gets the notes of each person in the list. :return: A list of contact person notes - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("note"))
-
[docs] def company(self) -> List[bool]: +
[docs] def company(self) -> list[bool]: """Gets the company status of each "person" in the list. :return: A list of contact company statuses - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("company"))
-
[docs] def middle_name(self) -> List[str]: +
[docs] def middle_name(self) -> list[str]: """Gets the middle name of each person in the list. :return: A list of contact person middle names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("middleName"))
-
[docs] def phonetic_first_name(self) -> List[str]: +
[docs] def phonetic_first_name(self) -> list[str]: """Gets the phonetic first name of each person in the list. :return: A list of contact person phonetic first names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("phoneticFirstName"))
-
[docs] def job_title(self) -> List[str]: +
[docs] def job_title(self) -> list[str]: """Gets the job title of each person in the list. :return: A list of contact person job titles - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("jobTitle"))
-
[docs] def last_name(self) -> List[str]: +
[docs] def last_name(self) -> list[str]: """Gets the last name of each person in the list. :return: A list of contact person last names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("lastName"))
-
[docs] def first_name(self) -> List[str]: +
[docs] def first_name(self) -> list[str]: """Gets the first name of each person in the list. :return: A list of contact person first names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ @@ -1633,18 +1718,34 @@

Source code for PyXA.apps.Contacts

     def nickname(self) -> str:
         return self.xa_elem.nickname().get()
 
+    @nickname.setter
+    def nickname(self, nickname: str):
+        self.set_property('nickname', nickname)
+
     @property
     def organization(self) -> str:
         return self.xa_elem.organization().get()
 
+    @organization.setter
+    def organization(self, organization: str):
+        self.set_property('organization', organization)
+
     @property
     def maiden_name(self) -> str:
         return self.xa_elem.maidenName().get()
 
+    @maiden_name.setter
+    def maiden_name(self, maiden_name: str):
+        self.set_property('maidenName', maiden_name)
+
     @property
     def suffix(self) -> str:
         return self.xa_elem.suffix().get()
 
+    @suffix.setter
+    def suffix(self, suffix: str):
+        self.set_property('suffix', suffix)
+
     @property
     def vcard(self) -> str:
         return self.xa_elem.vcard().get()
@@ -1653,62 +1754,122 @@ 

Source code for PyXA.apps.Contacts

     def home_page(self) -> str:
         return self.xa_elem.homePage().get()
 
+    @home_page.setter
+    def home_page(self, home_page: str):
+        self.set_property('homePage', home_page)
+
     @property
     def birth_date(self) -> datetime:
         return self.xa_elem.birthDate().get()
 
+    @birth_date.setter
+    def birth_date(self, birth_date: datetime):
+        self.set_property('birthDate', birth_date)
+
     @property
     def phonetic_last_name(self) -> str:
         return self.xa_elem.phoneticLastName().get()
 
+    @phonetic_last_name.setter
+    def phonetic_last_name(self, phonetic_last_name: str):
+        self.set_property('phoneticLastName', phonetic_last_name)
+
     @property
     def title(self) -> str:
         return self.xa_elem.title().get()
 
+    @title.setter
+    def title(self, title: str):
+        self.set_property('title', title)
+
     @property
     def phonetic_middle_name(self) -> str:
         return self.xa_elem.phoneticMiddleNamne().get()
 
+    @phonetic_middle_name.setter
+    def phonetic_middle_name(self, phonetic_middle_name: str):
+        self.set_property('phoneticMiddleName', phonetic_middle_name)
+
     @property
     def department(self) -> str:
         return self.xa_elem.department().get()
 
+    @department.setter
+    def department(self, department: str):
+        self.set_property('department', department)
+
     @property
     def image(self) -> XABase.XAImage:
         return XABase.XAImage(self.xa_elem.image().get())
 
+    @image.setter
+    def image(self, image: XABase.XAImage):
+        self.set_property('image', image.xa_elem)
+
     @property
     def name(self) -> str:
         return self.xa_elem.name()
 
+    @name.setter
+    def name(self, name: str):
+        self.set_property('name', name)
+
     @property
     def note(self) -> str:
         return self.xa_elem.note().get()
 
+    @note.setter
+    def note(self, note: str):
+        self.set_property('note', note)
+
     @property
     def company(self) -> str:
         return self.xa_elem.company().get()
 
+    @company.setter
+    def company(self, company: str):
+        self.set_property('company', company)
+
     @property
     def middle_name(self) -> str:
         return self.xa_elem.middleName().get()
 
+    @middle_name.setter
+    def middle_name(self, middle_name: str):
+        self.set_property('middleName', middle_name)
+
     @property
     def phonetic_first_name(self) -> str:
         return self.xa_elem.phoneticFirstName().get()
 
+    @phonetic_first_name.setter
+    def phonetic_first_name(self, phonetic_first_name: str):
+        self.set_property('phoneticFirstName', phonetic_first_name)
+
     @property
     def job_title(self) -> str:
         return self.xa_elem.jobTitle().get()
 
+    @job_title.setter
+    def job_title(self, job_title: str):
+        self.set_property('jobTitle', job_title)
+
     @property
     def last_name(self) -> str:
         return self.xa_elem.lastName().get()
 
+    @last_name.setter
+    def last_name(self, last_name: str):
+        self.set_property('lastName', last_name)
+
     @property
     def first_name(self) -> str:
         return self.xa_elem.firstName().get()
 
+    @first_name.setter
+    def first_name(self, first_name: str):
+        self.set_property('firstName', first_name)
+
 
[docs] def show(self) -> 'XAContactsPerson': """Shows the contact card for this contact in Contacts.app. @@ -1888,51 +2049,51 @@

Source code for PyXA.apps.Contacts

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XAContactsSocialProfile, filter)
 
-
[docs] def id(self) -> List[str]: +
[docs] def id(self) -> list[str]: """Gets the ID of each social profile in the list. :return: A list of social profile IDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def service_name(self) -> List[str]: +
[docs] def service_name(self) -> list[str]: """Gets the service name of each social profile in the list. :return: A list of social profile service names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("serviceName"))
-
[docs] def user_name(self) -> List[str]: +
[docs] def user_name(self) -> list[str]: """Gets the user name of each social profile in the list. :return: A list of social profile user names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("userName"))
-
[docs] def user_identifier(self) -> List[str]: +
[docs] def user_identifier(self) -> list[str]: """Gets the user identifier of each social profile in the list. :return: A list of social profile user identifiers - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ return list(self.xa_elem.arrayByApplyingSelector_("userIdentifier"))
-
[docs] def url(self) -> List[str]: +
[docs] def url(self) -> list[str]: """Gets the URL of each social profile in the list. :return: A list of social profile URLs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.7 """ @@ -2012,18 +2173,34 @@

Source code for PyXA.apps.Contacts

     def service_name(self) -> str:
         return self.xa_elem.serviceName().get()
 
+    @service_name.setter
+    def service_name(self, service_name: str):
+        self.set_property('serviceName', service_name)
+
     @property
     def user_name(self) -> str:
         return self.xa_elem.userName().get()
 
+    @user_name.setter
+    def user_name(self, user_name: str):
+        self.set_property('userName', user_name)
+
     @property
     def user_identifier(self) -> str:
         return self.xa_elem.userIdentifier().get()
 
+    @user_identifier.setter
+    def user_identifier(self, user_identifier: str):
+        self.set_property('userIdentifier', user_identifier)
+
     @property
     def url(self) -> str:
         return self.xa_elem.url()
 
+    @url.setter
+    def url(self, url: str):
+        self.set_property('url', url)
+
     def __repr__(self):
         return "<" + str(type(self)) + str(self.user_name) + ">"
diff --git a/docs/_modules/PyXA/apps/Dictionary 2.html b/docs/_modules/PyXA/apps/Dictionary 2.html new file mode 100644 index 0000000..2ff9662 --- /dev/null +++ b/docs/_modules/PyXA/apps/Dictionary 2.html @@ -0,0 +1,340 @@ + + + + + + PyXA.apps.Dictionary — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Dictionary

+""".. versionadded:: 0.0.2
+
+Control the macOS Dictionary application using JXA-like syntax.
+"""
+
+from CoreServices import DCSCopyTextDefinition
+
+from PyXA import XABase
+
+
[docs]class XADictionaryApplication(XABase.XAApplication): + """A class for managing and interacting with Dictionary.app. + + .. seealso:: :class:`XADictionaryDefinition` + + .. versionadded:: 0.0.2 + """ + def __init__(self, properties): + super().__init__(properties) + +
[docs] def define(self, word: str) -> str: + """Gets the definition of a word in raw text form. + + :param word: The word to define + :type word: str + :return: The definition of the word + :rtype: str + + .. versionadded:: 0.0.2 + """ + definition = DCSCopyTextDefinition(None, word, (0, len(word))) + return definition
+ + ### UI Interaction + ## Menu Bar + # Dictionary Menu +
[docs] def open_about_panel(self): + """Opens the "About Dictionary" panel. Mimics clicking File->About Dictionary. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(1).menu(0).menu_item(0).press()
+ +
[docs] def open_preferences(self): + """Opens the preferences window for the Dictionary application. Mimics clicking File->Preferences... + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(1).menu(0).menu_item(2).press()
+ + # File Menu +
[docs] def new_window(self): + """Opens a new Dictionary window. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(2).menu(0).menu_item(0).press()
+ +
[docs] def new_tab(self): + """Opens a new Dictionary tab. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(2).menu(0).menu_item(1).press()
+ +
[docs] def open_dictionaries_folder(self): + """Opens the folder containing custom/downloaded dictionaries. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(2).menu(0).menu_item(-3).press()
+ +
[docs] def print(self): + """Opens the dictionary app's print dialog. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(2).menu(0).menu_item(-1).press()
+ + # Edit Menu +
[docs] def paste(self): + """Pastes the current contents of the clipboard into the selected item, if there is one. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(3).menu(0).menu_item(5).press()
+ +
[docs] def start_dictation(self): + """Begins dictation to fill the selected item, if there is one. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(3).menu(0).menu_item(-2).press()
+ + # Go Menu +
[docs] def go_back(self): + """Goes to the previous page/definition. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(4).menu(0).menu_item(0).press()
+ +
[docs] def go_forward(self): + """Goes to the next page/definition. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(4).menu(0).menu_item(1).press()
+ +
[docs] def view_front_back_matter(self): + """Displays the front/back matter of the selected dictionary. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(4).menu(0).menu_item(3).press()
+ + # Window Menu +
[docs] def fullscreen(self): + """Toggles fullscreen for the current Dictionary window. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(6).menu(0).menu_item(9).press()
+ + # Help Menu +
[docs] def show_help(self): + """Displays the help window for Dictionary.app. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(7).menu(0).menu_item(-1).press()
+ + ## Window +
[docs] def switch_to_all(self): + """Switches to searching all installed dictionaries. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(5).menu(0).menu_item(0).press()
+ +
[docs] def switch_to_new_oxford(self): + """Switches to searching the New Oxford American Dictionary. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(5).menu(0).menu_item(2).press()
+ +
[docs] def switch_to_oxford_thesaurus(self): + """Switches to searching the Oxford American Writer's Thesaurus. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(5).menu(0).menu_item(3).press()
+ +
[docs] def switch_to_apple_dictionary(self): + """Switches to searching the Apple Dictionary. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(5).menu(0).menu_item(4).press()
+ +
[docs] def switch_to_wikipedia(self): + """Switches to searching Wikipedia. + + .. versionadded:: 0.0.2 + """ + self.menu_bar(0).menu_bar_item(5).menu(0).menu_item(5).press()
+ +
[docs] def search(self, term: str): + """Searches the currently selected dictionary. + + :param term: The term to search + :type term: str + + .. versionadded:: 0.0.2 + """ + if hasattr(self.window(0).toolbar(0).group(2).text_field(0), "value"): + # Search from empty + self.window(0).toolbar(0).group(2).text_field(0).set_property("value", term) + else: + # Search from searched word + self.window(0).toolbar(0).group(0).text_field(0).set_property("value", "") + print("hi") + self.window(0).toolbar(0).group(2).text_field(0).set_property("value", term)
+ +
[docs] def search_all(self, term: str): + """Searches the provided term in all dictionaries + + :param term: The term to search + :type term: str + + .. versionadded:: 0.0.2 + """ + self.switch_to_all() + self.search(term)
+ +
[docs] def search_new_oxford(self, term: str): + """Searches the provided term in the New Oxford American Dictionary + + :param term: The term to search + :type term: str + + .. versionadded:: 0.0.2 + """ + self.switch_to_new_oxford() + self.search(term)
+ +
[docs] def search_oxford_thesaurus(self, term: str): + """Searches the provided term in the Oxford American Writer's Thesaurus + + :param term: The term to search + :type term: str + + .. versionadded:: 0.0.2 + """ + self.switch_to_oxford_thesaurus() + self.search(term)
+ +
[docs] def search_apple_dictionary(self, term: str): + """Searches the provided term in the Apple Dictionary + + :param term: The term to search + :type term: str + + .. versionadded:: 0.0.2 + """ + self.switch_to_apple_dictionary() + self.search(term)
+ +
[docs] def search_wikipedia(self, term: str): + """Searches the provided term in Wikipedia + + :param term: The term to search + :type term: str + + .. versionadded:: 0.0.2 + """ + self.switch_to_wikipedia() + self.search(term)
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Dictionary.html b/docs/_modules/PyXA/apps/Dictionary.html index b8c1d4b..2ff9662 100644 --- a/docs/_modules/PyXA/apps/Dictionary.html +++ b/docs/_modules/PyXA/apps/Dictionary.html @@ -14,7 +14,9 @@ + + diff --git a/docs/_modules/PyXA/apps/Drafts 2.html b/docs/_modules/PyXA/apps/Drafts 2.html new file mode 100644 index 0000000..2ad9ea6 --- /dev/null +++ b/docs/_modules/PyXA/apps/Drafts 2.html @@ -0,0 +1,440 @@ + + + + + + PyXA.apps.Drafts — PyXA 0.0.9 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for PyXA.apps.Drafts

+""".. versionadded:: 0.0.8
+
+Control Drafts using JXA-like syntax.
+"""
+
+from datetime import datetime
+from typing import List, Union
+
+from PyXA import XABase
+from PyXA import XABaseScriptable
+from ..XAProtocols import XACanOpenPath
+
+
[docs]class XADraftsApplication(XABaseScriptable.XASBApplication, XACanOpenPath): + """A class for managing and interacting with Drafts.app. + + .. versionadded:: 0.0.8 + """ + def __init__(self, properties): + super().__init__(properties) + + self.name: str #: The name of the application + self.version: str #: The version of Drafts.app + + @property + def name(self) -> str: + return self.xa_scel.name() + + @property + def version(self) -> str: + return self.xa_scel.version() + +
[docs] def new_draft(self, content: str) -> 'XADraftsDraft': + """Creates a new draft with the given name and content + + :param content: The full content of the draft (the first line is the name) + :type content: str + :return: The newly created draft object + :rtype: XADraftsDraft + + .. versionadded:: 0.0.8 + """ + new_draft = self.make("draft", {"content": content}) + self.drafts().push(new_draft) + print(self.drafts().content()) + return self.drafts().last()
+ +
[docs] def drafts(self, filter: Union[dict, None] = None) -> 'XADraftsDraftList': + """Returns a list of drafts, as PyXA-wrapped objects, matching the given filter. + + :param filter: Keys and values to filter drafts by, defaults to None + :type filter: dict, optional + :return: A PyXA list object wrapping a list of drafts + :rtype: XADraftsDraftList + + .. versionadded:: 0.0.8 + """ + return self._new_element(self.xa_scel.drafts(), XADraftsDraftList, filter)
+ +
[docs] def make(self, specifier: str, properties: dict = None): + """Creates a new element of the given specifier class without adding it to any list. + + Use :func:`XABase.XAList.push` to push the element onto a list. + + :param specifier: The classname of the object to create + :type specifier: str + :param properties: The properties to give the object + :type properties: dict + :return: A PyXA wrapped form of the object + :rtype: XABase.XAObject + + .. versionadded:: 0.0.8 + """ + if properties is None: + properties = {} + + obj = self.xa_scel.classForScriptingClass_(specifier).alloc().initWithProperties_(properties) + + if specifier == "draft": + return self._new_element(obj, XADraftsDraft)
+ + + + +
[docs]class XADraftsDraftList(XABase.XAList): + """A wrapper around a list of drafts. + + .. versionadded:: 0.0.8 + """ + def __init__(self, properties: dict, filter: Union[dict, None] = None): + super().__init__(properties, XADraftsDraft, filter) + +
[docs] def id(self) -> List[str]: + """Gets the ID of each draft in the list. + + :return: A list of draft IDs + :rtype: List[str] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("id"))
+ +
[docs] def name(self) -> List[str]: + """Gets the name of each draft in the list. + + :return: A list of draft names + :rtype: List[str] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("name"))
+ +
[docs] def content(self) -> List[str]: + """Gets the content of each draft in the list. + + :return: A list of draft contents + :rtype: List[str] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("content"))
+ +
[docs] def flagged(self) -> List[bool]: + """Gets the flagged status of each draft in the list. + + :return: A list of draft flagged statuses + :rtype: List[bool] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("flagged"))
+ +
[docs] def tags(self) -> List[List[str]]: + """Gets the tags of each draft in the list. + + :return: A list of draft tags + :rtype: List[List[str]] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("tags"))
+ +
[docs] def created_at(self) -> List[datetime]: + """Gets the creation date of each draft in the list. + + :return: A list of draft creation dates + :rtype: List[datetime] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("createdAt"))
+ +
[docs] def modified_at(self) -> List[datetime]: + """Gets the last modification date of each draft in the list. + + :return: A list of draft modification dates + :rtype: List[datetime] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("modifiedAt"))
+ +
[docs] def accessed_at(self) -> List[datetime]: + """Gets the last access date of each draft in the list. + + :return: A list of draft last access dates + :rtype: List[datetime] + + .. versionadded:: 0.0.8 + """ + return list(self.xa_elem.arrayByApplyingSelector_("accessedAt"))
+ + + +
[docs] def by_id(self, id: str) -> Union['XADraftsDraft', None]: + """Retrieves the draft whose ID matches the given ID, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("id", id)
+ +
[docs] def by_name(self, name: str) -> Union['XADraftsDraft', None]: + """Retrieves the draft whose name matches the given name, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("name", name)
+ +
[docs] def by_content(self, content: str) -> Union['XADraftsDraft', None]: + """Retrieves the draft whose content matches the given content, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("content", content)
+ +
[docs] def by_flagged(self, flagged: bool) -> Union['XADraftsDraft', None]: + """Retrieves the first draft whose flagged status matches the given boolean value, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("flagged", flagged)
+ +
[docs] def by_tags(self, tags: List[str]) -> Union['XADraftsDraft', None]: + """Retrieves the first draft whose list of tags matches the given list, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("tags", tags)
+ +
[docs] def by_created_at(self, created_at: datetime) -> Union['XADraftsDraft', None]: + """Retrieves the draft whose creation date matches the given date, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("createdAt", created_at)
+ +
[docs] def by_modified_at(self, modified_at: datetime) -> Union['XADraftsDraft', None]: + """Retrieves the draft whose last modification date matches the given date, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("modifiedAt", modified_at)
+ +
[docs] def by_accessed_at(self, accessed_at: datetime) -> Union['XADraftsDraft', None]: + """Retrieves the draft whose last access date matches the given date, if one exists. + + :return: The desired draft, if it is found + :rtype: Union[XADraftsDraft, None] + + .. versionadded:: 0.0.8 + """ + return self.by_property("accessedAt", accessed_at)
+ +
+ +
[docs]class XADraftsDraft(XABase.XAObject): + """A draft in Drafts.app. + + .. versionadded:: 0.0.8 + """ + def __init__(self, properties): + super().__init__(properties) + self.id: str #: The unique identifier of the draft + self.name: str #: The first line of the draft + self.content: str #: The content of the draft + self.flagged: bool #: The flagged status of the draft + self.tags: List[str] #: The tags assigned to the draft + self.created_at: datetime #: The date the draft was created + self.modified_at: datetime #: The date the draft was last modified + self.accessed_at: datetime #: The date the draft was last accessed + self.permalink: str #: The URL of the draft + + @property + def id(self) -> str: + return self.xa_elem.id() + + @property + def name(self) -> str: + return self.xa_elem.name() + + @property + def content(self) -> str: + return self.xa_elem.content() + + @content.setter + def content(self, content: str): + self.set_property("content", content) + + @property + def flagged(self) -> bool: + return self.xa_elem.flagged() + + @flagged.setter + def flagged(self, flagged: bool): + self.set_property("flagged", flagged) + + @property + def tags(self) -> List[str]: + return self.xa_elem.tags() + + @tags.setter + def tags(self, tags: List[str]): + self.set_property("tags", tags) + + @property + def created_at(self) -> datetime: + return self.xa_elem.createdAt() + + @property + def modified_at(self) -> datetime: + return self.xa_elem.modifiedAt() + + @property + def accessed_at(self) -> datetime: + return self.xa_elem.accessedAt() + + @property + def permalink(self) -> str: + return self.xa_elem.permalink() + + def __repr__(self): + return "<" + str(type(self)) + str(self.name) + ">"
+
+ +
+
+
+ +
+ +
+

© Copyright 2022, Stephen Kaplan.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_modules/PyXA/apps/Drafts.html b/docs/_modules/PyXA/apps/Drafts.html index 157d28e..aca948f 100644 --- a/docs/_modules/PyXA/apps/Drafts.html +++ b/docs/_modules/PyXA/apps/Drafts.html @@ -3,7 +3,7 @@ - PyXA.apps.Drafts — PyXA 0.0.9 documentation + PyXA.apps.Drafts — PyXA 0.1.0 documentation @@ -14,7 +14,9 @@ + + @@ -74,7 +76,7 @@

Source code for PyXA.apps.Drafts

 """
 
 from datetime import datetime
-from typing import List, Union
+from typing import Union
 
 from PyXA import XABase
 from PyXA import XABaseScriptable
@@ -159,91 +161,91 @@ 

Source code for PyXA.apps.Drafts

     def __init__(self, properties: dict, filter: Union[dict, None] = None):
         super().__init__(properties, XADraftsDraft, filter)
 
-
[docs] def id(self) -> List[str]: +
[docs] def id(self) -> list[str]: """Gets the ID of each draft in the list. :return: A list of draft IDs - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("id"))
-
[docs] def name(self) -> List[str]: +
[docs] def name(self) -> list[str]: """Gets the name of each draft in the list. :return: A list of draft names - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("name"))
-
[docs] def content(self) -> List[str]: +
[docs] def content(self) -> list[str]: """Gets the content of each draft in the list. :return: A list of draft contents - :rtype: List[str] + :rtype: list[str] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("content"))
-
[docs] def flagged(self) -> List[bool]: +
[docs] def flagged(self) -> list[bool]: """Gets the flagged status of each draft in the list. :return: A list of draft flagged statuses - :rtype: List[bool] + :rtype: list[bool] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("flagged"))
-
[docs] def tags(self) -> List[List[str]]: +
[docs] def tags(self) -> list[list[str]]: """Gets the tags of each draft in the list. :return: A list of draft tags - :rtype: List[List[str]] + :rtype: list[list[str]] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("tags"))
-
[docs] def created_at(self) -> List[datetime]: +
[docs] def created_at(self) -> list[datetime]: """Gets the creation date of each draft in the list. :return: A list of draft creation dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("createdAt"))
-
[docs] def modified_at(self) -> List[datetime]: +
[docs] def modified_at(self) -> list[datetime]: """Gets the last modification date of each draft in the list. :return: A list of draft modification dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("modifiedAt"))
-
[docs] def accessed_at(self) -> List[datetime]: +
[docs] def accessed_at(self) -> list[datetime]: """Gets the last access date of each draft in the list. :return: A list of draft last access dates - :rtype: List[datetime] + :rtype: list[datetime] .. versionadded:: 0.0.8 """ return list(self.xa_elem.arrayByApplyingSelector_("accessedAt"))
-