Completed
Push — master ( 39bbc3...ba7203 )
by Stephan
49s
created

check_tolerance()   A

Complexity

Conditions 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 13
rs 9.4285
1
"""Layout calculation code"""
2
3
__author__ = "Stephan Sokolow (deitarion/SSokolow)"
4
__license__ = "GNU GPL 2.0 or later"
5
6
import math
7
from heapq import heappop, heappush
8
9
# Allow MyPy to work without depending on the `typing` package
10
# (And silence complaints from only using the imported types in comments)
11
MYPY = False
12
if MYPY:
13
    # pylint: disable=unused-import
14
    from typing import (Any, Dict, Iterable, Iterator, List, Optional,  # NOQA
15
                        Sequence, Sized, Tuple, Union)
16
17
    # pylint: disable=import-error, no-name-in-module
18
    from gtk.gdk import Rectangle  # NOQA
19
    from .util import GeomTuple, PercentRect  # NOQA
20
21
    Geom = Union[Rectangle, GeomTuple]  # pylint: disable=invalid-name
22
del MYPY
23
24
def check_tolerance(distance, monitor_geom, tolerance=0.1):
25
    """Check whether a distance is within tolerance, adjusted for window size.
26
27
    @param distance: An integer value representing a distance in pixels.
28
    @param monitor_geom: An (x, y, w, h) tuple representing the monitor
29
        geometry in pixels.
30
    @param tolerance: A value between 0.0 and 1.0, inclusive, which represents
31
        a percentage of the monitor size.
32
    """
33
34
    # Take the euclidean distance of the monitor rectangle and convert
35
    # `distance` into a percentage of it, then test against `tolerance`.
36
    return float(distance) / math.hypot(*tuple(monitor_geom)[2:4]) < tolerance
37
38
def closest_geom_match(needle, haystack):
39
    # type: (Geom, Sequence[Geom]) -> Tuple[int, int]
40
    """Find the geometry in C{haystack} that most closely matches C{needle}.
41
42
    @return: A tuple of the euclidean distance and index in C{haystack} for the
43
             best match.
44
    """
45
    # Calculate euclidean distances between the window's current geometry
46
    # and all presets and store them in a min heap.
47
    euclid_distance = []  # type: List[Tuple[int, int]]
48
    for haystack_pos, haystack_val in enumerate(haystack):
49
        distance = sum([(needle_i - haystack_i) ** 2 for (needle_i, haystack_i)
50
                        in zip(tuple(needle), tuple(haystack_val))]) ** 0.5
51
        heappush(euclid_distance, (distance, haystack_pos))
52
53
    # to the next configuration. Otherwise, use the first configuration.
54
    closest_distance, closest_idx = heappop(euclid_distance)
55
    return closest_distance, closest_idx
56
57
def resolve_fractional_geom(geom_tuple, monitor_geom, win_geom=None):
58
    # type: (Optional[Geom], Geom, Optional[Geom]) -> Geom
59
    """Resolve proportional (eg. 0.5) and preserved (None) coordinates.
60
61
    @param geom_tuple: An (x, y, w, h) tuple with monitor-relative values in
62
                       the range from 0.0 to 1.0, inclusive.
63
64
                       If C{None}, then the value of C{win_geom} will be used.
65
    @param monitor_geom: An (x, y, w, h) tuple defining the bounding box of the
66
                       monitor (or other desired region) within the desktop.
67
    @param win_geom: An (x, y, w, h) tuple defining the current shape of the
68
                       window, in absolute desktop pixel coordinates.
69
    """
70
    monitor_geom = tuple(monitor_geom)
71
72
    if geom_tuple is None:
73
        return win_geom
74
    else:
75
        # Multiply x and w by monitor.w, y and h by monitor.h
76
        return tuple(int(i * j) for i, j in
77
                     zip(geom_tuple, monitor_geom[2:4] + monitor_geom[2:4]))
78
79
class GravityLayout(object):  # pylint: disable=too-few-public-methods
80
    """Helper for translating top-left relative dimensions to other corners.
81
82
    Used to generate L{commands.cycle_dimensions} presets.
83
84
    Expects to operate on decimal percentage values. (0 <= x <= 1)
85
    """
86
    #: Possible window alignments relative to the monitor/desktop.
87
    #: @todo 1.0.0: Normalize these to X11 or CSS terminology for 1.0
