예제 #1
0
파일: plugin.py 프로젝트: saluzi/declaracad
class DeclaracadPlugin(Plugin):
    #: Project site
    wiki_page = Str("https;//declaracad.com/")

    #: Dock items to add
    dock_items = List(DockItem)
    dock_layout = Instance(AreaLayout)
    dock_style = Enum(*reversed(ALL_STYLES)).tag(config=True)

    #: Settings pages to add
    settings_pages = List(extensions.SettingsPage)

    #: Current settings page
    settings_page = Instance(extensions.SettingsPage)

    #: Internal settings models
    settings_model = Instance(Atom)

    def start(self):
        """ Load all the plugins declaracad is dependent on """
        w = self.workbench
        super(DeclaracadPlugin, self).start()
        self._refresh_dock_items()
        self._refresh_settings_pages()

    def _bind_observers(self):
        """ Setup the observers for the plugin.
        """
        super(DeclaracadPlugin, self)._bind_observers()
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DOCK_ITEM_POINT)
        point.observe('extensions', self._refresh_dock_items)

        point = workbench.get_extension_point(extensions.SETTINGS_PAGE_POINT)
        point.observe('extensions', self._refresh_settings_pages)

    def _unbind_observers(self):
        """ Remove the observers for the plugin.
        """
        super(DeclaracadPlugin, self)._unbind_observers()
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DOCK_ITEM_POINT)
        point.unobserve('extensions', self._refresh_dock_items)

        point = workbench.get_extension_point(extensions.SETTINGS_PAGE_POINT)
        point.unobserve('extensions', self._refresh_settings_pages)

    # -------------------------------------------------------------------------
    # Dock API
    # -------------------------------------------------------------------------
    def create_new_area(self):
        """ Create the dock area
        """
        with enaml.imports():
            from .dock import DockView
        area = DockView(workbench=self.workbench, plugin=self)
        return area

    def get_dock_area(self):
        """ Get the dock area

        Returns
        -------
            area: DockArea
        """
        ui = self.workbench.get_plugin('enaml.workbench.ui')
        if not ui.workspace or not ui.workspace.content:
            ui.select_workspace('declaracad.workspace')
        return ui.workspace.content.find('dock_area')

    def _refresh_dock_items(self, change=None):
        """ Reload all DockItems registered by any Plugins

        Any plugin can add to this list by providing a DockItem
        extension in their PluginManifest.

        """
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DOCK_ITEM_POINT)

        #: Layout spec
        layout = {name: [] for name in extensions.DockItem.layout.items}

        dock_items = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for declaration in extension.get_children(extensions.DockItem):
                # Create the item
                DockItem = declaration.factory()
                plugin = workbench.get_plugin(declaration.plugin_id)
                item = DockItem(plugin=plugin)

                # Add to our layout
                layout[declaration.layout].append(item.name)

                # Save it
                dock_items.append(item)

        # Update items
        log.debug("Updating dock items: {}".format(dock_items))
        self.dock_items = dock_items
        self._refresh_layout(layout)

    def _refresh_layout(self, layout):
        """ Create the layout for all the plugins


        """
        if not self.dock_items:
            return AreaLayout()
        items = layout.pop('main')
        if not items:
            raise EnvironmentError("At least one main layout item must be "
                                   "defined!")

        left_items = layout.pop('main-left', [])
        bottom_items = layout.pop('main-bottom', [])

        main = TabLayout(*items)

        if bottom_items:
            main = VSplitLayout(main, *bottom_items)
        if left_items:
            main = HSplitLayout(*left_items, main)

        dockbars = [
            DockBarLayout(*items, position=side)
            for side, items in layout.items() if items
        ]

        #: Update layout
        self.dock_layout = AreaLayout(main, dock_bars=dockbars)

    # -------------------------------------------------------------------------
    # Settings API
    # -------------------------------------------------------------------------
    def _default_settings_page(self):
        return self.settings_pages[0]

    def _observe_settings_page(self, change):
        log.debug("Settings page: {}".format(change))

    def _refresh_settings_pages(self, change=None):
        """ Reload all SettingsPages registered by any Plugins

        Any plugin can add to this list by providing a SettingsPage
        extension in their PluginManifest.

        """
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.SETTINGS_PAGE_POINT)

        settings_pages = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for d in extension.get_children(extensions.SettingsPage):
                settings_pages.append(d)

        #: Update items
        settings_pages.sort(key=lambda p: p.name)
        log.debug("Updating settings pages: {}".format(settings_pages))
        self.settings_pages = settings_pages
예제 #2
0
class Looper(Pattern):
    """ A pattern object that repeats its children over an iterable.

    The children of a `Looper` are used as a template when creating new
    objects for each item in the given `iterable`. Each iteration of the
    loop will be given an independent scope which is the union of the
    outer scope and any identifiers created during the iteration. This
    scope will also contain a `loop` variable which has `item` and `index`
    members to access the index and value of the iterable, respectively.

    All items created by the looper will be added as children of the
    parent of the `Looper`. The `Looper` keeps ownership of all items
    it creates. When the iterable for the looper is changed, the looper
    will only create and destroy children for the items in the iterable
    which have changed. When an item in the iterable is moved the
    `loop.index` will be updated to reflect the new index.

    The Looper works under the assumption that the values stored in the
    iterable are unique.

    The `loop_item` and `loop_index` scope variables are depreciated in favor
    of `loop.item` and `loop.index` respectively. This is because the old
    `loop_index` variable may become invalid when items are moved.

    """
    #: The iterable to use when creating the items for the looper.
    #: The items in the iterable must be unique. This allows the
    #: Looper to optimize the creation and destruction of widgets.
    iterable = d_(Instance(Iterable))

    #: The list of items created by the conditional. Each item in the
    #: list represents one iteration of the loop and is a list of the
    #: items generated during that iteration. This list should not be
    #: manipulated directly by user code.
    items = List()

    #: Private data storage which maps the user iterable data to the
    #: list of items created for that iteration. This allows the looper
    #: to only create and destroy the items which have changed.
    _iter_data = Typed(sortedmap, ())

    #--------------------------------------------------------------------------
    # Lifetime API
    #--------------------------------------------------------------------------
    def destroy(self):
        """ A reimplemented destructor.

        The looper will release the owned items on destruction.

        """
        super(Looper, self).destroy()
        del self.iterable
        del self.items
        del self._iter_data

    #--------------------------------------------------------------------------
    # Observers
    #--------------------------------------------------------------------------
    def _observe_iterable(self, change):
        """ A private observer for the `iterable` attribute.

        If the iterable changes while the looper is active, the loop
        items will be refreshed.

        """
        if change['type'] == 'update' and self.is_initialized:
            self.refresh_items()

    #--------------------------------------------------------------------------
    # Pattern API
    #--------------------------------------------------------------------------
    def pattern_items(self):
        """ Get a list of items created by the pattern.

        """
        return sum(self.items, [])

    def refresh_items(self):
        """ Refresh the items of the pattern.

        This method destroys the old items and creates and initializes
        the new items.

        """
        old_items = self.items[:]
        old_iter_data = self._iter_data
        iterable = self.iterable
        pattern_nodes = self.pattern_nodes
        new_iter_data = sortedmap()
        new_items = []

        if iterable is not None and len(pattern_nodes) > 0:
            for loop_index, loop_item in enumerate(iterable):
                iter_data = old_iter_data.get(loop_item)
                if iter_data is not None:
                    new_iter_data[loop_item] = iter_data
                    iteration = iter_data.nodes
                    new_items.append(iteration)
                    old_items.remove(iteration)
                    iter_data.index = loop_index
                    continue
                iter_data = Iteration(index=loop_index, item=loop_item)
                iteration = iter_data.nodes
                new_iter_data[loop_item] = iter_data
                new_items.append(iteration)
                for nodes, key, f_locals in pattern_nodes:
                    with new_scope(key, f_locals) as f_locals:
                        # Retain for compatibility reasons
                        f_locals['loop_index'] = loop_index
                        f_locals['loop_item'] = loop_item
                        f_locals['loop'] = iter_data
                        for node in nodes:
                            child = node(None)
                            if isinstance(child, list):
                                iteration.extend(child)
                            else:
                                iteration.append(child)

        for iteration in old_items:
            for old in iteration:
                if not old.is_destroyed:
                    old.destroy()

        if len(new_items) > 0:
            expanded = []
            recursive_expand(sum(new_items, []), expanded)
            self.parent.insert_children(self, expanded)

        self.items = new_items
        self._iter_data = new_iter_data
예제 #3
0
class Device(Model):
    """ The standard device. This is a standard model used throughout the
    application. An instance of this is configured by specifying a
    'DeviceDriver' in a plugin manifest.

    It simply delegates connection and handling to the selected transport
    and protocol respectively.

    """

    #: Internal model for drawing the preview on screen
    area = Instance(AreaBase)

    #: The declaration that defined this device
    declaration = Typed(extensions.DeviceDriver).tag(config=True)

    #: Protocols supported by this device (ex the HPGLProtocol)
    protocols = List(extensions.DeviceProtocol)

    #: Transports supported by this device (ex the SerialPort
    transports = List(extensions.DeviceTransport)

    #: Filters that this device applies to the output
    filters = List(DeviceFilter).tag(config=True)

    #: The active transport
    connection = Instance(DeviceTransport).tag(config=True)

    #: List of jobs that were run on this device
    jobs = List(Model).tag(config=True)

    #: List of jobs queued to run on this device
    queue = List(Model).tag(config=True)

    #: Current job being processed
    job = Instance(Model)  #.tag(config=True)

    #: The device specific config
    config = Instance(DeviceConfig, ()).tag(config=True)

    #: Position. Defaults to x,y,z. The protocol can
    #: handle this however necessary.
    position = ContainerList(default=[0, 0, 0])

    #: Origin position. Defaults to [0, 0, 0]. The system will translate
    #: jobs to the origin so multiple can be run.
    origin = ContainerList(default=[0, 0, 0])

    #: Device is currently busy processing a job
    busy = Bool()

    #: Status
    status = Unicode()

    def _default_connection(self):
        """ If no connection is set when the device is created,
        create one using the first "connection" type the driver supports.
        """
        if not self.transports:
            return TestTransport()
        declaration = self.transports[0]
        driver = self.declaration
        protocol = self._default_protocol()
        return declaration.factory(driver, declaration, protocol)

    def _default_protocol(self):
        """ Create the protocol for this device. """
        if not self.protocols:
            return DeviceProtocol()
        declaration = self.protocols[0]
        driver = self.declaration
        return declaration.factory(driver, declaration)

    def _default_area(self):
        """ Create the area based on the size specified by the Device Driver

        """
        d = self.declaration
        area = AreaBase()
        w = parse_unit(d.width)
        if d.length:
            h = parse_unit(d.length)
        else:
            h = 900000
        area.size = [w, h]
        return area

    @observe('declaration.width', 'declaration.length')
    def _refresh_area(self, change):
        self.area = self._default_area()

    @contextmanager
    def device_busy(self):
        """ Mark the device as busy """
        self.busy = True
        try:
            yield
        finally:
            self.busy = False

    @contextmanager
    def device_connection(self, test=False):
        """ """
        connection = self.connection
        try:
            #: Create a test connection if necessary
            if test:
                self.connection = TestTransport(
                    protocol=connection.protocol,
                    declaration=connection.declaration)
            #: Connect
            yield self.connection
        finally:
            #: Restore if necessary
            if test:
                self.connection = connection

    @defer.inlineCallbacks
    def test(self):
        """ Execute a test job on the device. This creates
        and submits new job that is simply a small square.

        """
        raise NotImplementedError

    def transform(self, path):
        """ Apply the device output transform to the given path. This
        is used by other plugins that may need to display or work with
        tranformed output.

        Parameters
        ----------
            path: QPainterPath
                Path to transform

        Returns
        -------
            path: QPainterPath

        """
        config = self.config

        t = QtGui.QTransform()

        #: Order matters!
        if config.scale:
            #: Do final output scaling
            t.scale(*config.scale)

        if config.rotation:
            #: Do final output rotation
            t.rotate(config.rotation)

        #: TODO: Translate back to 0,0 so all coordinates are positive
        path = path * t

        return path

    def init(self, job):
        """ Initialize the job. This should do any final path manipulation
        required by the device (or as specified by the config) and any filters
        should be applied here (overcut, blade offset compensation, etc..).

        The connection is not active at this stage.

        Parameters
        -----------
            job: inkcut.job.models.Job instance
                The job to handle.

        Returns
        --------
            model: QtGui.QPainterPath instance or Deferred that resolves
                to a QPainterPath if heavy processing is needed. This path
                is then interpolated and sent to the device.

        """
        log.debug("device | init {}".format(job))
        config = self.config

        #: Set the speed of this device for tracking purposes
        units = config.speed_units.split("/")[0]
        job.info.speed = from_unit(config.speed, units)

        #: Get the internal QPainterPath "model"
        model = job.model

        #: Transform the path to the device coordinates
        model = self.transform(model)

        if job.feed_to_end:
            #: Move the job to the new origin
            x, y, z = self.origin
            model.translate(x, -y)

        #: TODO: Apply filters here

        #: Return the transformed model
        return model

    @defer.inlineCallbacks
    def connect(self):
        """ Connect to the device. By default this delegates handling
        to the active transport or connection handler.

        Returns
        -------
            result: Deferred or None
                May return a Deferred object that the process will wait for
                completion before continuing.

        """
        log.debug("device | connect")
        yield defer.maybeDeferred(self.connection.connect)
        cmd = self.config.commands_connect
        if cmd:
            yield defer.maybeDeferred(self.connection.write, cmd)

    def move(self, position, absolute=True):
        """ Move to the given position. By default this delegates handling
        to the active protocol.

        Parameters
        ----------
            position: List of coordinates to move to.
                Desired position to move or move to (if using absolute
                coordinates).
            absolute: bool
                Position is in absolute coordinates
        Returns
        -------
            result: Deferred or None
                May return a deferred object that the process will wait for
                completion before continuing.

        """
        if absolute:
            #: Clip everything to never go below zero in absolute mode
            position = [max(0, p) for p in position]
            self.position = position
        else:
            #: Convert to relative to absolute for the UI
            p = self.position
            p[0] += position[0]
            p[1] += position[1]
            self.position = p

        result = self.connection.protocol.move(*position, absolute=absolute)
        if result:
            return result

    def finish(self):
        """ Finish the job applying any cleanup necessary.

        """
        log.debug("device | finish")
        return self.connection.protocol.finish()

    @defer.inlineCallbacks
    def disconnect(self):
        """ Disconnect from the device. By default this delegates handling
        to the active transport or connection handler.

        """
        log.debug("device | disconnect")
        cmd = self.config.commands_disconnect
        if cmd:
            yield defer.maybeDeferred(self.connection.write, cmd)
        yield defer.maybeDeferred(self.connection.disconnect)

    @defer.inlineCallbacks
    def submit(self, job, test=False):
        """ Submit the job to the device. If the device is currently running
        a job it will be queued and run when this is finished.

        This handles iteration over the path model defined by the job and
        sending commands to the actual device using roughly the procedure is
        as follows:

                device.connect()

                model = device.init(job)
                for cmd in device.process(model):
                    device.handle(cmd)
                device.finish()

                device.disconnect()

        Subclasses provided by your own DeviceDriver may reimplement this
        to handle path interpolation however needed. The return value is
        ignored.

        The live plot view will update whenever the device.position object
        is updated. On devices with lower cpu/gpu capabilities this should
        be updated sparingly (ie the raspberry pi).

        Parameters
        -----------
            job: Instance of `inkcut.job.models.Job`
                The job to execute on the device
            test: bool
                Do a test run. This specifies whether the commands should be
                sent to the actual device or not. If True, the connection will
                be replaced with a virtual connection that captures all the
                command output.

        """
        log.debug("device | submit {}".format(job))
        try:

            #: Only allow one job at a time
            if self.busy:
                queue = self.queue[:]
                queue.append(job)
                self.queue = queue  #: Copy and reassign so the UI updates
                log.info("Job {} put in device queue".format(job))
                return

            with self.device_busy():
                #: Set the current the job
                self.job = job
                self.status = "Initializing job"

                #: Get the time to sleep based for each unit of movement
                config = self.config

                #: Rate px/ms
                if config.custom_rate >= 0:
                    rate = config.custom_rate
                elif self.connection.always_spools or config.spooled:
                    rate = 0
                elif config.interpolate:
                    if config.step_time > 0:
                        rate = config.step_size / float(config.step_time)
                    else:
                        rate = 0  # Undefined
                else:
                    rate = from_unit(
                        config.speed,  # in/s or cm/s
                        config.speed_units.split("/")[0]) / 1000.0

                # Device model is updated in real time
                model = yield defer.maybeDeferred(self.init, job)

                #: Local references are faster
                info = job.info

                #: Determine the length for tracking progress
                whole_path = QtGui.QPainterPath()

                #: Some versions of Qt seem to require a value in
                #: toSubpathPolygons
                m = QtGui.QTransform.fromScale(1, 1)
                for path in model.toSubpathPolygons(m):
                    for i, p in enumerate(path):
                        whole_path.lineTo(p)
                total_length = whole_path.length()
                total_moved = 0
                log.debug("device | Path length: {}".format(total_length))

                #: So a estimate of the duration can be determined
                info.length = total_length
                info.speed = rate * 1000  #: Convert to px/s

                #: Waiting for approval
                info.status = 'waiting'

                #: If marked for auto approve start now
                if info.auto_approve:
                    info.status = 'approved'
                else:
                    #: Check for approval before starting
                    yield defer.maybeDeferred(info.request_approval)
                    if info.status != 'approved':
                        self.status = "Job cancelled"
                        return

                #: Update stats
                info.status = 'running'
                info.started = datetime.now()

                self.status = "Connecting to device"
                with self.device_connection(test
                                            or config.test_mode) as connection:
                    self.status = "Processing job"
                    try:
                        yield defer.maybeDeferred(self.connect)

                        #: Write startup command
                        if config.commands_before:
                            yield defer.maybeDeferred(connection.write,
                                                      config.commands_before)

                        self.status = "Working..."

                        #: For point in the path
                        for (d, cmd, args, kwargs) in self.process(model):

                            #: Check if we paused
                            if info.paused:
                                self.status = "Job paused"
                                #: Sleep until resumed, cancelled, or the
                                #: connection drops
                                while (info.paused and not info.cancelled
                                       and connection.connected):
                                    yield async_sleep(300)  # ms

                            #: Check for cancel for non interpolated jobs
                            if info.cancelled:
                                self.status = "Job cancelled"
                                info.status = 'cancelled'
                                break
                            elif not connection.connected:
                                self.status = "connection error"
                                info.status = 'error'
                                break

                            #: Invoke the command
                            #: If you want to let the device handle more complex
                            #: commands such as curves do it in process and handle
                            yield defer.maybeDeferred(cmd, *args, **kwargs)
                            total_moved += d

                            #: d should be the device must move in px
                            #: so wait a proportional amount of time for the device
                            #: to catch up. This avoids buffer errors from dumping
                            #: everything at once.

                            #: Since sending is way faster than cutting
                            #: we must delay (without blocking the UI) before
                            #: sending the next command or the device's buffer
                            #: quickly gets filled and crappy china piece cutters
                            #: get all jacked up. If the transport sends to a spooled
                            #: output (such as a printer) this can be set to 0
                            if rate > 0:
                                # log.debug("d={}, delay={} t={}".format(
                                #     d, delay, d/delay
                                # ))
                                yield async_sleep(d / rate)

                            #: TODO: Check if we need to update the ui
                            #: Set the job progress based on how far we've gone
                            if total_length > 0:
                                info.progress = int(
                                    max(
                                        0,
                                        min(100,
                                            100 * total_moved / total_length)))

                        if info.status != 'error':
                            #: We're done, send any finalization commands
                            yield defer.maybeDeferred(self.finish)

                        #: Write finalize command
                        if config.commands_after:
                            yield defer.maybeDeferred(connection.write,
                                                      config.commands_after)

                        #: Update stats
                        info.ended = datetime.now()

                        #: If not cancelled or errored
                        if info.status == 'running':
                            info.done = True
                            info.status = 'complete'
                    except Exception as e:
                        log.error(traceback.format_exc())
                        raise
                    finally:
                        if connection.connected:
                            yield defer.maybeDeferred(self.disconnect)

            #: Set the origin
            if job.feed_to_end and job.info.status == 'complete':
                self.origin = self.position

            #: If the user didn't cancel, set the origin and
            #: Process any jobs that entered the queue while this was running
            if self.queue and not job.info.cancelled:
                queue = self.queue[:]
                job = queue.pop(0)  #: Pull the first job off the queue
                log.info("Rescheduling {} from queue".format(job))
                self.queue = queue  #: Copy and reassign so the UI updates

                #: Call a minute later
                timed_call(60000, self.submit, job)
        except Exception as e:
            log.error(' device | Execution error {}'.format(
                traceback.format_exc()))
            raise

    def process(self, model):
        """  Process the path model of a job and return each command
        within the job.

        Parameters
        ----------
            model: QPainterPath
                The path to process

        Returns
        -------
            generator: A list or generator object that yields each command
             to invoke on the device and the distance moved. In the format
             (distance, cmd, args, kwargs)

        """
        config = self.config

        #: Previous point
        _p = QtCore.QPointF(self.origin[0], self.origin[1])

        #: Do a final translation since Qt's y axis is reversed
        t = QtGui.QTransform.fromScale(1, -1)
        model = model * t

        #: Determine if interpolation should be used
        skip_interpolation = (self.connection.always_spools or config.spooled
                              or not config.interpolate)

        # speed = distance/seconds
        # So distance/speed = seconds to wait
        step_size = config.step_size
        if not skip_interpolation and step_size <= 0:
            raise ValueError("Cannot have a step size <= 0!")
        try:
            # Apply device filters
            for f in self.filters:
                log.debug(" filter | Running {} on model".format(f))
                model = f.apply_to_model(model)

            # Some versions of Qt seem to require a value in toSubpathPolygons
            m = QtGui.QTransform.fromScale(1, 1)
            polypath = model.toSubpathPolygons(m)

            # Apply device filters to polypath
            for f in self.filters:
                log.debug(" filter | Running {} on polypath".format(f))
                polypath = f.apply_to_polypath(polypath)

            for path in polypath:

                #: And then each point within the path
                #: this is a polygon
                for i, p in enumerate(path):

                    #: Head state
                    # 0 move, 1 cut
                    z = 0 if i == 0 else 1

                    #: Make a subpath
                    subpath = QtGui.QPainterPath()
                    subpath.moveTo(_p)
                    subpath.lineTo(p)

                    #: Update the last point
                    _p = p

                    #: Total length
                    l = subpath.length()

                    #: If the device does not support streaming
                    #: the path interpolation is skipped entirely
                    if skip_interpolation:
                        x, y = p.x(), p.y()
                        yield (l, self.move, ([x, y, z], ), {})
                        continue

                    #: Where we are within the subpath
                    d = 0

                    #: Interpolate path in steps of dl and ensure we get
                    #: _p and p (t=0 and t=1)
                    #: This allows us to cancel mid point
                    while d <= l:
                        #: Now set d to the next point by step_size
                        #: if the end of the path is less than the step size
                        #: use the minimum of the two
                        dl = min(l - d, step_size)

                        #: Now find the point at the given step size
                        #: the first point d=0 so t=0, the last point d=l so t=1
                        t = subpath.percentAtLength(d)
                        sp = subpath.pointAtPercent(t)
                        #if d == l:
                        #    break  #: Um don't we want to send the last point??

                        #: -y because Qt's axis is from top to bottom not bottom
                        #: to top
                        x, y = sp.x(), sp.y()
                        yield (dl, self.move, ([x, y, z], ), {})

                        #: When we reached the end but instead of breaking above
                        #: with a d < l we do it here to ensure we get the last
                        #: point
                        if d == l:
                            #: We reached the end
                            break

                        #: Add step size
                        d += dl

            #: Make sure we get the endpoint
            ep = model.currentPosition()
            yield (0, self.move, ([ep.x(), ep.y(), 0], ), {})
        except Exception as e:
            log.error("device | processing error: {}".format(
                traceback.format_exc()))
            raise e

    def _observe_status(self, change):
        """ Whenever the status changes, log it """
        log.info("device | {}".format(self.status))

    def _observe_job(self, change):
        """ Save the previous jobs """
        if change['type'] == 'update':
            job = change['value']
            if job not in self.jobs:
                jobs = self.jobs[:]
                jobs.append(job)
                self.jobs = jobs
