root/sx-layoutmgr/sxlayoutmgr.py

Revision 880d60de64b64afb023616ad34572f19e3068f3b, 8.1 KB (checked in by Friedrich Weber <fred@…>, 14 months ago)

samurai-x2 and plugins: fixed imports (hope i didn't forget anything)

  • Property mode set to 100644
Line 
1# Copyright (c) 2008-2009, samurai-x.org
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#     * Redistributions of source code must retain the above copyright
7#       notice, this list of conditions and the following disclaimer.
8#     * Redistributions in binary form must reproduce the above copyright
9#       notice, this list of conditions and the following disclaimer in the
10#       documentation and/or other materials provided with the distribution.
11#     * Neither the name of the samurai-x.org nor the
12#       names of its contributors may be used to endorse or promote products
13#       derived from this software without specific prior written permission.
14#
15# THIS SOFTWARE IS PROVIDED BY SAMURAI-X.ORG ``AS IS'' AND ANY
16# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18# DISCLAIMED. IN NO EVENT SHALL SAMURAI-X.ORG  BE LIABLE FOR ANY
19# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26"""
27    sx-layoutmgr is a plugin that adds layout algorithms to desktops.
28
29    Dependencies
30    ------------
31
32    sx-layoutmgr depends on :ref:`sx-actions` and :ref:`sx-desktops`.
33
34    Configuration
35    -------------
36
37    sx-layoutmgr reads the desired layout for each desktop from the
38    desktop information key "layout".
39
40    Builtin layouts:
41
42    `floating`
43        The user is free to arrange the windows.
44    `max`
45        Maximize the currently focused window
46    `vert`
47        Stack the windows vertically
48    `horiz`
49        Stack the windows horizontally
50
51    Example::
52
53        'desktops.desktops': [
54             ('desktop one', {'layout': 'vert'}),
55             ('just another desktop', {'layout': 'floating'}),
56             ('the third one!', {'layout': 'max'}),
57             ]
58
59    Actions
60    -------
61
62    .. function:: layoutmgr.set_layout(name)
63        :module:
64
65        sets the layout of the currently active desktop to the layout *name*.
66
67        Required parameters:
68            * `screen`
69            * `name`
70
71    .. function:: layoutmgr.cycle([offset=+1])
72        :module:
73       
74        cycles through the available desktop layouts for the currently
75        active desktop.
76
77        Required parameters:
78            * `screen`
79
80"""
81
82from samuraix.plugin import Plugin
83from samuraix.util import OrderedDict
84from samuraix import app
85
86from ooxcb.protocol import xproto
87
88import logging
89log = logging.getLogger(__name__)
90
91
92class Layout(object):
93    def __init__(self, desktop):
94        self.desktop = desktop   
95        desktop.push_handlers(self)
96
97    def detach(self):
98        self.desktop.remove_handlers(self)
99
100    def on_rearrange(self, desktop):
101        self.layout()
102
103    def layout(self):
104        raise NotImplemented()
105
106
107class FloatingLayout(Layout):
108    """ do nothing. """
109    name = 'floating'
110
111    def layout(self):
112        # raise the currently focused window to the top
113        if self.desktop.clients:
114            self.desktop.clients.current() \
115                    .actor.configure(stack_mode=xproto.StackMode.Above)
116            app.conn.flush()
117
118
119class MaxLayout(Layout):
120    """ make the focused window fill the screen """
121
122    name = 'max'
123
124    def layout(self):
125        geom = self.desktop.screen.get_geometry()
126        client = self.desktop.clients.current()
127        client.actor.configure(x=geom.x, y=geom.y, width=geom.width, height=geom.height)
128        app.conn.flush()
129
130
131class VertLayout(Layout):
132    """ arrange windows vertically with equal height """
133
134    name = 'vert'
135
136    def layout(self):
137        if self.desktop.clients:
138            geom = self.desktop.screen.get_geometry()
139            h = geom.height / len(self.desktop.clients)
140            t = 0 
141            for client in self.desktop.clients:
142                client.actor.configure(x=0, y=t, width=geom.width, height=h)
143                t += h
144            app.conn.flush()
145
146
147class HorizLayout(Layout):
148    """ arrange windows horizontally with equal width """
149
150    name = 'horiz'
151
152    def layout(self):
153        if self.desktop.clients:
154            geom = self.desktop.screen.get_geometry()
155            w = geom.width / len(self.desktop.clients)
156            t = 0 
157            for client in self.desktop.clients:
158                client.actor.configure(x=t, y=0, width=w, height=geom.height)
159                t += w
160            app.conn.flush()
161
162
163class SXLayoutMgr(Plugin):
164    key = 'layoutmgr'
165
166    def __init__(self, app):
167        self.layouters = OrderedDict()
168        self.register(FloatingLayout)
169        self.register(MaxLayout)
170        self.register(VertLayout)
171        self.register(HorizLayout)
172
173        self.app = app
174        app.push_handlers(self)
175
176        # register actions
177        app.plugins['actions'].register('layoutmgr.set_layout',
178                self.action_set_layout)
179        app.plugins['actions'].register('layoutmgr.cycle',
180                self.action_cycle)
181
182    def action_set_layout(self, info):
183        """
184            set the layout of the current desktop.
185
186            parameters:
187                `name`: str
188                    identifier of the layout
189
190            needed:
191                `screen`
192        """
193        self.attach_layouter(
194                info['screen'].data['desktops'].active_desktop,
195                self.layouters[info['name']]
196                )
197
198    def action_cycle(self, info):
199        """
200            cycle the layouts for the current desktop
201
202            parameters:
203                `offset`: int
204                    offset to cycle, defaults to +1
205
206            needed:
207                `screen`
208        """
209        offset = info.get('offset', 1)
210        desktop = info['screen'].data['desktops'].active_desktop
211        layouter_cls = self.get_data(desktop).__class__
212        current_name = getattr(layouter_cls, 'name', layouter_cls.__name__)
213
214        keys = self.layouters.keys()
215        length = len(keys)
216        index = keys.index(current_name)
217        new_index = ((index or length) + offset) % length
218        new_layouter_cls = self.layouters[keys[new_index]]
219        self.attach_layouter(desktop, new_layouter_cls)
220
221    def register(self, layouter_cls):
222        name = getattr(layouter_cls, 'name', layouter_cls.__name__)
223        log.info('registering layouter %s = %s', name, layouter_cls)
224        self.layouters[name] = layouter_cls
225
226    def on_ready(self, app):
227        # should potentially do this in on_create_screen but
228        # that means making sure desktop.on_create_screen is called
229        # first...
230        for screen in app.screens:
231            screendata = screen.data['desktops']
232            screendata.push_handlers(self)
233            for desktop in screendata.desktops:
234                log.debug('trying to attach to %s...', desktop)
235                layouter_name = desktop.config.get('layout')
236                if not layouter_name:
237                    log.debug('no layouter_name')
238                    continue
239                layouter_cls = self.layouters.get(layouter_name)
240                if not layouter_cls:
241                    log.error('cant find layouter %s', name)
242                    return
243                self.attach_layouter(desktop, layouter_cls)
244
245    def attach_layouter(self, desktop, layouter_cls):
246        """
247            attach the layouter class *layouter_cls* to *desktop*.
248            That will overwrite any layouter that was
249            attached before.
250        """
251        if self.has_data(desktop):
252            self.get_data(desktop).detach()
253            self.remove_data(desktop)
254        log.debug('attached %s', layouter_cls)
255        layouter = layouter_cls(desktop)
256        self.attach_data_to(desktop, layouter)
257        #desktop.push_handlers(on_rearrange=self.on_rearrange)
258        # rearrange it automatically
259        layouter.layout()
Note: See TracBrowser for help on using the browser.