88
    #:     (API-breaking change)
89
    GRAVITIES = {
90
        'top-left': (0.0, 0.0),
91
        'top': (0.5, 0.0),
92
        'top-right': (1.0, 0.0),
93
        'left': (0.0, 0.5),
94
        'middle': (0.5, 0.5),
95
        'right': (1.0, 0.5),
96
        'bottom-left': (0.0, 1.0),
97
        'bottom': (0.5, 1.0),
98
        'bottom-right': (1.0, 1.0),
99
    }  # type: Dict[str, Tuple[float, float]]
100
101
    def __init__(self, margin_x=0, margin_y=0):  # type: (int, int) -> None
102
        """
103
        @param margin_x: Horizontal margin to apply when calculating window
104
            positions, as decimal percentage of screen width.
105
        @param margin_y: Vertical margin to apply when calculating window
106
            positions, as decimal percentage of screen height.
107
        """
108
        self.margin_x = margin_x
109
        self.margin_y = margin_y
110
111
    # pylint: disable=too-many-arguments
112
    def __call__(self,
0 ignored issues
show
Coding Style Naming introduced by
The name x does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
Coding Style Naming introduced by
The name y does not conform to the argument naming conventions ([a-z_][a-z0-9_]{2,30}$).

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
113
                 width,               # type: float
114
                 height,              # type: float
115
                 gravity='top-left',  # type: str
116
                 x=None,              # type: Optional[float]
117
                 y=None               # type: Optional[float]
118
                 ):  # type: (...) -> Tuple[float, float, float, float]
119
        """Return an C{(x, y, w, h)} tuple relative to C{gravity}.
120
121
        This function takes and returns percentages, represented as decimals
122
        in the range 0 <= x <= 1, which can be multiplied by width and height
123
        values in actual units to produce actual window geometry.
124
125
        It can be used in two ways:
126
127
          1. If called B{without} C{x} and C{y} values, it will compute a
128
          geometry tuple which will align a window C{w} wide and C{h} tall
129
          according to C{geometry}.
130
131
          2. If called B{with} C{x} and C{y} values, it will translate a
132
          geometry tuple which is relative to the top-left corner so that it is
133
          instead relative to another corner.
134
135
        @param width: Desired width
136
        @param height: Desired height
137
        @param gravity: Desired window alignment from L{GRAVITIES}
138
        @param x: Desired horizontal position if not the same as C{gravity}
139
        @param y: Desired vertical position if not the same as C{gravity}
140
141
        @returns: C{(x, y, w, h)}
142
143
        @note: All parameters except C{gravity} are decimal values in the range
144
        C{0 <= x <= 1}.
145
        """
146
147
        x = x or self.GRAVITIES[gravity][0]
148
        y = y or self.GRAVITIES[gravity][1]
149
        offset_x = width * self.GRAVITIES[gravity][0]
150
        offset_y = height * self.GRAVITIES[gravity][1]
151
152
        return (round(x - offset_x + self.margin_x, 3),
153
                round(y - offset_y + self.margin_y, 3),
154
                round(width - (self.margin_x * 2), 3),
155
                round(height - (self.margin_y * 2), 3))
156
157
def make_winsplit_positions(columns):
158
    # type: (int) -> Dict[str, List[PercentRect]]
159
    """Generate the classic WinSplit Revolution tiling presets
160
161
    @todo: Figure out how best to put this in the config file.
162
    """
163
164
    # TODO: Plumb GravityLayout.__init__'s arguments into the config file
165
    gvlay = GravityLayout()
166
    col_width = 1.0 / columns
167
    cycle_steps = tuple(round(col_width * x, 3)
168
                        for x in range(1, columns))
169
170
    middle_steps = (1.0,) + cycle_steps
171
    edge_steps = (0.5,) + cycle_steps
172
173
    positions = {
174
        'middle': [gvlay(width, 1, 'middle') for width in middle_steps],
175
    }
176
177
    for grav in ('top', 'bottom'):
178
        positions[grav] = [gvlay(width, 0.5, grav) for width in middle_steps]
179
    for grav in ('left', 'right'):
180
        positions[grav] = [gvlay(width, 1, grav) for width in edge_steps]
181
    for grav in ('top-left', 'top-right', 'bottom-left', 'bottom-right'):
182
        positions[grav] = [gvlay(width, 0.5, grav) for width in edge_steps]
183
184
    return positions
185