예제 #4
0
파일: http.py 프로젝트: samtux/enaml-native
class AndroidHttpRequest(HttpRequest):

    #: okhttp3.Call that can be used to cancel the request
    call = Instance(Call)

    #: okhttp3.Request
    request = Instance(Request)

    #: Handles the async callbacks
    handler = Instance(BridgedAsyncHttpCallback)

    def init_request(self):
        """ Init the native request using the okhttp3.Request.Builder """

        #: Build the request
        builder = Request.Builder()
        builder.url(self.url)

        #: Set any headers
        for k, v in self.headers.items():
            builder.addHeader(k, v)

        #: Get the body or generate from the data given
        body = self.body

        if body:
            #: Create the request body
            media_type = MediaType(__id__=MediaType.parse(self.content_type))
            request_body = RequestBody(
                __id__=RequestBody.create(media_type, body))
            #: Set the request method
            builder.method(self.method, request_body)
        elif self.method in ['get', 'delete', 'head']:
            #: Set the method
            getattr(builder, self.method)()
        else:
            raise ValueError("Cannot do a '{}' request "
                             "without a body".format(self.method))

        #: Save the okhttp request
        self.request = Request(__id__=builder.build())

    def _default_handler(self):
        handler = BridgedAsyncHttpCallback()
        handler.setAsyncHttpResponseListener(
            handler.getId(), self.streaming_callback is not None)
        handler.onStart.connect(self.on_start)
        handler.onCancel.connect(self.on_cancel)
        handler.onFailure.connect(self.on_failure)
        handler.onFinish.connect(self.on_finish)
        handler.onProgress.connect(self.on_progress)
        handler.onProgressData.connect(self.on_progress_data)
        handler.onSuccess.connect(self.on_success)
        handler.onRetry(self.on_retry)

        return handler

    def _default_body(self):
        """ If the body is not passed in by the user try to create one
        using the given data parameters. 
        """
        if not self.data:
            return ""
        if self.content_type == 'application/json':
            import json
            return json.dumps(self.data)
        elif self.content_type == 'application/x-www-form-urlencoded':
            import urllib
            return urllib.urlencode(self.data)
        else:
            raise NotImplementedError("You must manually encode the request "
                                      "body for '{}'".format(
                                          self.content_type))

    def on_start(self):
        pass

    def on_cancel(self):
        pass

    def on_retry(self, retry):
        self.retries = retry

    def on_success(self, status, headers, data):
        r = self.response
        r.code = status
        if headers:
            r.headers = headers
        if data:
            r.body = data
        r.progress = 100
        r.ok = True

    def on_failure(self, status, headers, data, error):
        r = self.response
        r.code = status
        if headers:
            r.headers = headers
        if error:
            r.reason = error
        r.error = HttpError(status, error, r)
        if data:
            r.body = data
        r.ok = False

    def on_finish(self):
        """ Called regardless of success or failure """
        r = self.response
        r.request_time = time.time() - self.start_time
        if self.callback:
            self.callback(r)

    def on_progress(self, written, total):
        r = self.response
        r.content_length = total
        if total:
            r.progress = int(100 * written / total)

    def on_progress_data(self, data):
        if self.streaming_callback:
            self.streaming_callback(data)
예제 #5
0
class TreeNode (Atom):
    """ Represents a tree node. Used by the tree editor and tree editor factory
        classes.
    """

    #---------------------------------------------------------------------------
    #  Trait definitions:
    #---------------------------------------------------------------------------

    # Name of trait containing children (if '', the node is a leaf).
    children = Str()

    # Either the name of a trait containing a label, or a constant label, if
    # the string starts with '='.
    label = Unicode()

    # The name of a trait containing a list of labels for any columns.
    column_labels = Unicode()

    # Either the name of a trait containing a tooltip, or constant tooltip, if
    # the string starts with '='.
    tooltip = Unicode()

    # Name to use for a new instance
    name = Unicode()

    # Can the object's children be renamed?
    rename = Bool( True )

    # Can the object be renamed?
    rename_me = Bool( True )

    # Can the object's children be copied?
    copy = Bool( True )

    # Can the object's children be deleted?
    delete = Bool( True )

    # Can the object be deleted (if its parent allows it)?
    delete_me = Bool( True )

    # Can children be inserted (vs. appended)?
    insert = Bool( True )

    # Should tree nodes be automatically opened (expanded)?
    auto_open = Bool( False )

    # Automatically close sibling tree nodes?
    auto_close = Bool( False )

    # List of object classes than can be added or copied
    add = List(Value())

    # List of object classes that can be moved
    move = List(Value())

    # List of object classes and/or interfaces that the node applies to
    node_for = List(Value())

    # Tuple of object classes that the node applies to
    node_for_class = Property()

    # List of object interfaces that the node applies to
    node_for_interface = Property()

    # Right-click context menu. The value can be one of:
    menu = Instance(Menu)

    # Name of leaf item icon
    icon_item = Unicode( '<item>' )

    # Name of group item icon
    icon_group = Unicode( '<group>' )

    # Name of opened group item icon
    icon_open = Unicode( '<open>' )

    # Resource path used to locate the node icon
    icon_path = Unicode

    # Selector or name for background color
    background = Value('white')

    # Selector or name for foreground color
    foreground = Value('black')
    
    _py_data = Value()

    #---------------------------------------------------------------------------
    #  Initializes the object:
    #---------------------------------------------------------------------------

    def __init__ ( self, **kwargs ):
        super( TreeNode, self ).__init__( **kwargs )
        if self.icon_path == '':
            self.icon_path = 'Icon'

    #-- Property Implementations -----------------------------------------------

    @node_for_class.getter
    def _get_node_for_class ( self ):
        return tuple([klass for klass in self.node_for])

    #-- Overridable Methods: ---------------------------------------------------

    #---------------------------------------------------------------------------
    #  Returns whether chidren of this object are allowed or not:
    #---------------------------------------------------------------------------

    def allows_children ( self, obj ):
        """ Returns whether this object can have children.
        """
        return (self.children != '')

    #---------------------------------------------------------------------------
    #  Returns whether or not the object has children:
    #---------------------------------------------------------------------------

    def has_children ( self, obj ):
        """ Returns whether the object has children.
        """
        return (len( self.get_children( obj ) ) > 0)

    #---------------------------------------------------------------------------
    #  Gets the object's children:
    #---------------------------------------------------------------------------

    def get_children ( self, obj ):
        """ Gets the object's children.
        """
        return getattr( obj, self.children )

    #---------------------------------------------------------------------------
    #  Gets the object's children identifier:
    #---------------------------------------------------------------------------

    def get_children_id ( self, obj ):
        """ Gets the object's children identifier.
        """
        return self.children

    #---------------------------------------------------------------------------
    #  Appends a child to the object's children:
    #---------------------------------------------------------------------------

    def append_child ( self, obj, child ):
        """ Appends a child to the object's children.
        """
        getattr( obj, self.children ).append( child )

    #---------------------------------------------------------------------------
    #  Inserts a child into the object's children:
    #---------------------------------------------------------------------------

    def insert_child ( self, obj, index, child ):
        """ Inserts a child into the object's children.
        """
        getattr( obj, self.children )[ index: index ] = [ child ]

    #---------------------------------------------------------------------------
    #  Confirms that a specified object can be deleted or not:
    #  Result = True:  Delete object with no further prompting
    #         = False: Do not delete object
    #         = other: Take default action (may prompt user to confirm delete)
    #---------------------------------------------------------------------------

    def confirm_delete ( self, obj ):
        """ Checks whether a specified object can be deleted.

        Returns
        -------
        * **True** if the object should be deleted with no further prompting.
        * **False** if the object should not be deleted.
        * Anything else: Caller should take its default action (which might
          include prompting the user to confirm deletion).
        """
        return None

    #---------------------------------------------------------------------------
    #  Deletes a child at a specified index from the object's children:
    #---------------------------------------------------------------------------

    def delete_child ( self, obj, index ):
        """ Deletes a child at a specified index from the object's children.
        """
        print getattr(obj, self.children)
        del getattr( obj, self.children )[ index ]
        print getattr(obj, self.children)

    #---------------------------------------------------------------------------
    #  Sets up/Tears down a listener for 'children replaced' on a specified
    #  object:
    #---------------------------------------------------------------------------

#    def when_children_replaced ( self, obj, listener, remove ):
#        """ Sets up or removes a listener for children being replaced on a
#        specified object.
#        """
##        print 'toto', obj, listener, self.children
#        if remove:
#            obj.unobserve(str(self.children), listener)
#        else:
##        print getattr(obj, self.children)
#            obj.observe(str(self.children),  listener)
##        print obj.has_observers(self.children)
#
#    #---------------------------------------------------------------------------
#    #  Sets up/Tears down a listener for 'children changed' on a specified
#    #  object:
#    #---------------------------------------------------------------------------
#
#    def when_children_changed ( self, obj, listener, remove ):
#        """ Sets up or removes a listener for children being changed on a
#        specified object.
#        """
#        if remove:
#            obj.unobserve(str(self.children), listener)
#        else:
#            obj.observe(self.children,  listener)

    #---------------------------------------------------------------------------
    #  Gets the label to display for a specified object:
    #---------------------------------------------------------------------------

    def get_label (self, obj):
        """ Gets the label to display for a specified object.
        """
        label = self.label
        if label[:1] == '=':
            return label[1:]

        label = getattr(obj, label)

        return label

    #---------------------------------------------------------------------------
    #  Sets the label for a specified object:
    #---------------------------------------------------------------------------

    def set_label ( self, obj, label ):
        """ Sets the label for a specified object.
        """
        label_name = self.label
        if label_name[:1] != '=':
            setattr( obj, label_name, label )

    #---------------------------------------------------------------------------
    #  Sets up/Tears down a listener for 'label changed' on a specified object:
    #---------------------------------------------------------------------------

    def when_label_changed ( self, obj, listener, remove ):
        """ Sets up or removes a listener for the label being changed on a
        specified object.
        """
        label = self.label
        if label[:1] != '=':
            if remove:
                obj.unobserve(label, listener)
            else:
                obj.observe(label, listener)

    def get_column_labels(self, obj):
        """ Get the labels for any columns that have been defined.
        """
        trait = self.column_labels
        return []
        labels = getattr(obj, trait)
        if not labels:
            labels = []
        formatted = []
        for formatter, label in map(None, self.column_formatters, labels):
            # If the list of column formatters is shorter than the list of
            # labels, then map(None) will extend it with Nones. Just pass the label
            # as preformatted. Similarly, explicitly using None in the list will
            # pass through the item.
            if formatter is None:
                formatted.append(label)
            else:
                formatted.append(formatter(label))
        return formatted

    def when_column_labels_change(self, obj, listener, remove):
        """ Sets up or removes a listener for the column labels being changed on
        a specified object.

        This will fire when either the list is reassigned or when it is
        modified. I.e., it listens both to the trait change event and the
        trait_items change event. Implement the listener appropriately to handle
        either case.
        """
        trait = self.column_labels
        if trait != '':
            if remove:
                obj.unobserve(trait, listener)
            else:
                obj.observe(trait, listener)

    #---------------------------------------------------------------------------
    #  Gets the tooltip to display for a specified object:
    #---------------------------------------------------------------------------

    def get_tooltip ( self, obj ):
        """ Gets the tooltip to display for a specified object.
        """
        tooltip = self.tooltip
        if tooltip == '':
            return tooltip

        if tooltip[:1] == '=':
            return tooltip[1:]

        tooltip = getattr( obj, tooltip)
        if not tooltip:
            tooltip = ''

        if self.tooltip_formatter is None:
            return tooltip

        return self.tooltip_formatter( obj, tooltip )

    #---------------------------------------------------------------------------
    #  Returns the icon for a specified object:
    #---------------------------------------------------------------------------

    def get_icon ( self, obj, is_expanded ):
        """ Returns the icon for a specified object.
        """
        if not self.allows_children( obj ):
            return self.icon_item

        if is_expanded:
            return self.icon_open

        return self.icon_group

    #---------------------------------------------------------------------------
    #  Returns the path used to locate an object's icon:
    #---------------------------------------------------------------------------

    def get_icon_path (self, obj):
        """ Returns the path used to locate an object's icon.
        """
        return self.icon_path

    #---------------------------------------------------------------------------
    #  Returns the name to use when adding a new object instance (displayed in
    #  the 'New' submenu):
    #---------------------------------------------------------------------------

    def get_name (self, obj):
        """ Returns the name to use when adding a new object instance
            (displayed in the "New" submenu).
        """
        return self.name

    #---------------------------------------------------------------------------
    #  Returns the right-click context menu for an object:
    #---------------------------------------------------------------------------

    def get_menu ( self, context):
        """ Returns the right-click context menu for an object.
        """
        if self.menu:
            self.menu.context = context
            return self.menu
        else:
            return None

    def get_background(self, obj) :
        background = self.background
        if isinstance(background, basestring) :
            background = getattr(obj, background, background)
        return background

    def get_foreground(self, obj) :
        foreground = self.foreground
        if isinstance(foreground, basestring) :
            foreground = getattr(obj, foreground, foreground)
        return foreground

    #---------------------------------------------------------------------------
    #  Returns whether or not the object's children can be renamed:
    #---------------------------------------------------------------------------

    def can_rename ( self, obj ):
        """ Returns whether the object's children can be renamed.
        """
        return self.rename

    #---------------------------------------------------------------------------
    #  Returns whether or not the object can be renamed:
    #---------------------------------------------------------------------------

    def can_rename_me ( self, obj ):
        """ Returns whether the object can be renamed.
        """
        return self.rename_me

    #---------------------------------------------------------------------------
    #  Returns whether or not the object's children can be copied:
    #---------------------------------------------------------------------------

    def can_copy ( self, obj ):
        """ Returns whether the object's children can be copied.
        """
        return self.copy

    #---------------------------------------------------------------------------
    #  Returns whether or not the object's children can be deleted:
    #---------------------------------------------------------------------------

    def can_delete ( self, obj ):
        """ Returns whether the object's children can be deleted.
        """
        return self.delete

    #---------------------------------------------------------------------------
    #  Returns whether or not the object can be deleted:
    #---------------------------------------------------------------------------

    def can_delete_me ( self, obj ):
        """ Returns whether the object can be deleted.
        """
        return self.delete_me

    #---------------------------------------------------------------------------
    #  Returns whether or not the object's children can be inserted (or just
    #  appended):
    #---------------------------------------------------------------------------

    def can_insert ( self, obj ):
        """ Returns whether the object's children can be inserted (vs.
        appended).
        """
        return self.insert

    #---------------------------------------------------------------------------
    #  Returns whether or not the object's children should be auto-opened:
    #---------------------------------------------------------------------------

    def can_auto_open ( self, obj ):
        """ Returns whether the object's children should be automatically
        opened.
        """
        return self.auto_open

    #---------------------------------------------------------------------------
    #  Returns whether or not the object's children should be auto-closed:
    #---------------------------------------------------------------------------

    def can_auto_close ( self, obj ):
        """ Returns whether the object's children should be automatically
        closed.
        """
        return self.auto_close

    #---------------------------------------------------------------------------
    #  Returns whether or not this is the node that should handle a specified
    #  object:
    #---------------------------------------------------------------------------

    def is_node_for ( self, obj ):
        """ Returns whether this is the node that handles a specified object.
        """
        return (isinstance(obj, self.node_for_class))

    #---------------------------------------------------------------------------
    #  Returns whether a given 'add_object' can be added to an object:
    #---------------------------------------------------------------------------

    def can_add ( self, obj, add_object ):
        """ Returns whether a given object is droppable on the node.
        """
        klass = self._class_for( add_object )
        if self.is_addable( klass ):
            return True

        for item in self.move:
            if type( item ) in (List, ContainerList, Dict):
                item = item[0]
            if issubclass( klass, item ):
                return True

        return False

    #---------------------------------------------------------------------------
    #  Returns the list of classes that can be added to the object:
    #---------------------------------------------------------------------------

    def get_add ( self, obj ):
        """ Returns the list of classes that can be added to the object.
        """
        return self.add

    #---------------------------------------------------------------------------
    #  Returns the 'draggable' version of a specified object:
    #---------------------------------------------------------------------------

    def get_drag_object ( self, obj ):
        """ Returns a draggable version of a specified object.
        """
        return obj

    #---------------------------------------------------------------------------
    #  Returns a droppable version of a specified object:
    #---------------------------------------------------------------------------

    def drop_object ( self, obj, dropped_object ):
        """ Returns a droppable version of a specified object.
        """
        klass = self._class_for( dropped_object )
        if self.is_addable( klass ):
            return dropped_object

        for item in self.move:
            if type( item ) in (List, ContainerList, Dict):
                if issubclass( klass, item[0] ):
                    return item[1]( obj, dropped_object )
            elif issubclass( klass, item ):
                return dropped_object

        return dropped_object

    #---------------------------------------------------------------------------
    #  Handles an object being selected:
    #---------------------------------------------------------------------------

    def select ( self, obj ):
        """ Handles an object being selected.
        """
#        if self.on_select is not None:
#            self.on_select( obj )
#            return None

        return True

#    #---------------------------------------------------------------------------
#    #  Handles an object being clicked:
#    #---------------------------------------------------------------------------
#
#    def click ( self, obj ):
#        """ Handles an object being clicked.
#        """
#        if self.on_click is not None:
#            self.on_click( obj )
#            return None
#
#        return True
#
#    #---------------------------------------------------------------------------
#    #  Handles an object being double-clicked:
#    #---------------------------------------------------------------------------
#
#    def dclick ( self, obj):
#        """ Handles an object being double-clicked.
#        """
#        if self.on_dclick is not None:
#            self.on_dclick( obj)
#            return None
#
#        return True
#
#    #---------------------------------------------------------------------------
#    #  Handles an object being activated:
#    #---------------------------------------------------------------------------
#
#    def activated ( self, object ):
#        """ Handles an object being activated.
#        """
#        if self.on_activated is not None:
#            self.on_activated( object )
#            return None
#
#        return True

    #---------------------------------------------------------------------------
    #  Returns whether or not a specified object class can be added to the node:
    #---------------------------------------------------------------------------

    def is_addable ( self, klass ):
        """ Returns whether a specified object class can be added to the node.
        """
        for item in self.add:
            if type( item ) in (List, ContainerList, Dict):
                item = item[0]

            if issubclass( klass, item ):
                return True

        return False

    #---------------------------------------------------------------------------
    #  Returns the class of an object:
    #---------------------------------------------------------------------------

    def _class_for ( self, obj ):
        """ Returns the class of an object.
        """
        if isinstance( obj, type ):
            return obj

        return obj.__class__
