Source code for enable.base

# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at
# Thanks for using Enthought open source!
Define a base set of constants and functions used by the remainder of the
Enable package.
# -----------------------------------------------------------------------------
#  Functions defined: bounding_box
#                     intersect_coordinates
#                     union_coordinates
#                     intersect_bounds
#                     union_bounds
#                     disjoint_intersect_coordinates
#                     does_disjoint_intersect_coordinates
#                     bounding_coordinates
#                     bounds_to_coordinates
#                     coordinates_to_bounds
#                     coordinates_to_size
#                     add_rectangles
#                     xy_in_bounds
#                     gc_image_for
#                     send_event_to
#                     subclasses_of
# -----------------------------------------------------------------------------

# Enthought library imports
from traits.api import TraitError

from kiva.api import (

from .colors import color_table, transparent_color

# Special 'empty rectangle' indicator:
empty_rectangle = -1

# Used to offset positions by half a pixel and bounding width/height by 1.
# TODO: Resolve this in a more intelligent manner.
half_pixel_bounds_inset = (0.5, 0.5, -1.0, -1.0)

# Positions:
TOP = 32
LEFT = 4


# -----------------------------------------------------------------------------
# Helper font functions
# -----------------------------------------------------------------------------

font_families = {
    "default": DEFAULT,
    "decorative": DECORATIVE,
    "roman": ROMAN,
    "script": SCRIPT,
    "swiss": SWISS,
    "modern": MODERN,
font_styles = {"italic": ITALIC}
font_weights = {"bold": BOLD}
font_noise = ["pt", "point", "family"]

# Pick a default font that should work on all platforms.
default_font_name = "modern 10"
default_font = Font(family=MODERN, size=10)

[docs]def bounding_box(components): "Compute the bounding box for a set of components" bxl, byb, bxr, byt = bounds_to_coordinates(components[0].bounds) for component in components[1:]: xl, yb, xr, yt = bounds_to_coordinates(component.bounds) bxl = min(bxl, xl) byb = min(byb, yb) bxr = max(bxr, xr) byt = max(byt, yt) return (bxl, byb, bxr, byt)
[docs]def intersect_coordinates(coordinates1, coordinates2): "Compute the intersection of two coordinate based rectangles" if (coordinates1 is empty_rectangle) or (coordinates2 is empty_rectangle): return empty_rectangle xl1, yb1, xr1, yt1 = coordinates1 xl2, yb2, xr2, yt2 = coordinates2 xl = max(xl1, xl2) yb = max(yb1, yb2) xr = min(xr1, xr2) yt = min(yt1, yt2) if (xr > xl) and (yt > yb): return (xl, yb, xr, yt) return empty_rectangle
[docs]def intersect_bounds(bounds1, bounds2): "Compute the intersection of two bounds rectangles" if (bounds1 is empty_rectangle) or (bounds2 is empty_rectangle): return empty_rectangle intersection = intersect_coordinates( bounds_to_coordinates(bounds1), bounds_to_coordinates(bounds2) ) if intersection is empty_rectangle: return empty_rectangle xl, yb, xr, yt = intersection return (xl, yb, xr - xl, yt - yb)
[docs]def union_coordinates(coordinates1, coordinates2): "Compute the union of two coordinate based rectangles" if coordinates1 is empty_rectangle: return coordinates2 elif coordinates2 is empty_rectangle: return coordinates1 xl1, yb1, xr1, yt1 = coordinates1 xl2, yb2, xr2, yt2 = coordinates2 return (min(xl1, xl2), min(yb1, yb2), max(xr1, xr2), max(yt1, yt2))
[docs]def union_bounds(bounds1, bounds2): "Compute the union of two bounds rectangles" xl, yb, xr, yt = union_coordinates( bounds_to_coordinates(bounds1), bounds_to_coordinates(bounds2) ) if xl is None: return empty_rectangle return (xl, yb, xr - xl, yt - yb)
[docs]def does_disjoint_intersect_coordinates(coordinates_list, coordinates): """ Return whether a rectangle intersects a disjoint set of rectangles anywhere """ # If new rectangle is empty, the result is empty: if coordinates is empty_rectangle: return False # If we have an 'infinite' area, then return the new rectangle: if coordinates_list is None: return True # Intersect the new rectangle against each rectangle in the list until an # non_empty intersection is found: xl1, yb1, xr1, yt1 = coordinates for xl2, yb2, xr2, yt2 in coordinates_list: if (min(xr1, xr2) > max(xl1, xl2)) and (min(yt1, yt2) > max(yb1, yb2)): return True return False
[docs]def bounding_coordinates(coordinates_list): "Return the bounding rectangle for a list of rectangles" if coordinates_list is None: return None if len(coordinates_list) == 0: return empty_rectangle xl, yb, xr, yt = 1.0e10, 1.0e10, -1.0e10, -1.0e10 for xl1, yb1, xr1, yt1 in coordinates_list: xl = min(xl, xl1) yb = min(yb, yb1) xr = max(xr, xr1) yt = max(yt, yt1) return (xl, yb, xr, yt)
[docs]def bounds_to_coordinates(bounds): "Convert a bounds rectangle to a coordinate rectangle" x, y, dx, dy = bounds return (x, y, x + dx, y + dy)
[docs]def coordinates_to_bounds(coordinates): "Convert a coordinates rectangle to a bounds rectangle" xl, yb, xr, yt = coordinates return (xl, yb, xr - xl, yt - yb)
[docs]def coordinates_to_size(coordinates): "Convert a coordinates rectangle to a size tuple" xl, yb, xr, yt = coordinates return (xr - xl, yt - yb)
[docs]def add_rectangles(rectangle1, rectangle2): "Add two bounds or coordinate rectangles" return ( rectangle1[0] + rectangle2[0], rectangle1[1] + rectangle2[1], rectangle1[2] + rectangle2[2], rectangle1[3] + rectangle2[3], )
[docs]def xy_in_bounds(x, y, bounds): "Test whether a specified (x,y) point is in a specified bounds" x0, y0, dx, dy = bounds return (x0 <= x < x0 + dx) and (y0 <= y < y0 + dy)
[docs]def send_event_to(components, event_name, event): "Send an event to a specified set of components until it is 'handled'" pre_event_name = "pre_" + event_name for component in components: setattr(component, pre_event_name, event) if event.handled: return len(components) for i in range(len(components) - 1, -1, -1): setattr(components[i], event_name, event) if event.handled: return i return 0
[docs]def subclasses_of(klass): "Generate all of the classes (and subclasses) for a specified class" yield klass for subclass in klass.__bases__: for result in subclasses_of(subclass): yield result
[docs]class IDroppedOnHandler: "Interface for draggable objects that handle the 'dropped_on' event"
[docs] def was_dropped_on(self, component, event): raise NotImplementedError