Example #1
0
 def _reset(self):
     self.storage = TileStorage()
     self.state.reset()
     self.cycleIndex = 0
     self.screen.needs_tiling()
Example #2
0
class Tile:
    #------------------------------------------------------------------------------
    # STATIC METHODS (DISPATCHER RELATED)
    #------------------------------------------------------------------------------

    #
    # Initiate the dispatching routing.
    #
    # Has two contexts: Called with an action and called without.
    #
    # *Called with an action*
    # Implies that PyTyle is forcing the issue and wants something done. This also
    # means that we are here _not_ because of a key press. However, we still check
    # to see if tiling is enabled for this screen. If it isn't, we die.
    #
    # *Called without an action*
    # Implies that we have initiated the dispatch routine with a key press.
    # Therefore, we should check the currently pressed key, and follow the key
    # binding to run its respective method. Make sure to fail if tiling isn't
    # enabled and we aren't calling tile. (Essentially, pressing the tile key
    # binding is the only way to enable tiling.)
    #
    @staticmethod
    def dispatch(tiler, action=None, keycode=None, masks=None):
        if not action and keycode and masks:
            if keycode not in State.get_dispatcher():
                print >> sys.stderr, "Keycode %s is not bound" % keycode
                return

            # Now we need to determine which masks were used...
            if masks in State.get_dispatcher()[keycode]:
                action = State.get_dispatcher()[keycode][masks]
            else:
                print >> sys.stderr, "Keycode %s and keymask %d are not bound" % (keycode, masks)
                return

            if not tiler.screen.is_tiling() and action.find('tile.') == -1:
                return
        elif action:
            # We can only initiate tiling through keycodes...
            if not tiler.screen.is_tiling():
                return

        # Turn the action into a method...
        if action.find('tile.') != -1:
            layout = action[(action.find('.') + 1):]
            if layout != 'default' and layout in Config.TILERS:
                tiler.screen.set_tiler(Config.tilers(layout))
                tiler = tiler.screen.get_tiler()
                tiler._reset()
            action = Tile.tile
        else:
            action = eval('Tile.' + action)

        action(tiler)


    #------------------------------------------------------------------------------
    # CONSTRUCTOR AND GENERIC TILING METHODS
    #------------------------------------------------------------------------------

    #
    # Constructor simply instantiates the TileStorage class and passes it the window filter.
    # Every tiling instance must be attached to a screen.
    #
    # Also, in general, the following tiling methods are fairly generic (any method beginning
    # with an underscore). However, the class hierarchy is setup in such a way that does not
    # preclude their customization. Simply overload whichever method that needs customizing
    # in your tiling class. (_tile, _cycle, _master_increase, _master_decrease should be
    # sufficient here. Along with the helper methods help_find_next and help_find_previous.)
    # We also initialize this tiler's "state"- this will automatically save certain things for
    # us, like the sizes of panes.
    #
    def __init__(self, screen):
        self.screen = screen
        self.storage = TileStorage()
        self.cycleIndex = 0
        self.state = TileState(self)

    #
    # The core of the tiling algorithm. This will be called whenever PyTyle senses
    # that a screen needs to be re-tiled. It is essentially the bread and butter of
    # your tiling algorithm. It is responsible for placing *all* masters and slaves
    # in the TileStorage on the screen (unless you desire other behavior, like a
    # limited number of windows, but the behavior could be unpredictable). See the
    # respective tile methods in the algorithms shipped with this release.
    #
    def _tile(self):
        pass

    #
    # There is probably no need to overload this one. It simply iterates over all
    # stored windows and resizes them back to their original geometry. Geometry is
    # mainly saved when a window is first created and maybe when it switches screens.
    #
    def _untile(self):
        # just resize all the windows back to their original x/y/width/height
        for window in self.storage.get_all():
            if Config.misc('original_decor'):
                window.add_decorations()
            else:
                window.remove_decorations()

            window.resize(window.origx, window.origy, window.origwidth, window.origheight)

    #
    # Tells PyTyle to reload the configuration file.
    #
    def _reload(self):
        State.do_reload()

    #
    # Does a hard reset of the current screen. It empties the tiling storage, reloads
    # the current screen (probes for all windows), and adds the screen to the tiling
    # queue. Hopefully shouldn't have to be used, but is worth trying before
    # restarting PyTyle.
    #
    def _reset(self):
        self.storage = TileStorage()
        self.state.reset()
        self.cycleIndex = 0
        self.screen.needs_tiling()

    #
    # Responsible for cycling all slaves through the master slot. If there are more
    # than one master, then simply use the first master slot. If there are no masters
    # then do nothing. The cycleIndex should be incremented (or decremented, depending
    # upon your algorithm) after the cycle, and you also need to reset it to 0 when it
    # reaches the end. You should use help_find_next and help_find_previous here.
    #
    def _cycle(self):
        pass

    #
    # Simply puts focus on the given screen. The screen is responsible for providing
    # the currently active window. If we don't have one, then don't do anything. Also,
    # immediately quit if the screen is the same as the current tiler screen.
    #
    def _screen_focus(self, screen_num):
        # stop if current window...
        if self.screen.id == screen_num:
            return

        for screen in self.screen.viewport.screens.values():
            if screen_num == screen.id:
                if not screen.get_active():
                    return
                else:
                    screen.get_active().activate()

    #
    # Moves the active window to the given screen. Immediately quit if the screen is
    # the same as the current tiler screen.
    #
    # This method is a little tricky. Once we find our desired screen, we need to
    # update the storage in each screen (that is, the storage for the old screen
    # and the new screen- and this storage counts for both the tiler instance
    # and the screen instance). While we are only here if the current screen is in
    # tiling mode, we do however need to check if the screen we're moving the window
    # to is in tiling mode. If so, add the screen to the tiling queue (the current screen
    # also has to be added). Thus, if the screen we're moving it to is in tiling mode
    # then that screen is responsible for its placement. If not, arbitrarily assign
    # the window to the upper left coordinates of the screen. (Could be bad.) We
    # must then update the screen object of the window we moved, and activate the *old*
    # screen. (Incidentally, I did it this way because that is the default behavior
    # of XMonad.)
    #
    def _screen_put(self, screen_num):
        # stop if current screen...
        if self.screen.id == screen_num:
            return

        for screen in self.screen.viewport.screens.values():
            if screen_num == screen.id:
                add = self.screen.get_active()
                self.screen.delete_window(add)
                self.storage.remove(add)
                screen.add_window(add)
                screen.get_tiler().storage.add(add)

                if not screen.is_tiling():
                    add.resize(screen.x, screen.y, add.width, add.height)

                add.screen = screen
                self.screen.get_active().activate()

    #
    # Increases the area of the master pane. What this does is up to your tiling
    # algorithm. Pay special attention to the proper resizing of any other pane(s).
    #
    def _master_increase(self):
        pass

    #
    # Inverse of _master_increase.
    # _master_increase(_master_decrease(window placement)) = window placement
    # (Unless you set default pixel amounts of each method to be different... o_O)
    #
    def _master_decrease(self):
        pass

    #
    # Simply adds a master to the storage and submits the current screen into the
    # tiling queue. It is possible that your tiling algorithm might not want to
    # support more than one master (ex. XMonad's Circle layout), and in that case,
    # simply overload the method and leave it empty. (Make sure to do the same for
    # _remove_master or else you could be stuck!)
    #
    # Note: Both _add_master and _remove_master try to look at the currently active
    # window. If it is a slave or master, respectively, then that is the window that
    # will be added or removed. Otherwise, the first slave or master, respectively,
    # will be chosen arbitrarily.
    #
    def _add_master(self):
        # use active window if it's a slave
        slaves = self.storage.get_slaves()
        if self.screen.get_active().id in self.storage.get_slaves_by_id():
            self.storage.inc_master_count()
            self.storage.remove(self.screen.get_active())
            self.storage.add(self.screen.get_active())
        elif slaves:
            self.storage.inc_master_count()
            self.storage.remove(slaves[0])
            self.storage.add(slaves[0])
        else:
            return

        self.screen.needs_tiling()

    #
    # Inverse of _add_master.
    #
    # Note: If we somehow get a bigger master count than the number of windows (which
    # is okay and anticipated behavior), but we are trying to decrement, then decrement
    # the counter until we get to the current number of windows.
    #
    # Note: TileStorage will not let the number of masters go below 0 (0 masters is fine).
    #
    def _remove_master(self):
        # if we have too many windows, decrement master count until we're good...
        all = self.storage.get_all()
        while self.storage.get_master_count() > len(all):
            self.storage.dec_master_count()

        # make sure the current window is a master...
        masters = self.storage.get_masters()
        if self.screen.get_active().id in self.storage.get_masters_by_id():
            self.storage.dec_master_count()
            self.storage.remove(self.screen.get_active())
            self.storage.add(self.screen.get_active())
        elif masters:
            self.storage.dec_master_count()
            self.storage.remove(masters[0])
            self.storage.add(masters[0])
        else:
            return

        self.screen.needs_tiling()

    #
    # A very simple method to make the current window the master. If there are more
    # than one master, then use the first master. If there are no masters, then do
    # nothing.
    #
    def _make_active_master(self):
        if self.storage.get_masters():
            self.help_switch(self.storage.get_masters()[0], self.screen.get_active())

    #
    # Simply put the focus on the master window. If there are more than one master,
    # then use the first master. If there are no masters, then do nothing.
    #
    def _win_master(self):
        masters = self.storage.get_masters()

        if not masters:
            return

        masters[0].activate()

    #
    # Simply closes the current window.
    #
    # Note: We don't *really* need this method here. The window can be closed
    # in any way, and it will be detected by PyTyle. It's here mostly for
    # completeness.
    #
    def _win_close(self):
        self.screen.get_active().close()

    #
    # Focuses on the previous window.
    #
    def _win_previous(self):
        self.help_find_previous().activate()

    #
    # Focuses on the next window.
    #
    def _win_next(self):
        self.help_find_next().activate()

    #
    # Switches the current window with the previous window.
    #
    def _switch_previous(self):
        previous = self.help_find_previous()

        # only one window... bye
        if previous.id == self.screen.get_active().id:
            return

        self.help_switch(previous, self.screen.get_active())

    #
    # Switches the current window with the next window.
    #
    def _switch_next(self):
        next = self.help_find_next()

        # only one window... bye
        if next.id == self.screen.get_active().id:
            return

        self.help_switch(next, self.screen.get_active())

    #
    # Maximizes all windows managed by the tiler.
    #
    # Note: This sends a maximize request to the window manager. I'm not sure if this
    # is better than resizing the window to the full screen.
    #
    def _max_all(self):
        for window in self.storage.get_all():
            window.maximize()

    #
    # Restores all windows managed by the tiler.
    #
    def _restore_all(self):
        for window in self.storage.get_all():
            window.restore()

    #
    # A simple debugging tool. Only useful if PyTyle is running from a shell.
    # (Tentatively assigned the Alt-Q key binding.)
    #
    def _query(self):
        print State.get_wm_name()
        print self.screen.viewport.desktop
        print self.storage


    #------------------------------------------------------------------------------
    # PRIVATE HELPER METHODS
    #------------------------------------------------------------------------------

    #
    # Simply saves the position of all windows on this tiling screen.
    #
    def help_save(self):
        for window in self.screen.windows.values():
            window.save_geometry()

    #
    # Resizes the given window. Takes into account its decorations.
    #
    def help_resize(self, window, x, y, width, height, margin = 0):
        if margin > 0:
            x += margin
            y += margin
            width -= (2 * margin)
            height -= (2 * margin)

        if window.static:
            window.remove_static_property()

        if Config.misc('decorations'):
            if not Config.misc('original_decor'):
                window.add_decorations()

            window.resize(int(x), int(y), int(width - window.d_left - window.d_right), int(height - window.d_top - window.d_bottom))
        else:
            if Config.misc('original_decor'):
                window.remove_decorations()

            window.resize(int(x), int(y), int(width - 2), int(height - 2))

    #
    # Reloads the entire storage container underlying the tiling algorithm.
    # Unless you have really special needs, this should be sufficient. The
    # TilingStorage class handles master/slave balance for you according to
    # the masterCount. To use more or less masters, make calls to inc_master_count
    # and dec_master_count respectively.
    #
    # This method tries to update existing storage, although it doesn't assume
    # that there is currently any storage. Keep care to keep storage separate
    # from your tiling algorithm.
    #
    def help_reload(self):
        # delete first...
        for win in self.storage.get_all():
            if win.id not in self.screen.windows or win.hidden:
                self.storage.remove(win)

        # gobble up active window first in case we need a master...
        # and then just add away...
        masters = self.storage.get_masters_by_id()

        if self.screen.get_active() and len(masters) < self.storage.get_master_count() and self.screen.get_active().id in self.screen.windows and self.screen.get_active().id not in masters:
            self.storage.remove(self.screen.get_active())
            self.storage.add(self.screen.get_active())
            masters = self.storage.get_masters_by_id()

        all = self.storage.get_all_by_id()
        for window in self.screen.windows.values():
            if not window.id in all:
                self.storage.add_bottom(window)
            else:
                self.storage.try_to_promote(window)

    #
    # Simple method to switch two windows visually. It also takes care of
    # switching the windows in the storage container as well.
    #
    def help_switch(self, win1, win2):
        # same? weird...
        if win1.id == win2.id:
            return

        newpos = [win2.x, win2.y, win2.width, win2.height]
        win2.resize(win1.x, win1.y, win1.width, win1.height)
        win1.resize(newpos[0], newpos[1], newpos[2], newpos[3])

        self.storage.switch(win1, win2)

    #
    # The help_find_next and help_find_previous helper methods must be implemented
    # in a sub class if the functionality is to be used. Tiling
    # algorithms can sort windows differently such that the next/previous
    # windows cannot be accurately predicted generally. Unfortunately, these
    # little guys can be a little bit of a pain to right. (Edge cases, meh.)
    #
    def help_find_next(self):
        pass

    def help_find_previous(self):
        pass


    #------------------------------------------------------------------------------
    # DISPATCH
    #
    # The following methods are called from the dispatcher, and are directly
    # referenced from inside the .pytylerc configuration file. Most of these
    # methods simply call their member equivalents, but some (like tile/untile)
    # do some house keeping before hand.
    #
    # These should not be overloaded, but rather their equivalent member methods
    # preceded with an "_" should be overloaded to customize your tiling algorithm.
    #
    # You can get cursory information about these methods from the configuration
    # file, or you may peruse their comments above (in this class), and/or may
    # also take a look at the comments in Tilers/TileDefault.py,
    # Tilers/Vertical.py, and/or Tilers/Horizontal.py.
    #------------------------------------------------------------------------------

    def tile(self):
        # save state...
        if not self.screen.is_tiling():
            self.help_save()

        # If we haven't tiled and we're about to,
        # reload the storage...
        if not self.screen.is_tiled():
            self.help_reload()

        self.screen.enable_tiling()
        self._tile()
        self.screen.got_tiling()

    def untile(self):
        self._untile()
        self.screen.disable_tiling()

    def cycle_tiler(self):
        for i in range(len(Config.misc('tilers'))):
            if Config.misc('tilers')[i] is self.__class__.__name__:
                if (i + 1) == len(Config.misc('tilers')):
                    self.screen.set_tiler(Config.tilers(Config.misc('tilers')[0]))
                else:
                    self.screen.set_tiler(Config.tilers(Config.misc('tilers')[i + 1]))

        self._reset()

    def reload(self):
        self._reload()

    def reset(self):
        self._reset()

    def cycle(self):
        self._cycle()

    def screen0_focus(self):
        self._screen_focus(0)

    def screen1_focus(self):
        self._screen_focus(1)

    def screen2_focus(self):
        self._screen_focus(2)

    def screen0_put(self):
        self._screen_put(0)

    def screen1_put(self):
        self._screen_put(1)

    def screen2_put(self):
        self._screen_put(2)

    def master_increase(self):
        self._master_increase()

    def master_decrease(self):
        self._master_decrease()

    def add_master(self):
        self._add_master()

    def remove_master(self):
        self._remove_master()

    def make_active_master(self):
        self._make_active_master()

    def win_master(self):
        self._win_master()

    def win_close(self):
        self._win_close()

    def win_previous(self):
        self._win_previous()

    def win_next(self):
        self._win_next()

    def switch_previous(self):
        self._switch_previous()

    def switch_next(self):
        self._switch_next()

    def max_all(self):
        self._max_all()

    def restore_all(self):
        self._restore_all()

    def query(self):
        self._query()
Example #3
0
 def __init__(self, screen):
     self.screen = screen
     self.storage = TileStorage()
     self.cycleIndex = 0
     self.state = TileState(self)