예제 #6
0
파일: models.py 프로젝트: yangbiaocn/inkcut
class JobInfo(Model):
    """ Job metadata """
    #: Controls
    done = Bool()
    cancelled = Bool()
    paused = Bool()

    #: Flags
    status = Enum('staged', 'waiting', 'running', 'error', 'approved',
                  'cancelled', 'complete').tag(config=True)

    #: Stats
    created = Instance(datetime).tag(config=True)
    started = Instance(datetime).tag(config=True)
    ended = Instance(datetime).tag(config=True)
    progress = Range(0, 100, 0).tag(config=True)
    data = Str().tag(config=True)
    count = Int().tag(config=True)

    #: Device speed in px/s
    speed = Float(strict=False).tag(config=True)
    #: Length in px
    length = Float(strict=False).tag(config=True)

    #: Estimates based on length and speed
    duration = Instance(timedelta, ()).tag(config=True)

    #: Units
    units = Enum('in', 'cm', 'm', 'ft').tag(config=True)

    #: Callback to open the approval dialog
    auto_approve = Bool().tag(config=True)
    request_approval = Callable()

    def __init__(self, *args, **kwargs):
        super(JobInfo, self).__init__(*args, **kwargs)
        self.created = self._default_created()

    def _default_created(self):
        return datetime.now()

    def _default_request_approval(self):
        """ Request approval using the current job """
        from inkcut.core.workbench import InkcutWorkbench
        workbench = InkcutWorkbench.instance()
        plugin = workbench.get_plugin("inkcut.job")
        return lambda: plugin.request_approval(plugin.job)

    def reset(self):
        """ Reset to initial states"""
        #: TODO: This is a stupid design
        self.progress = 0
        self.paused = False
        self.cancelled = False
        self.done = False
        self.status = 'staged'

    def _observe_done(self, change):
        if change['type'] == 'update':
            #: Increment count every time it's completed
            if self.done:
                self.count += 1

    @observe('length', 'speed')
    def _update_duration(self, change):
        if not self.length or not self.speed:
            self.duration = timedelta()
            return
        dt = self.length / self.speed
        self.duration = timedelta(seconds=dt)
예제 #7
0
파일: plugin.py 프로젝트: vmario89/inkcut
class ParallelTransport(RawFdTransport):
    """ This is just a wrapper for the RawFdTransport

    """
    #: Default config
    config = Instance(ParallelConfig, ()).tag(config=True)
예제 #8
0
class Plot1D(Atom):
    """plot attribute on dataobject"""
    updated = Event(kind=bool)

    x = Property()
    y = Property()

    _x_dict = Instance(PlotFunctionsDict)  #todo make this public?
    _y_dict = Instance(PlotFunctionsDict)

    parent = ForwardInstance(lambda: ap.data.XYDataObject)
    line = Instance(Line2D)

    norm = Bool(False)
    zero = Bool(False)

    def __init__(self, parent, *args, **kwargs):
        super(Plot1D, self).__init__(*args, **kwargs)
        self.parent = parent

        self._x_dict = PlotFunctionsDict({})
        self._y_dict = PlotFunctionsDict({})

    def _get_x(self):
        x = np.copy(self.parent.x)
        result = self._apply_functions(self._x_dict, x)
        return result

    def _get_y(self):
        y = np.copy(self.parent.y)
        result = self._apply_functions(self._y_dict, y)
        return result

    @observe('norm')
    def _norm_changed(self, change):
        if change['value']:
            #todo needs to be diffent for normalizing the fit, should be the same factor. but how!?!? -> same functions on fit!
            self._y_dict['_norm'] = (lambda y: y / y.max(), [], {})
        else:
            del self._y_dict['_norm']

        self._update_y('')

    @observe('zero')
    def _zero_changed(self, change):
        if change['value']:
            self._y_dict['_zero'] = (lambda x: x - x.min(), [], {})
            self._y_dict.move_to_end(
                '_zero', last=False)  # Move zeroing to be the first operation
        else:
            del self._y_dict['_zero']

        self._update_y('')

    @staticmethod
    def _apply_functions(functions, result):
        if functions:
            for f_key, content in functions.items():
                func, args, kwargs = content  # todo make args kwargs optional
                result = func(result, *args, **kwargs)
        return result

    @observe('parent.x_updated')
    def _update_x(self):
        print('update in in plot1d')
        self.line.set_xdata(self.x)

    @observe(['parent.y_updated', 'parent.y'])
    def _update_y(self, new):
        print('update y in plot1d')
        print(self.y)
        if self.line:
            self.line.set_ydata(self.y)

        self.updated = True
예제 #9
0
        v = member

    mode = (DefaultValue.MemberMethod_Object if isinstance(
        member, ForwardSubclass) else DefaultValue.Static)
    assert StaticTest.v.default_value_mode[0] == mode
    assert StaticTest().v == expected
    assert StaticTest.v.default_value_mode[0] == DefaultValue.Static


@pytest.mark.parametrize(
    "member, expect_error",
    [
        (Typed(int), False),
        (Typed(int, (), optional=False), False),
        (Typed(int, factory=lambda: 1, optional=False), False),
        (Instance(int), False),
        (Instance(int, (), optional=False), False),
        (Instance(int, factory=lambda: 1, optional=False), False),
        (Instance(int, optional=False), True),
        (ForwardTyped(lambda: int), False),
        (ForwardTyped(lambda: int, (), optional=False), False),
        (ForwardTyped(lambda: int, factory=lambda: 1, optional=False), False),
        (ForwardTyped(lambda: int, optional=False), True),
        (ForwardInstance(lambda: int), False),
        (ForwardInstance(lambda: int, (), optional=False), False),
        (ForwardInstance(lambda: int, factory=lambda: 1,
                         optional=False), False),
        (ForwardInstance(lambda: int, optional=False), True),
    ],
)
def test_non_optional_handler(member, expect_error):
예제 #10
0
파일: app.py 프로젝트: saluzi/declaracad
class Application(QtApplication):
    """ Add asyncio support . Seems like a complete hack compared to twisted
    but whatever.

    """

    loop = Instance(QEventLoop)
    queue = Instance(Queue, ())
    running = Bool()

    def __init__(self):
        super().__init__()
        self.loop = QEventLoop(self._qapp)
        asyncio.set_event_loop(self.loop)
        for name in ('asyncqt._unix._Selector',
                     'asyncqt._QEventLoop',
                     'asyncqt._SimpleTimer'):
            log = logging.getLogger(name)
            log.setLevel(logging.WARN)

    def start(self):
        """ Run using the event loop

        """
        log.info("Application starting")
        self.running = True
        with self.loop:
            try:
                self.loop.run_until_complete(self.main())
            except RuntimeError as e:
                if 'loop stopped' not in str(e):
                    raise

    async def main(self):
        """ Run any async deferred calls in the main ui loop.

        """
        while self.running:
            try:
                task = self.queue.get(block=False)
                await task
            except Empty:
                self.process_events()
                await asyncio.sleep(0.1)
            except Exception as e:
                if 'cannot enter context' in str(e):
                    # HACK: Something is jacked up
                    await asyncio.sleep(0.1)
                    continue
                log.exception(e)

    def process_events(self):
        """ Let the the app process events during long-running cpu intensive
        tasks.

        """
        self._qapp.processEvents()

    def deferred_call(self, callback, *args, **kwargs):
        """ Invoke a callable on the next cycle of the main event loop
        thread.

        Parameters
        ----------
        callback : callable
            The callable object to execute at some point in the future.

        args, kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        if asyncio.iscoroutinefunction(callback) or kwargs.pop('async_', None):
            task = asyncio.create_task(callback(*args, **kwargs))
            return self.add_task(task)
        return super().deferred_call(callback, *args, **kwargs)

    def timed_call(self, ms, callback, *args, **kwargs):
        """ Invoke a callable on the main event loop thread at a
        specified time in the future.

        Parameters
        ----------
        ms : int
            The time to delay, in milliseconds, before executing the
            callable.

        callback : callable
            The callable object to execute at some point in the future.

        args, kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        if asyncio.iscoroutinefunction(callback) or kwargs.pop('async_', None):
            task = asyncio.create_task(callback(*args, **kwargs))
            return super().timed_call(ms, self.add_task, task)
        return super().timed_call(ms, callback, *args, **kwargs)

    def add_task(self, task):
        """ Put a task into the queue

        """
        self.queue.put(task)
예제 #11
0
class AndroidView(AndroidToolkitObject, ProxyView):
    """ An Android implementation of an Enaml ProxyView.

    """
    #: A reference to the widget created by the proxy.
    widget = Typed(View)

    #: Display metrics density
    dp = Float(1.0)

    #: Layout type
    layout_param_type = Subclass(LayoutParams)

    #: Layout params
    layout_params = Instance(LayoutParams)

    #: Default layout params
    default_layout = Dict(default={
        'width': 'wrap_content',
        'height': 'wrap_content'
    })

    def _default_dp(self):
        return self.get_context().dp

    # -------------------------------------------------------------------------
    # Initialization API
    # -------------------------------------------------------------------------
    def create_widget(self):
        """ Create the underlying label widget.

        """
        self.widget = View(self.get_context())

    def init_widget(self):
        """ Initialize the underlying widget.
        
        This reads all items declared in the enamldef block for this node 
        and sets only the values that have been specified. All other values 
        will be left as default. Doing it this way makes atom to only create 
        the properties that need to be overridden from defaults thus greatly 
        reducing the number of initialization checks, saving time and memory.
        
        If you don't want this to happen override `get_declared_keys` 
        to return an empty list. 

        """
        super(AndroidView, self).init_widget()

        # Initialize the widget by updating only the members that
        # have read expressions declared. This saves a lot of time and
        # simplifies widget initialization code
        for k, v in self.get_declared_items():
            handler = getattr(self, 'set_'+k, None)
            if handler:
                handler(v)

    def get_declared_items(self):
        """ Get the members that were set in the enamldef block for this
        Declaration. Layout keys are grouped together until the end so as
        to avoid triggering multiple updates.
        
        Returns
        -------
        result: List of (k,v) pairs that were defined for this widget in enaml
            List of keys and values

        """
        d = self.declaration
        engine = d._d_engine
        if engine:
            layout = {}
            for k, h in engine._handlers.items():
                # Handlers with read operations
                if not h.read_pair:
                    continue
                v = getattr(d, k)
                if k in LAYOUT_KEYS:
                    layout[k] = v
                    continue
                yield (k, v)

            if layout:
                yield ('layout', layout)

    # -------------------------------------------------------------------------
    # OnClickListener API
    # -------------------------------------------------------------------------
    def on_click(self, view):
        """ Trigger the click

        """
        d = self.declaration
        d.clicked()

    # -------------------------------------------------------------------------
    # OnKeyListener API
    # -------------------------------------------------------------------------
    def on_key(self, view, key, event):
        """ Trigger the key event

        Parameters
        ----------
        view: int
            The ID of the view that sent this event
        key: int
            The code of the key that was pressed
        data: bytes
            The msgpack encoded key event

        """
        d = self.declaration
        r = {'key': key, 'result': False}
        d.key_event(r)
        return r['result']

    # -------------------------------------------------------------------------
    # OnTouchListener API
    # -------------------------------------------------------------------------
    def on_touch(self, view, event):
        """ Trigger the touch event

        Parameters
        ----------
        view: int
            The ID of the view that sent this event
        data: bytes
            The msgpack encoded key event

        """
        d = self.declaration
        r = {'event': event, 'result': False}
        d.touch_event(r)
        return r['result']

    # -------------------------------------------------------------------------
    # ProxyView API
    # -------------------------------------------------------------------------
    def set_touch_events(self, enabled):
        w = self.widget
        if enabled:
            w.setOnTouchListener(w.getId())
            w.onTouch.connect(self.on_touch)
        else:
            w.onTouch.disconnect(self.on_touch)

    def set_key_events(self, enabled):
        w = self.widget
        if enabled:
            w.setOnKeyListener(w.getId())
            w.onKey.connect(self.on_key)
        else:
            w.onKey.disconnect(self.on_key)

    def set_clickable(self, clickable):
        w = self.widget
        if clickable:
            w.setOnClickListener(w.getId())
            w.onClick.connect(self.on_click)
        else:
            w.onClick.disconnect(self.on_click)
        w.setClickable(clickable)

    def set_enabled(self, enabled):
        """ Set the enabled state of the widget.

        """
        self.widget.setEnabled(enabled)

    def set_visible(self, visible):
        """ Set the visibility of the widget.

        """
        v = View.VISIBILITY_VISIBLE if visible else View.VISIBILITY_GONE
        self.widget.setVisibility(v)

    # -------------------------------------------------------------------------
    # Style updates
    # -------------------------------------------------------------------------
    def set_background_color(self, color):
        """ Set the background color of the widget.
        
        """
        self.widget.setBackgroundColor(color)

    def set_alpha(self, alpha):
        """ Sets the alpha or opacity of the widget. """
        self.widget.setAlpha(alpha)

    # -------------------------------------------------------------------------
    # Layout updates
    # -------------------------------------------------------------------------
    def set_layout(self, layout):
        """ Sets the LayoutParams of this widget. 
           
        Since the available properties that may be set for the layout params 
        depends on the parent, actual creation of the params is delegated to 
        the parent
        
        Parameters
        ----------
        layout: Dict
            A dict of layout parameters the parent should used to layout this
            child.  The widget defaults are updated with user passed values. 
        
        """
        # Update the layout with the widget defaults
        update = self.layout_params is not None
        params = self.default_layout.copy()
        params.update(layout)

        # Create the layout params
        parent = self.parent()

        if not isinstance(parent, AndroidView):
            # Root node
            parent = self
            update = True

        parent.apply_layout(self, params)
        if update:
            self.widget.setLayoutParams(self.layout_params)

    def update_layout(self, **params):
        """ Updates the LayoutParams of this widget. 
           
        This delegates to the parent and expects the parent to update the
        existing layout without recreating it.
        
        Parameters
        ----------
        params: Dict
            A dict of layout parameters the parent should used to layout this
            child.  The widget defaults are updated with user passed values. 
        
        """
        self.parent().apply_layout(self, params)

    def create_layout_params(self, child, layout):
        """ Create the LayoutParams for a child with it's requested
        layout parameters. Subclasses should override this as needed
        to handle layout specific needs.
        
        Parameters
        ----------
        child: AndroidView
            A view to create layout params for.
        layout: Dict
            A dict of layout parameters to use to create the layout.
             
        Returns
        -------
        layout_params: LayoutParams
            A LayoutParams bridge object with the requested layout options.
        
        """
        dp = self.dp
        w, h = (coerce_size(layout.get('width', 'wrap_content')),
                coerce_size(layout.get('height', 'wrap_content')))
        w = w if w < 0 else int(w * dp)
        h = h if h < 0 else int(h * dp)
        layout_params = self.layout_param_type(w, h)

        if layout.get('margin'):
            l, t, r, b = layout['margin']
            layout_params.setMargins(int(l*dp), int(t*dp),
                                     int(b*dp), int(r*dp))
        return layout_params

    def apply_layout(self, child, layout):
        """ Apply a layout to a child. This sets the layout_params
        of the child which is later used during the `init_layout` pass.
        Subclasses should override this as needed to handle layout specific
        needs of the ViewGroup.
        
        Parameters
        ----------
        child: AndroidView
            A view to create layout params for.
        layout: Dict
            A dict of layout parameters to use to create the layout.
        
        """
        layout_params = child.layout_params
        if not layout_params:
            layout_params = self.create_layout_params(child, layout)
        w = child.widget
        if w:
            dp = self.dp
            # padding
            if 'padding' in layout:
                l, t, r, b = layout['padding']
                w.setPadding(int(l*dp), int(t*dp),
                             int(b*dp), int(r*dp))

            # left, top, right, bottom
            if 'left' in layout:
                w.setLeft(int(layout['left']*dp))
            if 'top' in layout:
                w.setTop(int(layout['top']*dp))
            if 'right' in layout:
                w.setRight(int(layout['right']*dp))
            if 'bottom' in layout:
                w.setBottom(int(layout['bottom']*dp))

            # x, y, z
            if 'x' in layout:
                w.setX(layout['x']*dp)
            if 'y' in layout:
                w.setY(layout['y']*dp)
            if 'z' in layout:
                w.setZ(layout['z']*dp)

            # set min width and height
            # maximum is not supported by AndroidViews (without flexbox)
            if 'min_height' in layout:
                w.setMinimumHeight(int(layout['min_height']*dp))
            if 'min_width' in layout:
                w.setMinimumWidth(int(layout['min_width']*dp))

        child.layout_params = layout_params

    def set_width(self, width):
        self.update_layout(width=width)

    def set_height(self, height):
        self.update_layout(height=height)

    def set_padding(self, padding):
        self.update_layout(padding=padding)

    def set_margin(self, margin):
        self.update_layout(margin=margin)

    def set_x(self, x):
        self.update_layout(x=x)

    def set_y(self, y):
        self.update_layout(y=y)

    def set_z(self, z):
        self.update_layout(z=z)

    def set_top(self, top):
        self.update_layout(top=top)

    def set_left(self, left):
        self.update_layout(left=left)

    def set_right(self, right):
        self.update_layout(right=right)

    def set_bottom(self, bottom):
        self.update_layout(bottom=bottom)

    def set_gravity(self, gravity):
        self.update_layout(gravity=gravity)

    def set_min_height(self, min_height):
        self.update_layout(min_height=min_height)

    def set_max_height(self, max_height):
        self.update_layout(max_height=max_height)

    def set_min_width(self, min_width):
        self.update_layout(min_width=min_width)

    def set_max_width(self, max_width):
        self.update_layout(max_width=max_width)

    def set_flex_grow(self, flex_grow):
        self.update_layout(flex_grow=flex_grow)

    def set_flex_basis(self, flex_basis):
        self.update_layout(flex_basis=flex_basis)

    def set_flex_shrink(self, flex_shrink):
        self.update_layout(flex_shrink=flex_shrink)

    def set_align_self(self, align_self):
        self.update_layout(align_self=align_self)
예제 #12
0
class Future(Atom, object):
    """Placeholder for an asynchronous result.

    A ``Future`` encapsulates the result of an asynchronous
    operation.  In synchronous applications ``Futures`` are used
    to wait for the result from a thread or process pool; in
    Tornado they are normally used with `.IOLoop.add_future` or by
    yielding them in a `.gen.coroutine`.

    `tornado.concurrent.Future` is similar to
    `concurrent.futures.Future`, but not thread-safe (and therefore
    faster for use with single-threaded event loops).

    In addition to ``exception`` and ``set_exception``, methods ``exc_info``
    and ``set_exc_info`` are supported to capture tracebacks in Python 2.
    The traceback is automatically available in Python 3, but in the
    Python 2 futures backport this information is discarded.
    This functionality was previously available in a separate class
    ``TracebackFuture``, which is now a deprecated alias for this class.

    .. versionchanged:: 4.0
       `tornado.concurrent.Future` is always a thread-unsafe ``Future``
       with support for the ``exc_info`` methods.  Previously it would
       be an alias for the thread-safe `concurrent.futures.Future`
       if that package was available and fall back to the thread-unsafe
       implementation if it was not.

    .. versionchanged:: 4.1
       If a `.Future` contains an error but that error is never observed
       (by calling ``result()``, ``exception()``, or ``exc_info()``),
       a stack trace will be logged when the `.Future` is garbage collected.
       This normally indicates an error in the application, but in cases
       where it results in undesired logging it may be necessary to
       suppress the logging by ensuring that the exception is observed:
       ``f.add_done_callback(lambda f: f.exception())``.
    """
    __slots__ = ('__weakref__', )
    _done = Bool()
    _result = Value()
    _exc_info = Value()
    _log_traceback = Bool()
    _tb_logger = Value()
    _callbacks = Instance(list, ())
    __id__ = Int()
    then = Callable()
    catch = Callable()

    # Implement the Python 3.5 Awaitable protocol if possible
    # (we can't use return and yield together until py33).
    if sys.version_info >= (3, 3):
        exec(
            textwrap.dedent("""
        def __await__(self):
            return (yield self)
        """))
    else:
        # Py2-compatible version for use with cython.
        def __await__(self):
            result = yield self
            # StopIteration doesn't take args before py33,
            # but Cython recognizes the args tuple.
            e = StopIteration()
            e.args = (result, )
            raise e

    def cancel(self):
        """Cancel the operation, if possible.

        Tornado ``Futures`` do not support cancellation, so this method always
        returns False.
        """
        return False

    def cancelled(self):
        """Returns True if the operation has been cancelled.

        Tornado ``Futures`` do not support cancellation, so this method
        always returns False.
        """
        return False

    def running(self):
        """Returns True if this operation is currently running."""
        return not self._done

    def done(self):
        """Returns True if the future has finished running."""
        return self._done

    def _clear_tb_log(self):
        self._log_traceback = False
        if self._tb_logger is not None:
            self._tb_logger.clear()
            self._tb_logger = None

    def result(self, timeout=None):
        """If the operation succeeded, return its result.  If it failed,
        re-raise its exception.

        This method takes a ``timeout`` argument for compatibility with
        `concurrent.futures.Future` but it is an error to call it
        before the `Future` is done, so the ``timeout`` is never used.
        """
        self._clear_tb_log()
        if self._result is not None:
            return self._result
        if self._exc_info is not None:
            try:
                raise_exc_info(self._exc_info)
            finally:
                self = None
        self._check_done()
        return self._result

    def exception(self, timeout=None):
        """If the operation raised an exception, return the `Exception`
        object.  Otherwise returns None.

        This method takes a ``timeout`` argument for compatibility with
        `concurrent.futures.Future` but it is an error to call it
        before the `Future` is done, so the ``timeout`` is never used.
        """
        self._clear_tb_log()
        if self._exc_info is not None:
            return self._exc_info[1]
        else:
            self._check_done()
            return None

    def add_done_callback(self, fn):
        """Attaches the given callback to the `Future`.

        It will be invoked with the `Future` as its argument when the Future
        has finished running and its result is available.  In Tornado
        consider using `.IOLoop.add_future` instead of calling
        `add_done_callback` directly.
        """
        if self._done:
            fn(self)
        else:
            self._callbacks.append(fn)

    def set_result(self, result):
        """Sets the result of a ``Future``.

        It is undefined to call any of the ``set`` methods more than once
        on the same object.
        """
        self._result = result
        self._set_done()

    def set_exception(self, exception):
        """Sets the exception of a ``Future.``"""
        self.set_exc_info((exception.__class__, exception,
                           getattr(exception, '__traceback__', None)))

    def exc_info(self):
        """Returns a tuple in the same format as `sys.exc_info` or None.

        .. versionadded:: 4.0
        """
        self._clear_tb_log()
        return self._exc_info

    def set_exc_info(self, exc_info):
        """Sets the exception information of a ``Future.``

        Preserves tracebacks on Python 2.

        .. versionadded:: 4.0
        """
        self._exc_info = exc_info
        self._log_traceback = True
        if not _GC_CYCLE_FINALIZERS:
            self._tb_logger = _TracebackLogger(exc_info)

        try:
            self._set_done()
        finally:
            # Activate the logger after all callbacks have had a
            # chance to call result() or exception().
            if self._log_traceback and self._tb_logger is not None:
                self._tb_logger.activate()
        self._exc_info = exc_info

    def _check_done(self):
        if not self._done:
            raise Exception(
                "DummyFuture does not support blocking for results")

    def _set_done(self):
        self._done = True
        for cb in self._callbacks:
            try:
                cb(self)
            except Exception:
                app_log.exception('Exception in callback %r for %r', cb, self)
        self._callbacks = None

    # On Python 3.3 or older, objects with a destructor part of a reference
    # cycle are never destroyed. It's no longer the case on Python 3.4 thanks to
    # the PEP 442.
    if _GC_CYCLE_FINALIZERS:

        def __del__(self, is_finalizing=is_finalizing):
            if is_finalizing() or not self._log_traceback:
                # set_exception() was not called, or result() or exception()
                # has consumed the exception
                return

            tb = traceback.format_exception(*self._exc_info)

            app_log.error('Future %r exception was never retrieved: %s', self,
                          ''.join(tb).rstrip())
예제 #13
0
class Job(Model):
    """ Create a plot depending on the properties set. Any property that is a
    traitlet will cause an update when the value is changed.

    """
    #: Material this job will be run on
    material = Instance(Material, ()).tag(config=True)

    #: Path to svg document this job parses
    document = Unicode().tag(config=True)

    #: Nodes to restrict
    document_kwargs = Dict().tag(config=True)

    #: Meta info a the job
    info = Instance(JobInfo, ()).tag(config=True)

    # Job properties used for generating the plot
    size = ContainerList(Float(), default=[1, 1])
    scale = ContainerList(Float(), default=[1, 1]).tag(config=True)
    auto_scale = Bool(False).tag(
        config=True, help="automatically scale if it's too big for the area")
    lock_scale = Bool(True).tag(
        config=True, help="automatically scale if it's too big for the area")

    mirror = ContainerList(Bool(), default=[False, False]).tag(config=True)
    align_center = ContainerList(Bool(), default=[False,
                                                  False]).tag(config=True)

    rotation = Float(0).tag(config=True)
    auto_rotate = Bool(False).tag(
        config=True, help="automatically rotate if it saves space")

    copies = Int(1).tag(config=True)
    auto_copies = Bool(False).tag(config=True, help="always use a full stack")
    copy_spacing = ContainerList(Float(), default=[10, 10]).tag(config=True)
    copy_weedline = Bool(False).tag(config=True)
    copy_weedline_padding = ContainerList(Float(),
                                          default=[10, 10, 10,
                                                   10]).tag(config=True)

    plot_weedline = Bool(False).tag(config=True)
    plot_weedline_padding = ContainerList(Float(),
                                          default=[10, 10, 10,
                                                   10]).tag(config=True)

    order = Enum(*sorted(ordering.REGISTRY.keys())).tag(config=True)

    def _default_order(self):
        return 'Normal'

    feed_to_end = Bool(False).tag(config=True)
    feed_after = Float(0).tag(config=True)

    stack_size = ContainerList(Int(), default=[0, 0])

    path = Instance(QtGui.QPainterPath)  # Original path
    model = Instance(QtGui.QPainterPath)  # Copy using job properties

    _blocked = Bool(False)  # block change events
    _desired_copies = Int(1)  # required for auto copies

    def __setstate__(self, *args, **kwargs):
        """ Ensure that when restoring from disk the material and info
        are not set to None. Ideally these would be defined as Typed but
        the material may be made extendable at some point.
        """
        super(Job, self).__setstate__(*args, **kwargs)
        if not self.info:
            self.info = JobInfo()
        if not self.material:
            self.material = Material()

    def _observe_document(self, change):
        """ Read the document from stdin """
        if change['type'] == 'update' and self.document == '-':
            #: Only load from stdin when explicitly changed to it (when doing
            #: open from the cli) otherwise when restoring state this hangs
            #: startup
            self.path = QtSvgDoc(sys.stdin, **self.document_kwargs)
        elif self.document and os.path.exists(self.document):
            self.path = QtSvgDoc(self.document, **self.document_kwargs)

    def _create_copy(self):
        """ Creates a copy of the original graphic applying the given
        transforms

        """
        bbox = self.path.boundingRect()

        # Create the base copy
        t = QtGui.QTransform()

        t.scale(
            self.scale[0] * (self.mirror[0] and -1 or 1),
            self.scale[1] * (self.mirror[1] and -1 or 1),
        )

        # Rotate about center
        if self.rotation != 0:
            c = bbox.center()
            t.translate(-c.x(), -c.y())
            t.rotate(self.rotation)
            t.translate(c.x(), c.y())

        # Apply transform
        path = self.path * t

        # Add weedline to copy
        if self.copy_weedline:
            self._add_weedline(path, self.copy_weedline_padding)

        # Apply ordering to path
        # this delegates to objects in the ordering module
        # TODO: Should this be done via plugins?
        OrderingHandler = ordering.REGISTRY.get(self.order)
        if OrderingHandler:
            path = OrderingHandler().order(self, path)

        # If it's too big we have to scale it
        w, h = path.boundingRect().width(), path.boundingRect().height()
        available_area = self.material.available_area

        #: This screws stuff up!
        if w > available_area.width() or h > available_area.height():

            # If it's too big an auto scale is enabled, resize it to fit
            if not self.auto_scale:
                raise JobError("Image is too large to fit on the material")
            sx, sy = 1, 1
            if w > available_area.width():
                sx = available_area.width() / w
            if h > available_area.height():
                sy = available_area.height() / h
            s = min(sx, sy)  # Fit to the smaller of the two
            path = self.path * QtGui.QTransform.fromScale(s, s)

        # Move to bottom left
        p = path.boundingRect().bottomRight()

        path = path * QtGui.QTransform.fromTranslate(-p.x(), -p.y())

        return path

    @contextmanager
    def events_suppressed(self):
        """ Block change events to prevent feedback loops

        """
        self._blocked = True
        try:
            yield
        finally:
            self._blocked = False

    @observe('path', 'scale', 'auto_scale', 'lock_scale', 'mirror',
             'align_center', 'rotation', 'auto_rotate', 'copies', 'order',
             'copy_spacing', 'copy_weedline', 'copy_weedline_padding',
             'plot_weedline', 'plot_weedline_padding', 'feed_to_end',
             'feed_after', 'material', 'material.size', 'material.padding',
             'auto_copies')
    def _job_changed(self, change):
        """ Recreate an instance of of the plot using the current settings

        """
        if self._blocked:
            return

        if change['name'] == 'copies':
            self._desired_copies = self.copies

        #try:
        model = QtGui.QPainterPath()

        if not self.path:
            return

        path = self._create_copy()

        # Update size
        bbox = path.boundingRect()
        self.size = [bbox.width(), bbox.height()]

        # Create copies
        c = 0
        points = self._copy_positions_iter(path)

        if self.auto_copies:
            self.stack_size = self._compute_stack_sizes(path)
            if self.stack_size[0]:
                copies_left = self.copies % self.stack_size[0]
                if copies_left:  # not a full stack
                    with self.events_suppressed():
                        self.copies = self._desired_copies
                        self.add_stack()

        while c < self.copies:
            x, y = next(points)
            model.addPath(path * QtGui.QTransform.fromTranslate(x, -y))
            c += 1

        # Create weedline
        if self.plot_weedline:
            self._add_weedline(model, self.plot_weedline_padding)

        # Move to 0,0
        bbox = model.boundingRect()
        p = bbox.bottomLeft()
        tx, ty = -p.x(), -p.y()

        # Center or set to padding
        tx += ((self.material.width() - bbox.width()) /
               2.0 if self.align_center[0] else self.material.padding_left)
        ty += (-(self.material.height() - bbox.height()) /
               2.0 if self.align_center[1] else -self.material.padding_bottom)

        t = QtGui.QTransform.fromTranslate(tx, ty)

        model = model * t

        end_point = (QtCore.QPointF(
            0, -self.feed_after + model.boundingRect().top())
                     if self.feed_to_end else QtCore.QPointF(0, 0))
        model.moveTo(end_point)

        # Set new model
        self.model = model  #.simplified()

        # Set device model
        #self.device_model = self.device.driver.prepare_job(self)
        #except:
        #    # Undo the change
        #    if 'oldvalue' in change:
        #        setattr(change['object'],change['name'],change['oldvalue'])
        #    raise
        #if not self.check_bounds(self.boundingRect(),self.available_area):
        #    raise JobError(
        #       "Plot outside of plotting area, increase the area"
        #       "or decrease the scale or decrease number of copies!")

    def _check_bounds(self, plot, area):
        """ Checks that the width and height of plot are less than the width
        and height of area

        """
        return plot.width() > area.width() or plot.height() > area.height()

    def _copy_positions_iter(self, path, axis=0):
        """ Generator that creates positions of points

        """
        other_axis = axis + 1 % 2
        p = [0, 0]

        bbox = path.boundingRect()
        d = (bbox.width(), bbox.height())
        pad = self.copy_spacing
        stack_size = self._compute_stack_sizes(path)

        while True:
            p[axis] = 0
            yield p  # Beginning of each row

            for i in range(stack_size[axis] - 1):
                p[axis] += d[axis] + pad[axis]
                yield p

            p[other_axis] += d[other_axis] + pad[other_axis]

    def _compute_stack_sizes(self, path):
        # Usable area
        material = self.material
        a = [material.width(), material.height()]
        a[0] -= material.padding[Padding.LEFT] + material.padding[
            Padding.RIGHT]
        a[1] -= material.padding[Padding.TOP] + material.padding[
            Padding.BOTTOM]

        # Clone includes weedline but not spacing
        bbox = path.boundingRect()
        size = [bbox.width(), bbox.height()]

        stack_size = [0, 0]
        p = [0, 0]
        for i in range(2):
            # Compute stack
            while (p[i] + size[i]) < a[i]:  # while another one fits
                stack_size[i] += 1
                p[i] += size[i] + self.copy_spacing[i]  # Add only to end

        self.stack_size = stack_size
        return stack_size

    def _add_weedline(self, path, padding):
        """ Adds a weedline to the path
        by creating a box around the path with the given padding

        """
        bbox = path.boundingRect()
        w, h = bbox.width(), bbox.height()

        tl = bbox.topLeft()
        x = tl.x() - padding[Padding.LEFT]
        y = tl.y() - padding[Padding.TOP]

        w += padding[Padding.LEFT] + padding[Padding.RIGHT]
        h += padding[Padding.TOP] + padding[Padding.BOTTOM]

        path.addRect(x, y, w, h)
        return path

    @property
    def state(self):
        pass

    @property
    def move_path(self):
        """ Returns the path the head moves when not cutting

        """
        # Compute the negative
        path = QtGui.QPainterPath()
        for i in range(self.model.elementCount()):
            e = self.model.elementAt(i)
            if e.isMoveTo():
                path.lineTo(e.x, e.y)
            else:
                path.moveTo(e.x, e.y)
        return path

    @property
    def cut_path(self):
        """ Returns path where it is cutting

        """
        return self.model

    #     def get_offset_path(self,device):
    #         """ Returns path where it is cutting """
    #         path = QtGui.QPainterPath()
    #         _p = QtCore.QPointF(0,0) # previous point
    #         step = 0.1
    #         for subpath in QtSvgDoc.toSubpathList(self.model):#.toSubpathPolygons():
    #             e = subpath.elementAt(0)
    #             path.moveTo(QtCore.QPointF(e.x,e.y))
    #             length = subpath.length()
    #             distance = 0
    #             while distance<=length:
    #                 t = subpath.percentAtLength(distance)
    #                 p = subpath.pointAtPercent(t)
    #                 a = subpath.angleAtPercent(t)+90
    #                 #path.moveTo(p)#QtCore.QPointF(x,y))
    #                 # TOOD: Do i need numpy here???
    #                 x = p.x()+np.multiply(self.device.blade_offset,np.sin(np.deg2rad(a)))
    #                 y = p.y()+np.multiply(self.device.blade_offset,np.cos(np.deg2rad(a)))
    #                 path.lineTo(QtCore.QPointF(x,y))
    #                 distance+=step
    #             #_p = p # update last
    #
    #         return path

    def add_stack(self):
        """ Add a complete stack or fill the row

        """
        copies_left = self.stack_size[0] - (self.copies % self.stack_size[0])
        if copies_left == 0:  # Add full stack
            self.copies = self.copies + self.stack_size[0]
        else:  # Fill stack
            self.copies = self.copies + copies_left

    def remove_stack(self):
        """ Remove a complete stack or the rest of the row

        """
        if self.copies <= self.stack_size[0]:
            self.copies = 1
            return

        copies_left = self.copies % self.stack_size[0]
        if copies_left == 0:  # Add full stack
            self.copies = self.copies - self.stack_size[0]
        else:  # Fill stack
            self.copies = self.copies - copies_left

    def clone(self):
        """ Return a cloned instance of this object

        """
        state = self.__getstate__()
        state.update({
            'material': Material(**self.material.__getstate__()),
            'info': JobInfo(**self.info.__getstate__()),
        })
        return Job(**state)
예제 #14
0
        1: 2
    }], [{
        1: 2
    }], [{
        2: ''
    }]),
    (Dict(int, int), [{
        1: 2
    }], [{
        1: 2
    }], [{
        '': 2
    }, {
        2: ''
    }]),
    (Instance((int, float)), [1, 2.0], [1, 2.0], ['']),
    (ForwardInstance(lambda: (int, float)), [1, 2.0], [1, 2.0], ['']),
    (Typed(float), [1.0], [1.0], [1]),
    (ForwardTyped(lambda: float), [1.0], [1.0], [1]),
    (Subclass(CAtom), [Atom], [Atom], [int]),
    (ForwardSubclass(lambda: CAtom), [Atom], [Atom], [int]),
] + ([(Range(sys.maxsize, sys.maxsize + 2), [sys.maxsize, sys.maxsize + 2],
       [sys.maxsize, sys.maxsize + 2],
       [sys.maxsize - 1, sys.maxsize + 3])] if sys.version_info >
     (3, ) else []))
def test_validation_modes(member, set_values, values, raising_values):
    """Test the validation modes.

    """
    class MemberTest(Atom):
예제 #15
0
class BridgedApplication(Application):
    """An abstract implementation of an Enaml application.

    This serves as a base class for both Android and iOS applications and
    provides support for the python event loop, the development server
    and the bridge.

    """

    __id__ = Int(-1)

    #: View to display within the activity
    activity = Instance(Activity)

    #: If true, debug bridge statements
    debug = Bool()

    #: Use dev server
    dev = Str()
    _dev_session = Value()

    #: Event loop
    loop = Instance(IOLoop, factory=IOLoop.current)

    #: Events to send to the bridge
    _bridge_queue = List()

    #: Time last sent
    _bridge_max_delay = Float(0.005)

    #: Time last sent
    _bridge_last_scheduled = Float()

    #: Entry points to load plugins
    plugins = Dict()

    #: Event triggered when an error occurs
    error_occurred = Event(Exception)

    # -------------------------------------------------------------------------
    # Defaults
    # -------------------------------------------------------------------------
    def _default_plugins(self):
        """Get entry points to load any plugins installed.
        The build process should create an "entry_points.json" file
        with all of the data from the installed entry points.

        """
        plugins = {}
        try:
            with open("entry_points.json") as f:
                entry_points = json.load(f)
            for ep, obj in entry_points.items():
                plugins[ep] = []
                for name, src in obj.items():
                    plugins[ep].append(Plugin(name=name, source=src))
        except Exception as e:
            print(f"Failed to load entry points {e}")
        return plugins

    # -------------------------------------------------------------------------
    # BridgedApplication Constructor
    # -------------------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """Initialize the event loop error handler.  Subclasses must properly
        initialize the proxy resolver.

        """
        super().__init__(*args, **kwargs)
        if self.dev:
            self.start_dev_session()
        self.init_error_handler()
        self.load_plugin_widgets()
        self.load_plugin_factories()

    # -------------------------------------------------------------------------
    # Abstract API Implementation
    # -------------------------------------------------------------------------
    def start(self):
        """Start the application event loop"""
        #: Schedule a load view if given and remote debugging is not active
        #: the remote debugging init call this after dev connection is ready
        if self.dev != "remote":
            self.deferred_call(self.activity.start)
        self.loop.start()

    def stop(self):
        """Stop the application's main event loop."""
        self.loop.stop()

    def deferred_call(self, callback, *args, **kwargs):
        """Invoke a callable on the next cycle of the main event loop
        thread.

        Parameters
        ----------
        callback : callable
            The callable object to execute at some point in the future.

        *args, **kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        self.loop.add_callback(callback, *args, **kwargs)

    def timed_call(self, ms, callback, *args, **kwargs):
        """Invoke a callable on the main event loop thread at a
        specified time in the future.

        Parameters
        ----------
        ms : int
            The time to delay, in milliseconds, before executing the
            callable.

        callback : callable
            The callable object to execute at some point in the future.

        *args, **kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        self.loop.call_later(ms / 1000, callback, *args, **kwargs)

    def is_main_thread(self):
        """Indicates whether the caller is on the main gui thread.

        Returns
        -------
        result : bool
            True if called from the main gui thread. False otherwise.

        """
        return False

    # -------------------------------------------------------------------------
    # App API Implementation
    # -------------------------------------------------------------------------
    async def has_permission(self, permission):
        """Return a future that resolves with the result of the permission"""
        raise NotImplementedError

    async def request_permissions(self, permissions):
        """Return a future that resolves with the result of the
        permission request

        """
        raise NotImplementedError

    # -------------------------------------------------------------------------
    # EventLoop API Implementation
    # -------------------------------------------------------------------------
    def init_error_handler(self):
        """When an error occurs, set the error view in the App"""
        # HACK: Reassign the discard method to show errors
        self.loop._discard_future_result = self._on_future_result

    def create_future(self,
                      return_type: Optional[type] = None) -> BridgeFuture:
        """Create a future object using the EventLoop implementation"""
        return BridgeFuture(return_type)

    # -------------------------------------------------------------------------
    # Bridge API Implementation
    # -------------------------------------------------------------------------
    def show_error(self, msg: Union[bytes, str]):
        """Show the error view with the given message on the UI."""
        self.send_event(Command.ERROR, msg)

    def send_event(self, name: str, *args, **kwargs):
        """Send an event to the native handler. This call is queued and
        batched.

        Parameters
        ----------
        name : str
            The event name to be processed by MainActivity.processMessages.
        *args: args
            The arguments required by the event.
        **kwargs: kwargs
            Options for sending. These are:

            now: boolean
                Send the event now

        """
        n = len(self._bridge_queue)

        # Add to queue
        self._bridge_queue.append((name, args))

        if n == 0:
            # First event, send at next available time
            self._bridge_last_scheduled = time()
            self.deferred_call(self._bridge_send)
            return
        elif kwargs.get("now"):
            self._bridge_send(now=True)
            return

        # If it's been over 5 ms since we last scheduled, run now
        dt = time() - self._bridge_last_scheduled
        if dt > self._bridge_max_delay:
            self._bridge_send(now=True)

    def force_update(self):
        """Force an update now."""
        #: So we don't get out of order
        self._bridge_send(now=True)

    def _on_future_result(self, future: Future) -> None:
        """Avoid unhandled-exception warnings from spawned coroutines."""
        try:
            future.result()
        except Exception as e:
            self.handle_error(future, e)

    def _bridge_send(self, now: bool = False):
        """Send the events over the bridge to be processed by the native
        handler.

        Parameters
        ----------
        now: boolean
            Send all pending events now instead of waiting for deferred calls
            to finish. Use this when you want to update the screen

        """
        if len(self._bridge_queue):
            if self.debug:
                print("======== Py --> Native ======")
                for i, event in enumerate(self._bridge_queue):
                    print(f"{i}: {event}")
                print("===========================")
            self.dispatch_events(dumps(self._bridge_queue))
            self._bridge_queue = []

    def dispatch_events(self, data):
        """Send events to the bridge using the system specific implementation."""
        raise NotImplementedError

    async def process_events(self, data: str):
        """The native implementation must use this call to"""
        events = loads(data)
        if self.debug:
            print("======== Py <-- Native ======")
            for event in events:
                print(event)
            print("===========================")
        for t, event in events:
            if t == "event":
                await self.handle_event(*event)

    async def handle_event(self, result_id: int, ptr: int, method: str,
                           args: list):
        """When we get an 'event' type from the bridge
        handle it by invoking the handler and if needed
        sending back the result.

        """
        obj = None
        result = None
        try:
            obj, handler = get_handler(ptr, method)
            if method == "set_exception":
                # Remote call failed
                obj.set_exception(BridgeException(args))
            elif iscoroutinefunction(handler):
                result = await handler(*(v for t, v in args))
            else:
                result = handler(*(v for t, v in args))
        except BridgeReferenceError as e:
            #: Log the event, don't blow up here
            event = (result_id, ptr, method, args)
            print(f"Error processing event: {event} - {e}")
            self.error_occurred(e)  # type: ignore
            # self.show_error(msg)
        except Exception as e:
            #: Log the event, blow up in user's face
            self.error_occurred(e)  # type: ignore
            err = traceback.format_exc()
            event = (result_id, ptr, method, args)
            msg = f"Error processing event: {event} - {err}"
            print(msg)
            self.show_error(msg)
            raise
        finally:
            if result_id:
                if hasattr(obj, "__nativeclass__"):
                    sig, ret_type = getattr(obj.__class__, method).__returns__
                else:
                    sig = result.__class__.__name__

                self.send_event(
                    Command.RESULT,  #: method
                    result_id,
                    (sig, encode(result)),  #: args
                    now=True,
                )

    def handle_error(self, callback, exc: Exception):
        """Called when an error occurs in an event loop callback.
        By default, sets the error view.

        """
        self.error_occurred(exc)  # type: ignore
        msg = f"Exception in callback {callback}: {traceback.format_exc()}"
        self.show_error(msg.encode("utf-8"))

    # -------------------------------------------------------------------------
    # AppEventListener API Implementation
    # -------------------------------------------------------------------------
    def on_events(self, data):
        """Called when the bridge sends an event. For instance the return
        result of a method call or a callback from a widget event.

        """
        #: Pass to event loop thread
        self.deferred_call(self.process_events, data)

    def on_pause(self):
        """Called when the app is paused."""
        self.activity.paused()

    def on_resume(self):
        """Called when the app is resumed."""
        self.activity.resumed()

    def on_stop(self):
        """Called when the app is stopped."""
        #: Called from thread, make sure the correct thread detaches
        self.activity.stopped()

    def on_destroy(self):
        """Called when the app is destroyed."""
        self.deferred_call(self.stop)

    # -------------------------------------------------------------------------
    # Dev Session Implementation
    # -------------------------------------------------------------------------
    def start_dev_session(self):
        """Start a client that attempts to connect to the dev server
        running on the host `app.dev`

        """
        try:
            from .dev import DevServerSession

            dev = self.dev
            if ":" in dev:
                host, port = dev.split(":")
                params = {"host": host, "port": int(port)}
            else:
                params = {"host": dev}
            session = DevServerSession.initialize(**params)
            session.start()

            #: Save a reference
            self._dev_session = session
        except Exception:
            self.show_error(traceback.format_exc())

    # -------------------------------------------------------------------------
    # Plugin implementation
    # -------------------------------------------------------------------------
    def get_plugins(self, group: str) -> list[Plugin]:
        """Was going to use entry points but that requires a ton of stuff
        which will be extremely slow.

        """
        return self.plugins.get(group, [])

    def load_plugin_widgets(self):
        """Pull widgets added via plugins using the `enaml_native_widgets`
        entry point. The entry point function must return a dictionary of
        Widget declarations to add to the core api.

        def install():
            from charts.widgets.chart_view import BarChart, LineChart
            return {
                'BarChart': BarChart,
                'LineCart': LineChart,
            }

        """
        from enamlnative.widgets import api

        for plugin in self.get_plugins(group="enaml_native_widgets"):
            get_widgets = plugin.load()
            for name, widget in iter(get_widgets()):
                #: Update the core api with these widgets
                setattr(api, name, widget)

    def load_plugin_factories(self):
        """Pull widgets added via plugins using the
        `enaml_native_ios_factories` or `enaml_native_android_factories`
        entry points. The entry point function must return a dictionary of
        Widget declarations to add to the factories for this platform.

        def install():
            return {
                'MyWidget':my_widget_factory,
                # etc...
            }

        """
        raise NotImplementedError
예제 #16
0
class SharedDict(Atom):
    """ Dict wrapper using a lock to protect access to its values.

    Parameters
    ----------
    default : callable, optional
        Callable to use as argument for defaultdict, if unspecified a regular
        dict is used.

    """
    def __init__(self, default=None):
        super(SharedDict, self).__init__()
        if default is not None:
            self._dict = defaultdict(default)
        else:
            self._dict = {}

    @contextmanager
    def safe_access(self, key):
        """Context manager to safely manipulate a value of the dict.

        """
        lock = self._lock
        lock.acquire()

        yield self._dict[key]

        lock.release()

    @contextmanager
    def locked(self):
        """Acquire the instance lock.

        """
        self._lock.acquire()

        yield self

        self._lock.release()

    def get(self, key, default=None):
        with self._lock:
            aux = self._dict.get(key, default)

        return aux

    def items(self):
        with self.locked():
            return self._dict.items()

    # =========================================================================
    # --- Private API ---------------------------------------------------------
    # =========================================================================

    #: Underlying dict.
    _dict = Instance((dict, defaultdict))

    #: Re-entrant lock use to secure the access to the dict.
    _lock = Value(factory=RLock)

    def __getitem__(self, key):

        with self.locked():
            aux = self._dict[key]

        return aux

    def __setitem__(self, key, value):

        with self._lock:
            self._dict[key] = value

    def __delitem__(self, key):

        with self._lock:
            del self._dict[key]

    def __contains__(self, key):
        return key in self._dict

    def __iter__(self):
        return iter(self._dict)

    def __len__(self):
        return len(self._dict)
예제 #17
0
파일: models.py 프로젝트: yangbiaocn/inkcut
class Job(Model):
    """ Create a plot depending on the properties set. Any property that is a
    traitlet will cause an update when the value is changed.

    """
    #: Material this job will be run on
    material = Instance(Material, ()).tag(config=True)

    #: Path to svg document this job parses
    document = Str().tag(config=True)

    #: Nodes to restrict
    document_kwargs = Dict().tag(config=True)

    #: Meta info a the job
    info = Instance(JobInfo, ()).tag(config=True)

    # Job properties used for generating the plot
    size = ContainerList(Float(), default=[1, 1])
    scale = ContainerList(Float(), default=[1, 1]).tag(config=True)
    auto_scale = Bool(False).tag(
        config=True, help="automatically scale if it's too big for the area")
    lock_scale = Bool(True).tag(
        config=True, help="automatically scale if it's too big for the area")

    mirror = ContainerList(Bool(), default=[False, False]).tag(config=True)
    align_center = ContainerList(Bool(), default=[False,
                                                  False]).tag(config=True)

    rotation = Float(0).tag(config=True)
    auto_rotate = Bool(False).tag(
        config=True, help="automatically rotate if it saves space")

    copies = Int(1).tag(config=True)
    auto_copies = Bool(False).tag(config=True, help="always use a full stack")
    copy_spacing = ContainerList(Float(), default=[10, 10]).tag(config=True)
    copy_weedline = Bool(False).tag(config=True)
    copy_weedline_padding = ContainerList(Float(),
                                          default=[10, 10, 10,
                                                   10]).tag(config=True)

    plot_weedline = Bool(False).tag(config=True)
    plot_weedline_padding = ContainerList(Float(),
                                          default=[10, 10, 10,
                                                   10]).tag(config=True)

    order = Enum(*sorted(ordering.REGISTRY.keys())).tag(config=True)

    def _default_order(self):
        return 'Normal'

    feed_to_end = Bool(False).tag(config=True)
    feed_after = Float(0).tag(config=True)

    stack_size = ContainerList(Int(), default=[0, 0])

    #: Filters to cut only certain items
    filters = ContainerList(filters.JobFilter)

    #: Original path parsed from the source document
    path = Instance(QtSvgDoc)

    #: Path filtered by layers/colors and ordered according to the order
    optimized_path = Instance(QPainterPath)

    #: Finaly copy using all the applied job properties
    #: This is what is actually cut out
    model = Instance(QPainterPath)

    _blocked = Bool(False)  # block change events
    _desired_copies = Int(1)  # required for auto copies

    def __setstate__(self, *args, **kwargs):
        """ Ensure that when restoring from disk the material and info
        are not set to None. Ideally these would be defined as Typed but
        the material may be made extendable at some point.
        """
        super(Job, self).__setstate__(*args, **kwargs)
        if not self.info:
            self.info = JobInfo()
        if not self.material:
            self.material = Material()

    def _observe_document(self, change):
        """ Read the document from stdin """
        if change['type'] == 'update' and self.document == '-':
            #: Only load from stdin when explicitly changed to it (when doing
            #: open from the cli) otherwise when restoring state this hangs
            #: startup
            self.path = QtSvgDoc(sys.stdin, **self.document_kwargs)
        elif self.document and os.path.exists(self.document):
            self.path = QtSvgDoc(self.document, **self.document_kwargs)

        # Recreate available filters when the document changes
        self.filters = self._default_filters()

    def _default_filters(self):
        results = []
        if not self.path:
            return results
        for Filter in filters.REGISTRY.values():
            try:
                results.extend(Filter.get_filter_options(self, self.path))
            except Exception as e:
                log.error("Failed loading filters for: %s" % Filter)
                log.exception(e)
        return results

    def _default_optimized_path(self):
        """ Filter parts of the documen based on the selected layers and colors

        """
        doc = self.path
        for f in self.filters:
            # If the color/layer is NOT enabled, then remove that color/layer
            if not f.enabled:
                log.debug("Applying filter {}".format(f))
                doc = f.apply_filter(self, doc)

        # Apply ordering to path
        # this delegates to objects in the ordering module
        OrderingHandler = ordering.REGISTRY.get(self.order)
        if OrderingHandler:
            doc = OrderingHandler().order(self, doc)

        return doc

    @observe('path', 'order', 'filters')
    def _update_optimized_path(self, change):
        """ Whenever the loaded file (and parsed SVG path) changes update
        it based on the filters from the job.

        """
        self.optimized_path = self._default_optimized_path()

    def _create_copy(self):
        """ Creates a copy of the original graphic applying the given
        transforms

        """
        optimized_path = self.optimized_path
        bbox = optimized_path.boundingRect()

        # Create the base copy
        t = QTransform()

        t.scale(
            self.scale[0] * (self.mirror[0] and -1 or 1),
            self.scale[1] * (self.mirror[1] and -1 or 1),
        )

        # Rotate about center
        if self.rotation != 0:
            c = bbox.center()
            t.translate(-c.x(), -c.y())
            t.rotate(self.rotation)
            t.translate(c.x(), c.y())

        # Apply transform
        path = optimized_path * t

        # Add weedline to copy
        if self.copy_weedline:
            self._add_weedline(path, self.copy_weedline_padding)

        # If it's too big we have to scale it
        w, h = path.boundingRect().width(), path.boundingRect().height()
        available_area = self.material.available_area

        #: This screws stuff up!
        if w > available_area.width() or h > available_area.height():

            # If it's too big an auto scale is enabled, resize it to fit
            if self.auto_scale:
                sx, sy = 1, 1
                if w > available_area.width():
                    sx = available_area.width() / w
                if h > available_area.height():
                    sy = available_area.height() / h
                s = min(sx, sy)  # Fit to the smaller of the two
                path = optimized_path * QTransform.fromScale(s, s)

        # Move to bottom left
        p = path.boundingRect().bottomRight()

        path = path * QTransform.fromTranslate(-p.x(), -p.y())

        return path

    @contextmanager
    def events_suppressed(self):
        """ Block change events to prevent feedback loops

        """
        self._blocked = True
        try:
            yield
        finally:
            self._blocked = False

    @observe('path', 'scale', 'auto_scale', 'lock_scale', 'mirror',
             'align_center', 'rotation', 'auto_rotate', 'copies', 'order',
             'copy_spacing', 'copy_weedline', 'copy_weedline_padding',
             'plot_weedline', 'plot_weedline_padding', 'feed_to_end',
             'feed_after', 'material', 'material.size', 'material.padding',
             'auto_copies')
    def update_document(self, change=None):
        """ Recreate an instance of of the plot using the current settings

        """
        if self._blocked:
            return

        if change:
            name = change['name']
            if name == 'copies':
                self._desired_copies = self.copies
            elif name in ('layer', 'color'):
                self._update_optimized_path(change)

        model = self.create()
        if model:
            self.model = model

    def create(self, swap_xy=False, scale=None):
        """ Create a path model that is rotated and scaled

        """
        model = QPainterPath()

        if not self.path:
            return

        path = self._create_copy()

        # Update size
        bbox = path.boundingRect()
        self.size = [bbox.width(), bbox.height()]

        # Create copies
        c = 0
        points = self._copy_positions_iter(path)

        if self.auto_copies:
            self.stack_size = self._compute_stack_sizes(path)
            if self.stack_size[0]:
                copies_left = self.copies % self.stack_size[0]
                if copies_left:  # not a full stack
                    with self.events_suppressed():
                        self.copies = self._desired_copies
                        self.add_stack()

        while c < self.copies:
            x, y = next(points)
            model.addPath(path * QTransform.fromTranslate(x, -y))
            c += 1

        # Create weedline
        if self.plot_weedline:
            self._add_weedline(model, self.plot_weedline_padding)

        # Determine padding
        bbox = model.boundingRect()
        if self.align_center[0]:
            px = (self.material.width() - bbox.width()) / 2.0
        else:
            px = self.material.padding_left

        if self.align_center[1]:
            py = -(self.material.height() - bbox.height()) / 2.0
        else:
            py = -self.material.padding_bottom

        # Scale and rotate
        if scale:
            model *= QTransform.fromScale(*scale)
            px, py = px * abs(scale[0]), py * abs(scale[1])

        if swap_xy:
            t = QTransform()
            t.rotate(90)
            model *= t

        # Move to 0,0
        bbox = model.boundingRect()
        p = bbox.bottomLeft()
        tx, ty = -p.x(), -p.y()

        # If swapped, make sure padding is still correct
        if swap_xy:
            px, py = -py, -px
        tx += px
        ty += py

        model = model * QTransform.fromTranslate(tx, ty)

        end_point = (QPointF(0, -self.feed_after + model.boundingRect().top())
                     if self.feed_to_end else QPointF(0, 0))
        model.moveTo(end_point)

        return model

    def _check_bounds(self, plot, area):
        """ Checks that the width and height of plot are less than the width
        and height of area

        """
        return plot.width() > area.width() or plot.height() > area.height()

    def _copy_positions_iter(self, path, axis=0):
        """ Generator that creates positions of points

        """
        other_axis = axis + 1 % 2
        p = [0, 0]

        bbox = path.boundingRect()
        d = (bbox.width(), bbox.height())
        pad = self.copy_spacing
        stack_size = self._compute_stack_sizes(path)

        while True:
            p[axis] = 0
            yield p  # Beginning of each row

            for i in range(stack_size[axis] - 1):
                p[axis] += d[axis] + pad[axis]
                yield p

            p[other_axis] += d[other_axis] + pad[other_axis]

    def _compute_stack_sizes(self, path):
        # Usable area
        material = self.material
        a = [material.width(), material.height()]
        a[0] -= material.padding[Padding.LEFT] + material.padding[
            Padding.RIGHT]
        a[1] -= material.padding[Padding.TOP] + material.padding[
            Padding.BOTTOM]

        # Clone includes weedline but not spacing
        bbox = path.boundingRect()
        size = [bbox.width(), bbox.height()]

        stack_size = [0, 0]
        p = [0, 0]
        for i in range(2):
            # Compute stack
            while (p[i] + size[i]) < a[i]:  # while another one fits
                stack_size[i] += 1
                p[i] += size[i] + self.copy_spacing[i]  # Add only to end

        self.stack_size = stack_size
        return stack_size

    def _add_weedline(self, path, padding):
        """ Adds a weedline to the path
        by creating a box around the path with the given padding

        """
        bbox = path.boundingRect()
        w, h = bbox.width(), bbox.height()

        tl = bbox.topLeft()
        x = tl.x() - padding[Padding.LEFT]
        y = tl.y() - padding[Padding.TOP]

        w += padding[Padding.LEFT] + padding[Padding.RIGHT]
        h += padding[Padding.TOP] + padding[Padding.BOTTOM]

        path.addRect(x, y, w, h)
        return path

    @property
    def state(self):
        pass

    @property
    def move_path(self):
        """ Returns the path the head moves when not cutting

        """
        # Compute the negative
        path = QPainterPath()
        for i in range(self.model.elementCount()):
            e = self.model.elementAt(i)
            if e.isMoveTo():
                path.lineTo(e.x, e.y)
            else:
                path.moveTo(e.x, e.y)
        return path

    @property
    def cut_path(self):
        """ Returns path where it is cutting

        """
        return self.model

    #     def get_offset_path(self,device):
    #         """ Returns path where it is cutting """
    #         path = QPainterPath()
    #         _p = QPointF(0,0) # previous point
    #         step = 0.1
    #         for subpath in QtSvgDoc.toSubpathList(self.model):#.toSubpathPolygons():
    #             e = subpath.elementAt(0)
    #             path.moveTo(QPointF(e.x,e.y))
    #             length = subpath.length()
    #             distance = 0
    #             while distance<=length:
    #                 t = subpath.percentAtLength(distance)
    #                 p = subpath.pointAtPercent(t)
    #                 a = subpath.angleAtPercent(t)+90
    #                 #path.moveTo(p)#QPointF(x,y))
    #                 # TOOD: Do i need numpy here???
    #                 x = p.x()+np.multiply(self.device.blade_offset,np.sin(np.deg2rad(a)))
    #                 y = p.y()+np.multiply(self.device.blade_offset,np.cos(np.deg2rad(a)))
    #                 path.lineTo(QPointF(x,y))
    #                 distance+=step
    #             #_p = p # update last
    #
    #         return path

    def add_stack(self):
        """ Add a complete stack or fill the row

        """
        stack_size = self.stack_size[0]
        if stack_size == 0:
            self.copies += 1
            return  # Don't divide by 0
        copies_left = stack_size - (self.copies % stack_size)
        if copies_left == 0:  # Add full stack
            self.copies += stack_size
        else:  # Fill stack
            self.copies += copies_left

    def remove_stack(self):
        """ Remove a complete stack or the rest of the row

        """
        stack_size = self.stack_size[0]
        if stack_size == 0 or self.copies <= stack_size:
            self.copies = 1
            return
        copies_left = self.copies % stack_size
        if copies_left == 0:  # Add full stack
            self.copies -= stack_size
        else:  # Fill stack
            self.copies -= copies_left

    def clone(self):
        """ Return a cloned instance of this object

        """
        state = self.__getstate__()
        state.update({
            'material': Material(**self.material.__getstate__()),
            'info': JobInfo(**self.info.__getstate__()),
        })
        return Job(**state)
예제 #18
0
    class Builder(JavaBridgeObject):
        """ Builds a notification.

        Notes
        ------
        The constructor automatically sets the when field to
        System.currentTimeMillis() and the audio stream to the STREAM_DEFAULT.

        """
        __nativeclass__ = set_default('%s.NotificationCompat$Builder' %
                                      package)
        __signature__ = set_default(
            ('android.content.Context', 'java.lang.String'))
        addAction = JavaMethod('%s.NotificationCompat$Action' % package)
        addAction_ = JavaMethod('android.R', 'java.lang.CharSequence',
                                'android.app.PendingIntent')
        addInvisibleAction = JavaMethod('%s.NotificationCompat$Action' %
                                        package)
        addInvisibleAction_ = JavaMethod('android.R', 'java.lang.CharSequence',
                                         'android.app.PendingIntent')
        addPerson = JavaMethod('java.lang.String')

        build = JavaMethod(returns='android.app.Notification')

        setAutoCancel = JavaMethod('boolean')
        setBadgeIconType = JavaMethod('android.R')
        setCategory = JavaMethod('java.lang.String')
        setChannelId = JavaMethod('java.lang.String')
        setColor = JavaMethod('android.graphics.Color')
        setColorized = JavaMethod('boolean')
        setContent = JavaMethod('android.widget.RemoteViews')
        setContentInfo = JavaMethod('java.lang.CharSequence')
        setContentIntent = JavaMethod('android.app.PendingIntent')
        setContentText = JavaMethod('java.lang.CharSequence')
        setContentTitle = JavaMethod('java.lang.CharSequence')
        setCustomBigContentView = JavaMethod('android.widget.RemoteViews')
        setCustomContentView = JavaMethod('android.widget.RemoteViews')
        setCustomHeadsUpContentView = JavaMethod('android.widget.RemoteViews')
        setDefaults = JavaMethod('int')
        setDeleteIntent = JavaMethod('android.app.PendingIntent')
        setExtras = JavaMethod('android.os.Bundle')
        setFullScreenIntent = JavaMethod('android.app.PendingIntent',
                                         'boolean')

        setGroup = JavaMethod('java.lang.String')
        setGroupAlertBehavior = JavaMethod('int')
        setGroupSummary = JavaMethod('boolean')
        setLargeIcon = JavaMethod('android.graphics.Bitmap')
        setLights = JavaMethod('android.graphics.Color', 'int', 'int')
        setLocalOnly = JavaMethod('boolean')
        setNumber = JavaMethod('int')
        setOngoing = JavaMethod('boolean')
        setOnlyAlertOnce = JavaMethod('boolean')
        setPriority = JavaMethod('int')
        setProgress = JavaMethod('int', 'int', 'boolean')
        setPublicVersion = JavaMethod('android.app.Notification')
        setRemoteInputHistory = JavaMethod('Landroid.app.Notification;[')
        setShortcutId = JavaMethod('java.lang.String')
        setShowWhen = JavaMethod('boolean')
        setSmallIcon = JavaMethod('android.R')
        setSmallIcon_ = JavaMethod('android.R', 'int')
        setSortKey = JavaMethod('java.lang.String')
        setSound = JavaMethod('android.net.Uri')
        setSound_ = JavaMethod('android.net.Uri', 'int')
        setStyle = JavaMethod('%s.NotificationCompat$Style' % package)
        setSubText = JavaMethod('java.lang.CharSequence')
        setTicker = JavaMethod('java.lang.CharSequence',
                               'android.widget.RemoteViews')
        setTicker_ = JavaMethod('java.lang.CharSequence')
        setTimeoutAfter = JavaMethod('long')
        setUsesChronometer = JavaMethod('boolean')
        setVibrate = JavaMethod('J[')
        setVisibility = JavaMethod('int')
        setWhen = JavaMethod('long')

        #: Glide Manager for loading bitmaps
        manager = Instance(RequestManager)

        #: BroadcastReceiver for handling actions
        _receivers = List(BroadcastReceiver)

        def _default_manager(self):
            app = AndroidApplication.instance()
            return RequestManager(__id__=Glide.with__(app))

        def load_bitmap(self, src):
            r = RequestBuilder(__id__=self.manager.load(src)).asBitmap()
            return Bitmap(__id__=r.__id__)

        def update(self,
                   title="",
                   text="",
                   info="",
                   sub_text="",
                   ticker="",
                   importance=NotificationManager.IMPORTANCE_DEFAULT,
                   group="",
                   group_summary=None,
                   group_alert_behavior=None,
                   small_icon="@mipmap/ic_launcher",
                   large_icon="",
                   number=None,
                   ongoing=None,
                   local_only=None,
                   style=None,
                   color=None,
                   colorized=None,
                   sound=None,
                   light_color="",
                   light_on=500,
                   light_off=500,
                   show_when=None,
                   show_stopwatch=False,
                   show_progress=False,
                   progress_max=100,
                   progress_current=0,
                   progress_indeterminate=False,
                   auto_cancel=True,
                   timeout_after=0,
                   sort_key="",
                   vibration_pattern=[],
                   shortcut_id="",
                   category="",
                   badge_icon_type=None,
                   only_alert_once=None,
                   notification_options=None,
                   actions=None):
            """ Creates and shows a notification. On android 8+ the channel
            must be created.

            Parameters
            ----------
            badge_icon_type: Int
                Sets which icon to display as a badge for this notification.
            category: String
                Set the notification category.
            channel_id: String
                The id of the channel this notification is displayed on
            color: String
                A color string ex #F00 for red
            colorized: Bool
                Set whether this notification should be colorized.
            group: String
                Set this notification to be part of a group of notifications
                sharing the same key.
            group_summary: Bool
                Set this notification to be the group summary for a group of
                notifications.
            group_alert_behavior: Int
                Sets the group alert behavior for this notification.
            title: String
                Set the title (first row) of the notification, in a standard
                notification.
            text: String
                Set the text (second row) of the notification, in a standard
                notification.
            info: String
                Set the large text at the right-hand side of the notification.
            sub_text: String
                Set the third line of text in the platform notification
                template.
            ticker: String
                Sets the "ticker" text which is sent to accessibility services.
            importance: Int
                The importance or priority of the notification
            number: Int
                Set the large number at the right-hand side of the
                notification.
            ongoing: Bool
                Set whether this is an ongoing notification.
            local_only: Bool
                Set whether or not this notification is only relevant to the
                current device.
            style: String
                A style resource string
            small_icon: String
                A resource identifier @drawable/my_drawable
            large_icon: String
                A resource that glide can load (file:// or http://) url
            show_when: Bool
                Control whether the timestamp set with setWhen is shown in the
                content view.
            show_stopwatch: Bool
                Show the when field as a stopwatch.
            show_progress: Bool
                Show a progressbar
            progress_current: Int
                Current progress
            progress_max: Int
                Max progress
            progress_indeterminate: Bool
                Progress should be indeterminate
            light_color: String
                Set the argb value that you would like the LED on the device to
                blink
            light_on: Int
                Set the on duration of the LED
            light_off: Int
                Set the off duration of the LED
            auto_cancel: Bool
                Close automatically when tapped
            only_alert_once: Bool
                Set this flag if you would only like the sound, vibrate and
                ticker to be played if the notification is not already showing
            timeout_after: Long
                Specifies the time at which this notification should be
                canceled, if it is not already canceled.
            sort_key: String
                Set a sort key that orders this notification among other
                notifications from the same package.
            shortcut_id: String
                If this notification is duplicative of a Launcher shortcut,
                sets the id of the shortcut, in case the Launcher wants to hide
                the shortcut.
            sound: String
               Set the Uri of the sound to play.
            vibration_pattern: List of Long
                Set the vibration pattern to use.
            notification_options: Int
                Set the default notification options that will be used.
            actions: List
                A list of actions to handle

            Returns
            --------
            result: Future
                A future that resolves with the builder of the notification
                that was created.

            References
            ----------
            - https://developer.android.com/guide/topics/ui/notifiers/notifications.html
            - https://developer.android.com/training/notify-user/build-notification.html

            """
            if title:
                self.setContentTitle(title)
            if importance:
                self.setPriority(importance)
            if small_icon:
                self.setSmallIcon(small_icon)
            if text:
                self.setContentText(text)
            if sub_text:
                self.setSubText(sub_text)
            if ticker:
                self.setTicker_(ticker)
            if info:
                self.setContentInfo(info)
            if style:
                self.setStyle(style)
            if color:
                self.setColor(color)
            if colorized is not None:
                self.setColorized(bool(colorized))
            if number is not None:
                self.setNumber(int(number))
            if ongoing is not None:
                self.setOngoing(bool(ongoing))
            if only_alert_once is not None:
                self.setOnlyAlertOnce(bool(only_alert_once))
            if local_only is not None:
                self.setLocalOnly(bool(local_only))
            if auto_cancel:
                self.setAutoCancel(bool(auto_cancel))
            if show_when is not None:  # When is shown by default
                self.setShowWhen(bool(show_when))
            if show_stopwatch:
                self.setUsesChronometer(bool(show_stopwatch))
            if show_progress:
                self.setProgress(int(progress_max), int(progress_current),
                                 bool(progress_indeterminate))
            if timeout_after:
                self.setTimeoutAfter(long(timeout_after))
            if category:
                self.setCategory(category)
            if group:
                self.setGroup(group)
            if group_summary is not None:
                self.setGroupSummary(bool(group_summary))
            if group_alert_behavior is not None:
                self.setGroupAlertBehavior(int(group_alert_behavior))
            if shortcut_id:
                self.setShortcutId(shortcut_id)
            if large_icon:
                self.setLargeIcon(self.load_bitmap(large_icon))
            if light_color:
                self.setLights(light_color, light_on, light_off)
            if sort_key:
                self.setSortKey(sort_key)
            if sound:
                self.setSound(Uri.parse(sound))
            if vibration_pattern:
                self.setVibrate([long(i) for i in vibration_pattern])
            if badge_icon_type is not None:
                self.setBadgeIconType(int(badge_icon_type))
            if notification_options is not None:
                self.setDefaults(int(notification_options))
            if actions is not None:
                app = AndroidApplication.instance()
                for (action_icon, action_text, action_callback) in actions:
                    action_key = "com.codelv.enamlnative.Notify{}".format(
                        self.__id__)
                    intent = Intent()
                    intent.setAction(action_key)
                    receiver = BroadcastReceiver.for_action(action_key,
                                                            action_callback,
                                                            single_shot=False)
                    self._receivers.append(receiver)
                    self.addAction_(
                        "@mipmap/ic_launcher", action_text,
                        PendingIntent.getBroadcast(app, 0, intent, 0))

        def show(self):
            """ Build and show this notification """
            app = AndroidApplication.instance()
            f = app.create_future()

            # Build and show it
            def on_ready(mgr):
                if not mgr:
                    f.set_result(None)
                    return
                self.build().then(lambda r, mgr=mgr: on_built(r, mgr))

            def on_built(__id__, mgr):
                notification = Notification(__id__=__id__)
                mgr.notify(self.__id__, notification)
                f.set_result(self)

            NotificationManager.get().then(on_ready)
            return f
예제 #19
0
class QtTreeView(QtAbstractItemView, ProxyTreeView):
    #: Tree widget
    widget = Typed(QTreeView)
    
    #: Root index
    index = Instance(QModelIndex,())
    
    def create_widget(self):
        self.widget = QTreeView(self.parent_widget())
        
    def init_widget(self):
        super(QtTreeView, self).init_widget()
        d = self.declaration
        self.set_show_root(d.show_root)
        
    def init_model(self):
        self.set_model(QAtomTreeModel(parent=self.widget))
     
    #--------------------------------------------------------------------------
    # Widget Setters
    #--------------------------------------------------------------------------
    def set_show_root(self,show):
        self.widget.setRootIsDecorated(show)
        
    def set_cell_padding(self,padding):
        self.widget.setStyleSheet("QTreeView::item { padding: %ipx }"%padding);
        
    def set_horizontal_minimum_section_size(self,size):
        self.widget.header().setMinimumSectionSize(size)
        
    def set_horizontal_stretch(self,stretch):
        self.widget.header().setStretchLastSection(stretch)
        
    def set_horizontal_headers(self, headers):
        self.widget.header().model().layoutChanged.emit()
        
    def set_resize_mode(self,mode):
        self.widget.header().setResizeMode(RESIZE_MODES[mode])
        
    def set_show_horizontal_header(self,show):
        header = self.widget.header()
        header.show() if show else header.hide()
        
    def set_model(self, model):
        super(QtTreeView, self).set_model(model)
        
    #--------------------------------------------------------------------------
    # View refresh handlers
    #--------------------------------------------------------------------------
    def _refresh_visible_column(self, value):
        self._pending_column_refreshes -=1
        if self._pending_column_refreshes==0:
            d = self.declaration
            # TODO: What about parents???
            try:
                d.visible_column = max(0,min(value,self.model.columnCount(self.index)-d.visible_columns))
            except RuntimeError:
                pass
        
    def _refresh_visible_row(self, value):
        self._pending_row_refreshes -=1
        if self._pending_row_refreshes==0:
            d = self.declaration
            try:
                d.visible_row = max(0,min(value,self.model.rowCount(self.index)-d.visible_rows))
            except RuntimeError:
                pass
예제 #20
0
class PollIOLoop(IOLoop):
    """Base class for IOLoops built around a select-like function.

    For concrete implementations, see `tornado.platform.epoll.EPollIOLoop`
    (Linux), `tornado.platform.kqueue.KQueueIOLoop` (BSD and Mac), or
    `tornado.platform.select.SelectIOLoop` (all platforms).
    """
    _impl = Value()
    time_func = Callable()
    _handlers = Dict()
    _events = Dict()
    _callbacks = Instance(collections.deque)
    _timeouts = List()
    _cancellations = Int()
    _running = Bool()
    _stopped = Bool()
    _closing = Bool()
    _thread_ident = Value()
    _pid = Int()
    _blocking_signal_threshold = Value()
    _timeout_counter = Value()
    _waker = Value(Waker)

    def initialize(self, impl, time_func=None, **kwargs):
        super(PollIOLoop, self).initialize(**kwargs)
        self._impl = impl
        if hasattr(self._impl, 'fileno'):
            set_close_exec(self._impl.fileno())
        self.time_func = time_func or time.time
        #self._handlers = {}
        #self._events = {}
        self._callbacks = collections.deque()
        #self._timeouts = []
        #self._cancellations = 0
        #self._running = False
        #self._stopped = False
        #self._closing = False
        #self._thread_ident = None
        self._pid = os.getpid()
        #self._blocking_signal_threshold = None
        self._timeout_counter = itertools.count()

        # Create a pipe that we send bogus data to when we want to wake
        # the I/O loop when it is idle
        self._waker = Waker()
        self.add_handler(self._waker.fileno(),
                         lambda fd, events: self._waker.consume(), self.READ)

    @classmethod
    def configurable_base(cls):
        return PollIOLoop

    @classmethod
    def configurable_default(cls):
        if hasattr(select, "epoll"):
            from .platforms import EPollIOLoop
            return EPollIOLoop
        if hasattr(select, "kqueue"):
            # Python 2.6+ on BSD or Mac
            from .platforms import KQueueIOLoop
            return KQueueIOLoop
        from .platforms import SelectIOLoop
        return SelectIOLoop

    def close(self, all_fds=False):
        self._closing = True
        self.remove_handler(self._waker.fileno())
        if all_fds:
            for fd, handler in list(self._handlers.values()):
                self.close_fd(fd)
        self._waker.close()
        self._impl.close()
        self._callbacks = None
        self._timeouts = None

    def add_handler(self, fd, handler, events):
        fd, obj = self.split_fd(fd)
        self._handlers[fd] = (obj, stack_context.wrap(handler))
        self._impl.register(fd, events | self.ERROR)

    def update_handler(self, fd, events):
        fd, obj = self.split_fd(fd)
        self._impl.modify(fd, events | self.ERROR)

    def remove_handler(self, fd):
        fd, obj = self.split_fd(fd)
        self._handlers.pop(fd, None)
        self._events.pop(fd, None)
        try:
            self._impl.unregister(fd)
        except Exception:
            gen_log.debug("Error deleting fd from IOLoop", exc_info=True)

    def set_blocking_signal_threshold(self, seconds, action):
        if not hasattr(signal, "setitimer"):
            gen_log.error(
                "set_blocking_signal_threshold requires a signal module "
                "with the setitimer method")
            return
        self._blocking_signal_threshold = seconds
        if seconds is not None:
            signal.signal(signal.SIGALRM,
                          action if action is not None else signal.SIG_DFL)

    def start(self):
        if self._running:
            raise RuntimeError("IOLoop is already running")
        if os.getpid() != self._pid:
            raise RuntimeError("Cannot share PollIOLoops across processes")
        self._setup_logging()
        if self._stopped:
            self._stopped = False
            return
        old_current = getattr(IOLoop._current, "instance", None)
        IOLoop._current.instance = self
        self._thread_ident = thread.get_ident()
        self._running = True

        # signal.set_wakeup_fd closes a race condition in event loops:
        # a signal may arrive at the beginning of select/poll/etc
        # before it goes into its interruptible sleep, so the signal
        # will be consumed without waking the select.  The solution is
        # for the (C, synchronous) signal handler to write to a pipe,
        # which will then be seen by select.
        #
        # In python's signal handling semantics, this only matters on the
        # main thread (fortunately, set_wakeup_fd only works on the main
        # thread and will raise a ValueError otherwise).
        #
        # If someone has already set a wakeup fd, we don't want to
        # disturb it.  This is an issue for twisted, which does its
        # SIGCHLD processing in response to its own wakeup fd being
        # written to.  As long as the wakeup fd is registered on the IOLoop,
        # the loop will still wake up and everything should work.
        old_wakeup_fd = None
        if hasattr(signal, 'set_wakeup_fd') and os.name == 'posix':
            # requires python 2.6+, unix.  set_wakeup_fd exists but crashes
            # the python process on windows.
            try:
                old_wakeup_fd = signal.set_wakeup_fd(
                    self._waker.write_fileno())
                if old_wakeup_fd != -1:
                    # Already set, restore previous value.  This is a little racy,
                    # but there's no clean get_wakeup_fd and in real use the
                    # IOLoop is just started once at the beginning.
                    signal.set_wakeup_fd(old_wakeup_fd)
                    old_wakeup_fd = None
            except ValueError:
                # Non-main thread, or the previous value of wakeup_fd
                # is no longer valid.
                old_wakeup_fd = None

        try:
            while True:
                # Prevent IO event starvation by delaying new callbacks
                # to the next iteration of the event loop.
                ncallbacks = len(self._callbacks)

                # Add any timeouts that have come due to the callback list.
                # Do not run anything until we have determined which ones
                # are ready, so timeouts that call add_timeout cannot
                # schedule anything in this iteration.
                due_timeouts = []
                if self._timeouts:
                    now = self.time()
                    while self._timeouts:
                        if self._timeouts[0].callback is None:
                            # The timeout was cancelled.  Note that the
                            # cancellation check is repeated below for timeouts
                            # that are cancelled by another timeout or callback.
                            heapq.heappop(self._timeouts)
                            self._cancellations -= 1
                        elif self._timeouts[0].deadline <= now:
                            due_timeouts.append(heapq.heappop(self._timeouts))
                        else:
                            break
                    if (self._cancellations > 512 and self._cancellations >
                        (len(self._timeouts) >> 1)):
                        # Clean up the timeout queue when it gets large and it's
                        # more than half cancellations.
                        self._cancellations = 0
                        self._timeouts = [
                            x for x in self._timeouts if x.callback is not None
                        ]
                        heapq.heapify(self._timeouts)

                for i in range(ncallbacks):
                    self._run_callback(self._callbacks.popleft())
                for timeout in due_timeouts:
                    if timeout.callback is not None:
                        self._run_callback(timeout.callback)
                # Closures may be holding on to a lot of memory, so allow
                # them to be freed before we go into our poll wait.
                due_timeouts = timeout = None

                if self._callbacks:
                    # If any callbacks or timeouts called add_callback,
                    # we don't want to wait in poll() before we run them.
                    poll_timeout = 0.0
                elif self._timeouts:
                    # If there are any timeouts, schedule the first one.
                    # Use self.time() instead of 'now' to account for time
                    # spent running callbacks.
                    poll_timeout = self._timeouts[0].deadline - self.time()
                    poll_timeout = max(0, min(poll_timeout, _POLL_TIMEOUT))
                else:
                    # No timeouts and no callbacks, so use the default.
                    poll_timeout = _POLL_TIMEOUT

                if not self._running:
                    break

                if self._blocking_signal_threshold is not None:
                    # clear alarm so it doesn't fire while poll is waiting for
                    # events.
                    signal.setitimer(signal.ITIMER_REAL, 0, 0)

                try:
                    event_pairs = self._impl.poll(poll_timeout)
                except Exception as e:
                    # Depending on python version and IOLoop implementation,
                    # different exception types may be thrown and there are
                    # two ways EINTR might be signaled:
                    # * e.errno == errno.EINTR
                    # * e.args is like (errno.EINTR, 'Interrupted system call')
                    if errno_from_exception(e) == errno.EINTR:
                        continue
                    else:
                        raise

                if self._blocking_signal_threshold is not None:
                    signal.setitimer(signal.ITIMER_REAL,
                                     self._blocking_signal_threshold, 0)

                # Pop one fd at a time from the set of pending fds and run
                # its handler. Since that handler may perform actions on
                # other file descriptors, there may be reentrant calls to
                # this IOLoop that modify self._events
                self._events.update(event_pairs)
                while self._events:
                    fd, events = self._events.popitem()
                    try:
                        fd_obj, handler_func = self._handlers[fd]
                        handler_func(fd_obj, events)
                    except (OSError, IOError) as e:
                        if errno_from_exception(e) == errno.EPIPE:
                            # Happens when the client closes the connection
                            pass
                        else:
                            self.handle_callback_exception(
                                self._handlers.get(fd))
                    except Exception:
                        self.handle_callback_exception(self._handlers.get(fd))
                fd_obj = handler_func = None

        finally:
            # reset the stopped flag so another start/stop pair can be issued
            self._stopped = False
            if self._blocking_signal_threshold is not None:
                signal.setitimer(signal.ITIMER_REAL, 0, 0)
            IOLoop._current.instance = old_current
            if old_wakeup_fd is not None:
                signal.set_wakeup_fd(old_wakeup_fd)

    def stop(self):
        self._running = False
        self._stopped = True
        self._waker.wake()

    def time(self):
        return self.time_func()

    def call_at(self, deadline, callback, *args, **kwargs):
        timeout = _Timeout(
            deadline,
            functools.partial(stack_context.wrap(callback), *args, **kwargs),
            self)
        heapq.heappush(self._timeouts, timeout)
        return timeout

    def remove_timeout(self, timeout):
        # Removing from a heap is complicated, so just leave the defunct
        # timeout object in the queue (see discussion in
        # http://docs.python.org/library/heapq.html).
        # If this turns out to be a problem, we could add a garbage
        # collection pass whenever there are too many dead timeouts.
        timeout.callback = None
        self._cancellations += 1

    def add_callback(self, callback, *args, **kwargs):
        if self._closing:
            return
        # Blindly insert into self._callbacks. This is safe even
        # from signal handlers because deque.append is atomic.
        self._callbacks.append(
            functools.partial(stack_context.wrap(callback), *args, **kwargs))
        if thread.get_ident() != self._thread_ident:
            # This will write one byte but Waker.consume() reads many
            # at once, so it's ok to write even when not strictly
            # necessary.
            self._waker.wake()
        else:
            # If we're on the IOLoop's thread, we don't need to wake anyone.
            pass

    def add_callback_from_signal(self, callback, *args, **kwargs):
        with stack_context.NullContext():
            self.add_callback(callback, *args, **kwargs)
예제 #21
0
class Measure(Atom):
    """

    Attributes
    ----------
    is_running : bool
        Boolean indicating whether or not the measurement is running.
    root_task : instance(RootTask)
        `RootTask` instance representing the enqueued measure.
    monitor : instance(MeasureMonitor)
        Monitor which can be used to follow the measurement.
    use_monitor : bool
        Boolean indicating whether or not to use a monitor to follow the
        measure.

    """
    is_running = Bool(False)
    root_task = Instance(RootTask, ())
    monitor = Instance(MeasureMonitor, ())
    use_monitor = Bool(True)

    def save_measure(self, path):
        """
        """
        config = ConfigObj(path, indent_type='    ')
        config['root_task'] = save_task(self.root_task, mode='config').dict()
        config['monitor'] = self.monitor.save_monitor_state()
        config.write()

    def load_measure(self, path):
        """
        """
        config = ConfigObj(path)
        if 'root_task' in config:
            self.root_task = build_root(mode='config',
                                        config=config['root_task'])
            self.monitor.load_monitor_state(config['monitor'])
        else:
            #Assume this a raw root_task file without name or monitor
            self.root_task = build_root(mode='config', config=config)

    def _observe_root_task(self, change):
        """
        """
        if 'oldvalue' in change:
            change['oldvalue'].unobserve('task_database.notifier',
                                         self.monitor.database_modified)
        self.monitor.clear_state()
        change['value'].task_database.observe('notifier',
                                              self.monitor.database_modified)
        database = change['value'].task_database
        self.monitor.database_entries = database.list_all_entries()
        self.monitor.refresh_monitored_entries()
        self.monitor.measure_name = ''

    def _observe_is_running(self, change):
        """
        """
        new = change['value']
        if new:
            self.monitor.status = 'RUNNING'
예제 #22
0
class ProfileForm(Atom):
    """
    Simple model representing the informations stored in an instrument profile.

    Parameters
    ----------
    **kwargs
        Keyword arguments used to initialize attributes and form attributes

    Attributes
    ----------
    manager: InstrManagerPlugin
        A reference to the current instrument manager.
    name : str
        Name of the instrument used to identify him. Should be different from
        the driver name
    driver_type : str
        Kind of driver to use, ie which kind of standard is used to
        communicate
    driver_list : list(Unicodestr)
        List of known driver matching `driver_type`
    driver : str
        Name of the selected driver
    connection_form : instance(AbstractConnectionForm)
        Form used to display the informations specific to the `driver_type`


    """
    manager = Typed(InstrManagerPlugin)
    name = Unicode('')
    driver_type = Str('')
    drivers = List(Str(), [])
    driver = Str('')
    connection_form = Instance(AbstractConnectionForm)
    connection_form_view = Value()

    def __init__(self, **kwargs):
        super(ProfileForm, self).__init__()
        self.manager = kwargs.pop('manager')
        if 'name' in kwargs:
            self.name = kwargs.pop('name')
        if 'driver_type' in kwargs:
            self.driver_type = kwargs.pop('driver_type')
        if 'driver' in kwargs:
            self.driver = kwargs.pop('driver')

        if self.driver or self.driver_type:
            aux = self.driver if self.driver else self.driver_type
            form_class, view = self.manager.matching_form(aux, view=True)
            if form_class:
                if self.driver:
                    aux, _ = self.manager.drivers_request([self.driver])
                    self.connection_form = form_class(driver=aux[self.driver],
                                                      **kwargs)
                else:
                    self.connection_form = form_class(**kwargs)

    def dict(self):
        """ Return the informations of the form as a dict

        """
        infos = {'name': self.name, 'driver_type': self.driver_type,
                 'driver': self.driver}
        if self.connection_form:
            infos.update(self.connection_form.connection_dict())
        return infos

    def _observe_driver_type(self, change):
        """Build the list of driver matching the selected type.

        """
        new_type = change['value']
        if new_type:
            driver_list = self.manager.matching_drivers([new_type])
            self.drivers = sorted(driver_list)

            form_class, view = self.manager.matching_form(new_type, view=True)
            if form_class:
                self.connection_form = form_class()
                self.connection_form_view = view
            else:
                self.connection_form = None
                self.connection_form_view = None

    def _observe_driver(self, change):
        """ Select the right connection_form for the selected driver.

        """
        driver = change['value']
        if driver:
            form_class, view = self.manager.matching_form(driver, view=True)
            aux, _ = self.manager.drivers_request([driver])
            if form_class:
                if isinstance(self.connection_form, form_class):
                    self.connection_form.driver = aux[driver]
                else:
                    self.connection_form = form_class(driver=driver)
                self.connection_form_view = view
            else:
                self.connection_form = None
                self.connection_form_view = None
예제 #23
0
파일: html.py 프로젝트: ylwb/enaml-web
class Tag(ToolkitObject):
    #: Reference to the proxy object
    proxy = Typed(ProxyTag)

    #: Object ID
    id = d_(Unicode())

    #: Tag name
    tag = d_(Unicode()).tag(attr=False)

    #: CSS classes
    cls = d_(Instance((list, object))).tag(attr=False)

    #: CSS styles
    style = d_(Instance((dict, object))).tag(attr=False)

    #: Node text
    text = d_(Unicode()).tag(attr=False)

    #: Node tail text
    tail = d_(Unicode()).tag(attr=False)

    #: Alt attribute
    alt = d_(Unicode())

    #: Custom attributes not explicitly defined
    attrs = d_(Dict()).tag(attr=False)

    #: JS onclick definition
    onclick = d_(Unicode())

    #: Used to tell js to send click events back to the server
    clickable = d_(Coerced(bool))

    #: Event triggered on click
    clicked = d_(Event())

    #: Used to tell js to send drag events back to the server and sets the
    #: draggable attribute. Must be used with ondragstart.
    draggable = d_(Coerced(bool)).tag(attr=False)

    #: JS ondragstart definition
    ondragstart = d_(Unicode())

    #: JS ondragover definition
    ondragover = d_(Unicode())

    #: JS ondrop definition
    ondrop = d_(Unicode())

    #: Event triggered when a drop occurs
    dropped = d_(Event(ToolkitObject))

    def _default_id(self):
        return '%0x' % id(self)

    @observe('id', 'tag', 'cls', 'style', 'text', 'tail', 'alt', 'attrs',
             'onclick', 'clickable', 'ondragstart', 'ondragover', 'ondrop',
             'draggable')
    def _update_proxy(self, change):
        """ Update the proxy widget when the Widget data changes.

        This also notifies the root that the dom has been modified.

        """
        #: Try default handler
        t = change['type']
        if t == 'update' and self.proxy_is_active:
            name = change['name']
            value = change['value']
            handler = getattr(self.proxy, 'set_' + name, None)
            if handler is not None:
                handler(value)
            else:
                self.proxy.set_attribute(name, value)
            self._notify_modified({
                'id': self.id,
                'type': t,
                'name': name,
                'value': value
            })

    def _notify_modified(self, change):
        """  Triggers a modified event on the root node with the given change.

        Parameters
        ----------
        change: Dict
            A change event dict indicating what change has occurred.

        """
        root = self.root_object()
        if isinstance(root, Html):
            root.modified(change)

    # =========================================================================
    # Object API
    # =========================================================================
    def child_added(self, child):
        super(Tag, self).child_added(child)
        if isinstance(child, Tag) and self.proxy_is_active:
            change = {
                'id': self.id,
                'type': 'added',
                'name': 'children',
                'value': child.render()
            }

            # Indicate where it was added
            children = self.children
            i = children.index(child) + 1
            while i < len(children):
                c = children[i]
                if isinstance(c, Tag):  # Ignore pattern nodes
                    change['before'] = c.id
                    break
                else:
                    i += 1
            # else added to the end
            self._notify_modified(change)

    def child_moved(self, child):
        super(Tag, self).child_moved(child)
        if isinstance(child, Tag) and self.proxy_is_active:
            if self.proxy.child_moved(child.proxy):
                change = {
                    'id': self.id,
                    'type': 'moved',
                    'name': 'children',
                    'value': child.id
                }

                # Indicate where it was moved to
                children = self.children
                i = children.index(child) + 1
                while i < len(children):
                    c = children[i]
                    if isinstance(c, Tag):  # Ignore pattern nodes
                        change['before'] = c.id
                        break
                    else:
                        i += 1
                # else moved to the end
                self._notify_modified(change)

    def child_removed(self, child):
        """ Handles the child removed event.

        This will generate a modified event indicating which child was removed.

        """
        super(Tag, self).child_removed(child)
        if isinstance(child, Tag) and self.proxy_is_active:
            self._notify_modified({
                'id': self.id,
                'type': 'removed',
                'name': 'children',
                'value': child.id,
            })

    # =========================================================================
    # Tag API
    # =========================================================================
    def xpath(self, query, **kwargs):
        """ Find nodes matching the given xpath query

        Parameters
        ----------
        query: String
            The xpath query to run

        Returns
        -------
        nodes: List[Tag]
            List of tags matching the xpath query.

        """
        nodes = self.proxy.xpath(query, **kwargs)
        return [n.declaration for n in nodes]

    def prepare(self, **kwargs):
        """ Prepare this node for rendering.

        This sets any attributes given, initializes and actives the proxy
        as needed.

        """
        for k, v in kwargs.items():
            setattr(self, k, v)
        if not self.is_initialized:
            self.initialize()
        if not self.proxy_is_active:
            self.activate_proxy()

    def render(self, **kwargs):
        """ Render this tag and all children to a string.

        Returns
        -------
        html: String
            The rendered html content of the node.

        """
        self.prepare(**kwargs)
        return self.proxy.render()
예제 #24
0
파일: dev.py 프로젝트: youpsla/enaml-native
class DevServerSession(Atom):
    """ Connect to a dev server running on the LAN
        or if host is 0.0.0.0 server a page to let
        code be pasted in. Note this should NEVER be used
        in a released app!
    """

    #: Singleton Instance of this class
    _instance = None

    #: Reference to the current Application
    app = ForwardInstance(get_app)

    #: Host to connect to (in client mode) or
    #: if set to "server" it will enable "server" mode
    host = Unicode()

    #: Port to serve on (in server mode) or port to connect to (in client mode)
    port = Int(8888)

    #: URL to connect to (in client mode)
    url = Unicode('ws://192.168.21.119:8888/dev')

    #: Websocket connection state
    connected = Bool()

    #: Message buffer
    buf = Unicode()

    #: Dev session mode
    mode = Enum('client', 'server', 'remote')

    #: Hotswap support class
    hotswap = Instance(Hotswapper)

    #: Delegate dev server
    servers = List(Subclass(DevServer),
                   default=[
                       TornadoDevServer,
                       TwistedDevServer,
                   ])
    server = Instance(DevServer)

    #: Delegate dev client
    clients = List(Subclass(DevClient),
                   default=[
                       TornadoDevClient,
                       TwistedDevClient,
                   ])
    client = Instance(DevClient)

    # -------------------------------------------------------------------------
    # Initialization
    # -------------------------------------------------------------------------
    @classmethod
    def initialize(cls, *args, **kwargs):
        """ Create an instance of this class. """
        try:
            return DevServerSession(*args, **kwargs)
        except ImportError:
            pass

    @classmethod
    def instance(cls):
        """ Get the singleton instance  """
        return cls._instance

    def __init__(self, *args, **kwargs):
        """ Overridden constructor that forces only one instance to ever exist.

        """
        if self.instance() is not None:
            raise RuntimeError("A DevServerClient instance already exists!")
        super(DevServerSession, self).__init__(*args, **kwargs)
        DevServerSession._instance = self

    def start(self):
        """ Start the dev session. Attempt to use tornado first, then try
        twisted

        """
        print("Starting debug client cwd: {}".format(os.getcwd()))
        print("Sys path: {}".format(sys.path))

        #: Initialize the hotswapper
        self.hotswap = Hotswapper(debug=False)

        if self.mode == 'server':
            self.server.start(self)
        else:
            self.client.start(self)

    # -------------------------------------------------------------------------
    # Defaults
    # -------------------------------------------------------------------------
    def _default_mode(self):
        """ If host is set to server then serve it from the app! """
        host = self.host
        if host == 'server':
            return 'server'
        elif host == 'remote':
            return 'remote'
        return 'client'

    def _default_url(self):
        """ Websocket URL to connect to and listen for reload requests """
        host = 'localhost' if self.mode == 'remote' else self.host
        return 'ws://{}:{}/dev'.format(host, self.port)

    def _default_app(self):
        """ Application instance """
        return get_app().instance()

    def _default_server(self):
        for Server in self.servers:
            if Server.available():
                return Server()
        raise NotImplementedError(
            "No dev servers are available! "
            "Include tornado or twisted in your requirements!")

    def _default_client(self):
        for Client in self.clients:
            if Client.available():
                return Client()
        raise NotImplementedError(
            "No dev clients are available! "
            "Include tornado or twisted in your requirements!")

    def _observe_connected(self, change):
        """ Log connection state changes """
        print("Dev session {}".format(
            "connected" if self.connected else "disconnected"))

    # -------------------------------------------------------------------------
    # Dev Session API
    # -------------------------------------------------------------------------
    def write_message(self, data, binary=False):
        """ Write a message to the active client

        """
        self.client.write_message(data, binary=binary)

    def handle_message(self, data):
        """ When we get a message """
        msg = json.loads(data)
        print("Dev server message: {}".format(msg))
        handler_name = 'do_{}'.format(msg['type'])
        if hasattr(self, handler_name):
            handler = getattr(self, handler_name)
            result = handler(msg)
            return {'ok': True, 'result': result}
        else:
            err = "Warning: Unhandled message: {}".format(msg)
            print(err)
            return {'ok': False, 'message': err}

    # -------------------------------------------------------------------------
    # Message handling API
    # -------------------------------------------------------------------------
    def do_reload(self, msg):
        """ Called when the dev server wants to reload the view. """
        #: TODO: This should use the autorelaoder
        app = self.app

        #: Show loading screen
        try:
            self.app.widget.showLoading("Reloading... Please wait.", now=True)
            #self.app.widget.restartPython(now=True)
            #sys.exit(0)
        except:
            #: TODO: Implement for iOS...
            pass
        self.save_changed_files(msg)

        if app.load_view is None:
            print("Warning: Reloading the view is not implemented. "
                  "Please set `app.load_view` to support this.")
            return
        if app.view is not None:
            try:
                app.view.destroy()
            except:
                pass

        def wrapped(f):
            def safe_reload(*args, **kwargs):
                try:
                    return f(*args, **kwargs)
                except:
                    #: Display the error
                    app.send_event(Command.ERROR, traceback.format_exc())

            return safe_reload

        app.deferred_call(wrapped(app.load_view), app)

    def do_hotswap(self, msg):
        """ Attempt to hotswap the code """
        #: Show hotswap tooltip
        try:
            self.app.widget.showTooltip("Hot swapping...", now=True)
        except:
            pass
        self.save_changed_files(msg)

        hotswap = self.hotswap
        app = self.app
        try:
            print("Attempting hotswap....")
            with hotswap.active():
                hotswap.update(app.view)
        except:
            #: Display the error
            app.send_event(Command.ERROR, traceback.format_exc())

    # -------------------------------------------------------------------------
    # Utility methods
    # -------------------------------------------------------------------------
    def save_changed_files(self, msg):
        #: On iOS we can't write in the app bundle
        if os.environ.get('TMP'):
            tmp_dir = os.environ['TMP']
            if not os.path.exists(tmp_dir):
                os.makedirs(tmp_dir)
            if tmp_dir not in sys.path:
                sys.path.insert(0, tmp_dir)

                import site
                reload(site)

        with cd(sys.path[0]):
            #: Clear cache
            if os.path.exists('__enamlcache__'):
                shutil.rmtree('__enamlcache__')
            for fn in msg['files']:
                print("Updating {}".format(fn))
                folder = os.path.dirname(fn)
                if folder and not os.path.exists(folder):
                    os.makedirs(folder)
                with open(fn, 'wb') as f:
                    f.write(msg['files'][fn].encode('utf-8'))
예제 #25
0
class BridgedApplication(Application):
    """ An abstract implementation of an Enaml application.

    This serves as a base class for both Android and iOS applications and 
    provides support for the python event loop, the development server 
    and the bridge.

    """
    __id__ = Int(-1)

    #: Keep screen on by setting the WindowManager flag
    keep_screen_on = Bool()

    #: Application lifecycle state must be set by the implementation
    state = Enum('created', 'paused', 'resumed', 'stopped', 'destroyed')

    #: View to display within the activity
    view = Value()

    #: Factory to create and show the view. It takes the app as the first arg
    load_view = Callable()

    #: If true, debug bridge statements
    debug = Bool()

    #: Use dev server
    dev = Unicode()
    _dev_session = Value()

    #: Event loop
    loop = Instance(EventLoop)

    #: Events to send to the bridge
    _bridge_queue = List()

    #: Time last sent
    _bridge_max_delay = Float(0.005)

    #: Time last sent
    _bridge_last_scheduled = Float()

    #: Entry points to load plugins
    plugins = Dict()

    # -------------------------------------------------------------------------
    # Defaults
    # -------------------------------------------------------------------------
    def _default_loop(self):
        """ Get the event loop based on what libraries are available. """
        return EventLoop.default()

    def _default_plugins(self):
        """ Get entry points to load any plugins installed. 
        The build process should create an "entry_points.json" file
        with all of the data from the installed entry points.
        
        """
        plugins = {}
        try:
            with open('entry_points.json') as f:
                entry_points = json.load(f)
            for ep, obj in entry_points.items():
                plugins[ep] = []
                for name, src in obj.items():
                    plugins[ep].append(Plugin(name=name, source=src))
        except Exception as e:
            print("Failed to load entry points {}".format(e))
        return plugins

    # -------------------------------------------------------------------------
    # BridgedApplication Constructor
    # -------------------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """ Initialize the event loop error handler.  Subclasses must properly
        initialize the proxy resolver.
        
        """
        super(BridgedApplication, self).__init__(*args, **kwargs)
        if self.dev:
            self.start_dev_session()
        self.init_error_handler()
        self.load_plugin_widgets()
        self.load_plugin_factories()

    # -------------------------------------------------------------------------
    # Abstract API Implementation
    # -------------------------------------------------------------------------
    def start(self):
        """ Start the application's main event loop
        using either twisted or tornado.
        
        """
        #: Schedule a load view if given and remote debugging is not active
        #: the remote debugging init call this after dev connection is ready
        if self.load_view and self.dev != "remote":
            self.deferred_call(self.load_view, self)

        self.loop.start()

    def stop(self):
        """ Stop the application's main event loop.

        """
        self.loop.stop()

    def deferred_call(self, callback, *args, **kwargs):
        """ Invoke a callable on the next cycle of the main event loop
        thread.

        Parameters
        ----------
        callback : callable
            The callable object to execute at some point in the future.

        *args, **kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        return self.loop.deferred_call(callback, *args, **kwargs)

    def timed_call(self, ms, callback, *args, **kwargs):
        """ Invoke a callable on the main event loop thread at a
        specified time in the future.

        Parameters
        ----------
        ms : int
            The time to delay, in milliseconds, before executing the
            callable.

        callback : callable
            The callable object to execute at some point in the future.

        *args, **kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        return self.loop.timed_call(ms, callback, *args, **kwargs)

    def is_main_thread(self):
        """ Indicates whether the caller is on the main gui thread.

        Returns
        -------
        result : bool
            True if called from the main gui thread. False otherwise.

        """
        return False

    # -------------------------------------------------------------------------
    # App API Implementation
    # -------------------------------------------------------------------------
    def has_permission(self, permission):
        """ Return a future that resolves with the result of the permission """
        raise NotImplementedError

    def request_permissions(self, permissions):
        """ Return a future that resolves with the result of the 
        permission request
        
        """
        raise NotImplementedError

    # -------------------------------------------------------------------------
    # EventLoop API Implementation
    # -------------------------------------------------------------------------
    def init_error_handler(self):
        """ When an error occurs, set the error view in the App

        """
        self.loop.set_error_handler(self.handle_error)

    def create_future(self):
        """ Create a future object using the EventLoop implementation """
        return self.loop.create_future()

    def run_iteration(self):
        """ Run an iteration of the event loop  """
        return self.loop.run_iteration()

    def add_done_callback(self, future, callback):
        """ Add a callback on a future object put here so it can be
        implemented with different event loops.

        Parameters
        -----------
            future: Future or Deferred
                Future implementation for the current EventLoop
                
            callback: callable
                Callback to invoke when the future is done
                
        """
        if future is None:
            raise bridge.BridgeReferenceError(
                "Tried to add a callback to a nonexistent Future. "
                "Make sure you pass the `returns` argument to your JavaMethod")
        return self.loop.add_done_callback(future, callback)

    def set_future_result(self, future, result):
        """ Set the result of the future

        Parameters
        -----------
            future: Future or Deferred
                Future implementation for the current EventLoop
                
            result: object
                Result to set
        """
        return self.loop.set_future_result(future, result)

    # -------------------------------------------------------------------------
    # Bridge API Implementation
    # -------------------------------------------------------------------------
    def show_view(self):
        """ Show the current `app.view`. This will fade out the previous
        with the new view.
            
        """
        raise NotImplementedError

    def get_view(self):
        """ Get the root view to display. Make sure it is
        properly initialized.
        
        """
        view = self.view
        if not view.is_initialized:
            view.initialize()
        if not view.proxy_is_active:
            view.activate_proxy()
        return view.proxy.widget

    def show_error(self, msg):
        """ Show the error view with the given message on the UI.

        """
        self.send_event(bridge.Command.ERROR, msg)

    def send_event(self, name, *args, **kwargs):
        """ Send an event to the native handler. This call is queued and 
        batched.

        Parameters
        ----------
        name : str
            The event name to be processed by MainActivity.processMessages.
        *args: args
            The arguments required by the event.
        **kwargs: kwargs
            Options for sending. These are:

            now: boolean
                Send the event now

        """
        n = len(self._bridge_queue)

        # Add to queue
        self._bridge_queue.append((name, args))

        if n == 0:
            # First event, send at next available time
            self._bridge_last_scheduled = time()
            self.deferred_call(self._bridge_send)
            return
        elif kwargs.get('now'):
            self._bridge_send(now=True)
            return

        # If it's been over 5 ms since we last scheduled, run now
        dt = time() - self._bridge_last_scheduled
        if dt > self._bridge_max_delay:
            self._bridge_send(now=True)

    def force_update(self):
        """ Force an update now. """
        #: So we don't get out of order
        self._bridge_send(now=True)

    def _bridge_send(self, now=False):
        """  Send the events over the bridge to be processed by the native
        handler.

        Parameters
        ----------
        now: boolean
            Send all pending events now instead of waiting for deferred calls 
            to finish. Use this when you want to update the screen

        """
        if len(self._bridge_queue):
            if self.debug:
                print("======== Py --> Native ======")
                for event in self._bridge_queue:
                    print(event)
                print("===========================")
            self.dispatch_events(bridge.dumps(self._bridge_queue))
            self._bridge_queue = []

    def dispatch_events(self, data):
        """ Send events to the bridge using the system specific implementation.
        
        """
        raise NotImplementedError

    def process_events(self, data):
        """ The native implementation must use this call to """
        events = bridge.loads(data)
        if self.debug:
            print("======== Py <-- Native ======")
            for event in events:
                print(event)
            print("===========================")
        for event in events:
            if event[0] == 'event':
                self.handle_event(event)

    def handle_event(self, event):
        """ When we get an 'event' type from the bridge
        handle it by invoking the handler and if needed
        sending back the result.
        
        """
        result_id, ptr, method, args = event[1]
        obj = None
        result = None
        try:
            obj, handler = bridge.get_handler(ptr, method)
            result = handler(*[v for t, v in args])
        except bridge.BridgeReferenceError as e:
            #: Log the event, don't blow up here
            msg = "Error processing event: {} - {}".format(
                  event, e).encode("utf-8")
            print(msg)
            self.show_error(msg)
        except:
            #: Log the event, blow up in user's face
            msg = "Error processing event: {} - {}".format(
                  event, traceback.format_exc()).encode("utf-8")
            print(msg)
            self.show_error(msg)
            raise
        finally:
            if result_id:
                if hasattr(obj, '__nativeclass__'):
                    sig = getattr(type(obj), method).__returns__
                else:
                    sig = type(result).__name__

                self.send_event(
                    bridge.Command.RESULT,  #: method
                    result_id,
                    bridge.msgpack_encoder(sig, result)  #: args
                )

    def handle_error(self, callback):
        """ Called when an error occurs in an event loop callback.
        By default, sets the error view.
        
        """
        self.loop.log_error(callback)
        msg = "\n".join([
            "Exception in callback %r"%callback,
            traceback.format_exc()
        ])
        self.show_error(msg.encode('utf-8'))

    # -------------------------------------------------------------------------
    # AppEventListener API Implementation
    # -------------------------------------------------------------------------
    def on_events(self, data):
        """ Called when the bridge sends an event. For instance the return 
        result of a method call or a callback from a widget event.
        
        """
        #: Pass to event loop thread
        self.deferred_call(self.process_events, data)

    def on_pause(self):
        """ Called when the app is paused.
        """
        pass

    def on_resume(self):
        """ Called when the app is resumed.
        """
        pass

    def on_stop(self):
        """ Called when the app is stopped.
        """
        #: Called from thread, make sure the correct thread detaches
        pass

    def on_destroy(self):
        """ Called when the app is destroyed.
        """
        self.deferred_call(self.stop)

    # -------------------------------------------------------------------------
    # Dev Session Implementation
    # -------------------------------------------------------------------------
    def start_dev_session(self):
        """ Start a client that attempts to connect to the dev server
        running on the host `app.dev`
        
        """
        try:
            from .dev import DevServerSession
            session = DevServerSession.initialize(host=self.dev)
            session.start()

            #: Save a reference
            self._dev_session = session
        except:
            self.show_error(traceback.format_exc())

    # -------------------------------------------------------------------------
    # Plugin implementation
    # -------------------------------------------------------------------------
    def get_plugins(self, group):
        """ Was going to use entry points but that requires a ton of stuff 
        which will be extremely slow.
        
        """
        return self.plugins.get(group, [])

    def load_plugin_widgets(self):
        """ Pull widgets added via plugins using the `enaml_native_widgets` 
        entry point. The entry point function must return a dictionary of 
        Widget declarations to add to the core api.

        def install():
            from charts.widgets.chart_view import BarChart, LineChart
            return {
                'BarChart': BarChart,
                'LineCart': LineChart,
            }

        """
        from enamlnative.widgets import api
        for plugin in self.get_plugins(group='enaml_native_widgets'):
            get_widgets = plugin.load()
            for name, widget in iter(get_widgets()):
                #: Update the core api with these widgets
                setattr(api, name, widget)

    def load_plugin_factories(self):
        """ Pull widgets added via plugins using the 
        `enaml_native_ios_factories` or `enaml_native_android_factories` 
        entry points. The entry point function must return a dictionary of 
        Widget declarations to add to the factories for this platform.

        def install():
            return {
                'MyWidget':my_widget_factory,
                # etc...
            }

        """
        raise NotImplementedError
예제 #26
0
class OccLoadShape(OccShape, ProxyLoadShape):
    #: Update the class reference
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'class_topo_d_s___shape.html')

    #: The shape created
    shape = Instance(BRepBuilderAPI_Transform)

    def create_shape(self):
        """ Create the shape by loading it from the given path. """
        shape = self.load_shape()
        t = self.get_transform()
        self.shape = BRepBuilderAPI_Transform(shape, t, False)

    def get_transform(self):
        d = self.declaration
        t = gp_Trsf()
        #p = d.position
        t.SetTransformation(gp_Ax3(d.axis))  #gp_Vec(p.X(), p.Y(), p.Z()))
        return t

    def load_shape(self):
        d = self.declaration
        if not os.path.exists(d.path):
            raise ValueError("Can't load shape from `{}`, "
                             "the path does not exist".format(d.path))
        path, ext = os.path.splitext(d.path)
        name = ext[1:] if d.loader == 'auto' else d.loader
        loader = getattr(self, 'load_{}'.format(name.lower()))
        return loader(d.path)

    def load_brep(self, path):
        """ Load a brep model """
        shape = TopoDS_Shape()
        builder = BRep_Builder()
        breptools_Read(shape, path, builder)
        return shape

    def load_iges(self, path):
        """ Load an iges model """
        reader = IGESControl_Reader()
        status = reader.ReadFile(path)
        if status != IFSelect_RetDone:
            raise ValueError("Failed to load: {}".format(path))
        reader.PrintCheckLoad(False, IFSelect_ItemsByEntity)
        reader.PrintCheckTransfer(False, IFSelect_ItemsByEntity)
        ok = reader.TransferRoots()
        return reader.Shape(1)

    def load_step(self, path):
        """ Alias for stp """
        return self.load_stp(path)

    def load_stp(self, path):
        """ Load a stp model """
        reader = STEPControl_Reader()
        status = reader.ReadFile(path)
        if status != IFSelect_RetDone:
            raise ValueError("Failed to load: {}".format(path))
        reader.PrintCheckLoad(False, IFSelect_ItemsByEntity)
        reader.PrintCheckTransfer(False, IFSelect_ItemsByEntity)
        ok = reader.TransferRoot()
        return reader.Shape(1)

    def load_stl(self, path):
        """ Load a stl model """
        reader = StlAPI_Reader()
        shape = TopoDS_Shape()
        reader.Read(shape, path)
        return shape

    # -------------------------------------------------------------------------
    # ProxyLoadShape API
    # -------------------------------------------------------------------------
    def set_path(self, path):
        self.create_shape()

    def set_loader(self, loader):
        self.create_shape()
예제 #27
0
class DeviceProtocol(Model):

    #: The declaration that defined this protocol
    declaration = Typed(extensions.DeviceProtocol).tag(config=True)

    #: The active protocol
    transport = Instance(DeviceTransport)

    #: The protocol specific config
    config = Instance(Model, ()).tag(config=True)

    def connection_made(self):
        """ This is called when a connection is made to the device.

        Use this to send any initialization commands before the job starts.

        """
        raise NotImplementedError

    def set_pen(self, p):
        """ Set the pen or tool that should be used.

         Parameters
        ----------
        p: int
            The pen or tool number to use.

        """

    def set_force(self, f):
        """ Set the force the device should use.

        Parameters
        ----------
        f: int
            The force setting value to send to the device

        """

    def set_velocity(self, v):
        """ Set the force the device should use.

        Parameters
        ----------
        v: int
            The force setting value to send to the device

        """

    def move(self, x, y, z, absolute=True):
        """ Called when the device position is updated.

        """
        raise NotImplementedError

    def write(self, data):
        """ Call this to write data using the underlying transport.

        This should typically not be overridden.

        """
        if self.transport is not None:
            self.transport.write(data)

    def data_received(self, data):
        """ Called when the device replies back with data. This can occur
        at any time as communication is asynchronous. The protocol should
        handle as needed.

        Parameters
        ----------
        data


        """
        log.debug("data received: {}".format(data))

    def finish(self):
        """ Called when processing all of the paths of the job are complete.

        Use this to send any finalization commands.

        """
        pass

    def connection_lost(self):
        """ Called the connection to the device is dropped or
        failed to connect.  No more data can be written when this is called.

        """
        pass
예제 #28
0
class WxNotebook(WxConstraintsWidget, ProxyNotebook):
    """ A Wx implementation of an Enaml ProxyNotebook.

    """
    #: A reference to the widget created by the proxy.
    widget = Instance((wxPreferencesNotebook, wxDocumentNotebook))

    #--------------------------------------------------------------------------
    # Initialization API
    #--------------------------------------------------------------------------
    def create_widget(self):
        """ Create the underlying wx notebook widget.

        """
        if self.declaration.tab_style == 'preferences':
            w = wxPreferencesNotebook(self.parent_widget())
        else:
            style = aui.AUI_NB_SCROLL_BUTTONS
            w = wxDocumentNotebook(self.parent_widget(), agwStyle=style)
        self.widget = w

    def init_widget(self):
        """ Create and initialize the notebook control

        """
        super(WxNotebook, self).init_widget()
        d = self.declaration
        self.set_tab_style(d.tab_style)
        self.set_tab_position(d.tab_position)
        self.set_tabs_closable(d.tabs_closable)
        self.set_tabs_movable(d.tabs_movable)

    def init_layout(self):
        """ Handle the layout initialization for the notebook.

        """
        super(WxNotebook, self).init_layout()
        widget = self.widget
        for page in self.pages():
            widget.AddWxPage(page)
        widget.Bind(EVT_COMMAND_LAYOUT_REQUESTED, self.on_layout_requested)

    #--------------------------------------------------------------------------
    # Utility Methods
    #--------------------------------------------------------------------------
    def pages(self):
        """ Get the pages defined for the notebook.

        """
        for p in self.declaration.pages():
            yield p.proxy.widget or None

    #--------------------------------------------------------------------------
    # Child Events
    #--------------------------------------------------------------------------
    def child_added(self, child):
        """ Handle the child added event for a WxNotebook.

        """
        super(WxNotebook, self).child_added(child)
        if isinstance(child, WxPage):
            for index, dchild in enumerate(self.children()):
                if child is dchild:
                    self.widget.InsertWxPage(index, child.widget)

    def child_removed(self, child):
        """ Handle the child removed event for a WxNotebook.

        """
        super(WxNotebook, self).child_removed(child)
        if isinstance(child, WxPage):
            self.widget().RemoveWxPage(child.widget)
            self.size_hint_updated()

    #--------------------------------------------------------------------------
    # Event Handlers
    #--------------------------------------------------------------------------
    def on_layout_requested(self, event):
        """ Handle the layout request event from a child page.

        """
        self.size_hint_updated()

    #--------------------------------------------------------------------------
    # ProxyNotebook API
    #--------------------------------------------------------------------------
    def set_tab_style(self, style):
        """ Set the tab style for the underlying widget.

        """
        # Changing the tab style on wx is not supported
        pass

    def set_tab_position(self, position):
        """ Set the position of the tab bar in the widget.

        """
        # Tab position changes only supported on the document notebook.
        widget = self.widget
        if isinstance(widget, wxDocumentNotebook):
            flags = widget.GetAGWWindowStyleFlag()
            flags &= ~_TAB_POSITION_MASK
            flags |= _TAB_POSITION_MAP[position]
            widget.SetAGWWindowStyleFlag(flags)
            widget.Refresh()  # Avoids rendering artifacts

    def set_tabs_closable(self, closable):
        """ Set whether or not the tabs are closable.

        """
        # Closable tabs are only supported on the document notebook.
        widget = self.widget
        if isinstance(widget, wxDocumentNotebook):
            flags = widget.GetAGWWindowStyleFlag()
            if closable:
                flags |= aui.AUI_NB_CLOSE_ON_ALL_TABS
            else:
                flags &= ~aui.AUI_NB_CLOSE_ON_ALL_TABS
            widget.SetAGWWindowStyleFlag(flags)

    def set_tabs_movable(self, movable):
        """ Set whether or not the tabs are movable.

        """
        # Movable tabs are only supported on the document notebook.
        widget = self.widget
        if isinstance(widget, wxDocumentNotebook):
            flags = widget.GetAGWWindowStyleFlag()
            if movable:
                flags |= aui.AUI_NB_TAB_MOVE
            else:
                flags &= ~aui.AUI_NB_TAB_MOVE
            widget.SetAGWWindowStyleFlag(flags)
예제 #29
0
class DevicePlugin(Plugin):
    """ Plugin for configuring, using, and communicating with
    a device.

    """

    #: Protocols registered in the system
    protocols = List(extensions.DeviceProtocol)

    #: Transports
    transports = List(extensions.DeviceTransport)

    #: Drivers registered in the system
    drivers = List(extensions.DeviceDriver)

    #: Filters registered in the system
    filters = List(extensions.DeviceFilter)

    #: Devices configured
    devices = List(Device).tag(config=True)

    #: Current device
    device = Instance(Device).tag(config=True)

    # -------------------------------------------------------------------------
    # Plugin API
    # -------------------------------------------------------------------------
    def start(self):
        """ Load all the plugins the device is dependent on """
        w = self.workbench
        plugins = []
        with enaml.imports():
            from .transports.raw.manifest import RawFdManifest
            from .transports.serialport.manifest import SerialManifest
            from .transports.printer.manifest import PrinterManifest
            from .transports.disk.manifest import FileManifest
            from inkcut.device.protocols.manifest import ProtocolManifest
            from inkcut.device.drivers.manifest import DriversManifest
            from inkcut.device.filters.manifest import FiltersManifest
            from inkcut.device.pi.manifest import PiManifest
            plugins.append(RawFdManifest)
            plugins.append(SerialManifest)
            plugins.append(PrinterManifest)
            plugins.append(FileManifest)
            plugins.append(ProtocolManifest)
            plugins.append(DriversManifest)
            plugins.append(FiltersManifest)
            plugins.append(PiManifest)

        for Manifest in plugins:
            w.register(Manifest())

        #: This refreshes everything else
        self._refresh_extensions()

        #: Restore state after plugins are loaded
        super(DevicePlugin, self).start()

    def submit(self, job):
        """ Send the given job to the device and restart all stats

        """
        job.info.reset()
        job.info.started = datetime.now()
        return self.device.submit(job)

    def _default_device(self):
        """ If no device is loaded from the previous state, get the device
        from the first driver loaded.

        """
        self._refresh_extensions()

        #: If a device is configured, use that
        if self.devices:
            return self.devices[0]

        #: Otherwise create one using the first registered driver
        if not self.drivers:
            raise RuntimeError("No device drivers were registered. "
                               "This indicates a missing plugin.")
        return self.get_device_from_driver(self.drivers[0])

    def _observe_device(self, change):
        """ Whenever the device changes, redraw """
        #: Redraw
        plugin = self.workbench.get_plugin('inkcut.job')
        plugin.refresh_preview()

    # -------------------------------------------------------------------------
    # Device Driver API
    # -------------------------------------------------------------------------

    def get_device_from_driver(self, driver):
        """ Load the device driver. This generates the device using
        the factory function the DeviceDriver specifies and assigns
        the protocols and transports based on the filters given by
        the driver.

        Parameters
        ----------
            driver: inkcut.device.extensions.DeviceDriver
                The DeviceDriver declaration to use to create a device.
        Returns
        -------
            device: inkcut.device.plugin.Device
                The actual device object that will be used for communication
                and processing the jobs.

        """
        # Set the protocols based on the declaration
        transports = [
            t for t in self.transports if not driver.connections
            or t.id == 'disk' or t.id in driver.connections
        ]

        # Set the protocols based on the declaration
        protocols = [
            p for p in self.protocols
            if not driver.protocols or p.id in driver.protocols
        ]

        # Generate the device
        return driver.factory(driver, transports, protocols)

    # -------------------------------------------------------------------------
    # Device Extensions API
    # -------------------------------------------------------------------------
    def _refresh_extensions(self):
        """ Refresh all extensions provided by the DevicePlugin """
        self._refresh_protocols()
        self._refresh_transports()
        self._refresh_drivers()
        self._refresh_filters()

    def _refresh_protocols(self):
        """ Reload all DeviceProtocols registered by any Plugins

        Any plugin can add to this list by providing a DeviceProtocol
        extension in the PluginManifest.

        """
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DEVICE_PROTOCOL_POINT)

        protocols = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for p in extension.get_children(extensions.DeviceProtocol):
                protocols.append(p)

        #: Update
        self.protocols = protocols

    def _refresh_transports(self):
        """ Reload all DeviceTransports registered by any Plugins

        Any plugin can add to this list by providing a DeviceTransport
        extension in the PluginManifest.

        """
        workbench = self.workbench
        point = workbench.get_extension_point(
            extensions.DEVICE_TRANSPORT_POINT)

        transports = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for t in extension.get_children(extensions.DeviceTransport):
                transports.append(t)

        #: Update
        self.transports = transports

    def _refresh_drivers(self):
        """ Reload all DeviceDrivers registered by any Plugins

        Any plugin can add to this list by providing a DeviceDriver
        extension in the PluginManifest.

        """
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DEVICE_DRIVER_POINT)
        drivers = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for driver in extension.get_children(extensions.DeviceDriver):
                if not driver.id:
                    driver.id = "{} {}".format(driver.manufacturer,
                                               driver.model)
                drivers.append(driver)
        drivers.sort(key=lambda d: d.id)

        # Update
        self.drivers = drivers

    def _refresh_filters(self):
        """ Reload all DeviceFilters registered by any Plugins

        Any plugin can add to this list by providing a DeviceFilter
        extension in the PluginManifest.

        """
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DEVICE_FILTER_POINT)
        filters = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for t in extension.get_children(extensions.DeviceFilter):
                filters.append(t)
        self.filters = filters

    # -------------------------------------------------------------------------
    # Live progress API
    # -------------------------------------------------------------------------
    def reset_preview(self):
        """ Clear the preview """
        self._reset_preview(None)

    @observe('device', 'device.job')
    def _reset_preview(self, change):
        """ Redraw the preview on the screen

        """
        view_items = []

        #: Transform used by the view
        preview_plugin = self.workbench.get_plugin('inkcut.preview')
        plot = preview_plugin.live_preview
        t = preview_plugin.transform

        #: Draw the device
        device = self.device
        job = device.job
        if device and device.area:
            area = device.area
            view_items.append(
                dict(path=device.transform(device.area.path * t),
                     pen=plot.pen_device,
                     skip_autorange=True))

        if job and job.material:
            # Also observe any change to job.media and job.device
            view_items.extend([
                dict(path=device.transform(job.material.path * t),
                     pen=plot.pen_media,
                     skip_autorange=True),
                dict(path=device.transform(job.material.padding_path * t),
                     pen=plot.pen_media_padding,
                     skip_autorange=True)
            ])

        #: Update the plot
        preview_plugin.set_live_preview(*view_items)

    @observe('device.position')
    def _update_preview(self, change):
        """ Watch the position of the device as it changes. """
        if change['type'] == 'update' and self.device.job:
            x, y, z = change['value']
            preview_plugin = self.workbench.get_plugin('inkcut.preview')
            preview_plugin.live_preview.update(change['value'])
예제 #30
0
class OccTransform(OccOperation, ProxyTransform):
    reference = set_default('https://dev.opencascade.org/doc/refman/html/'
                            'classgp___trsf.html')

    _old_shape = Instance(OccShape)

    def get_transform(self):
        d = self.declaration
        result = gp_Trsf()
        #: TODO: Order matters... how to configure it???
        if d.operations:
            for op in d.operations:
                t = gp_Trsf()
                if isinstance(op, Translate):
                    t.SetTranslation(gp_Vec(op.x, op.y, op.z))
                elif isinstance(op, Rotate):
                    t.SetRotation(
                        gp_Ax1(gp_Pnt(*op.point), gp_Dir(*op.direction)),
                        op.angle)
                elif isinstance(op, Mirror):
                    Ax = gp_Ax2 if op.plane else gp_Ax1
                    t.SetMirror(Ax(gp_Pnt(*op.point), gp_Dir(op.x, op.y,
                                                             op.z)))
                elif isinstance(op, Scale):
                    t.SetScale(gp_Pnt(*op.point), op.s)
                result.Multiply(t)
        else:
            axis = gp_Ax3()
            axis.SetDirection(d.direction.proxy)
            result.SetTransformation(axis)
            result.SetTranslationPart(gp_Vec(*d.position))
            if d.rotation:
                t = gp_Trsf()
                t.SetRotation(gp_Ax1(d.position.proxy, d.direction.proxy),
                              d.rotation)
                result.Multiply(t)

        return result

    def update_shape(self, change=None):
        d = self.declaration

        #: Get the shape to apply the tranform to
        if d.shape:
            make_copy = True
            original = coerce_shape(d.shape)
        else:
            # Use the first child
            make_copy = False
            child = self.get_first_child()
            if child is None:
                raise ValueError("Transform has no shape to transform %s" % d)
            original = child.shape

        t = self.get_transform()
        transform = BRepBuilderAPI_Transform(original, t, make_copy)
        shape = transform.Shape()

        # Convert it back to the original type
        self.shape = Topology.cast_shape(shape)

    def set_shape(self, shape):
        if self._old_shape:
            self._old_shape.unobserve('shape', self.update_shape)
        self._old_shape = shape.proxy
        self._old_shape.observe('shape', self.update_shape)

    def set_translate(self, translation):
        self.update_shape()

    def set_rotate(self, rotation):
        self.update_shape()

    def set_scale(self, scale):
        self.update_shape()

    def set_mirror(self, axis):
        self.update_shape()