reorder.py 5.7 KB
Newer Older
mathieui's avatar
mathieui committed
1 2 3 4 5 6 7 8 9 10 11 12 13
"""
``reorder`` plugin: Reorder the tabs according to a layout

Commands
--------

.. glossary::

    /reorder
        **Usage:** ``/reorder``

        Reorder the tabs according to the configuration.

14 15 16 17
    /save_order
        **Usage:** ``/save_order``

        Save the current tab order to the configuration.
mathieui's avatar
mathieui committed
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

Configuration
-------------

The configuration file must contain a section ``[reorder]`` and each option
must be formatted like ``[tab number] = [tab type]:[tab name]``.

For example:

.. code-block:: ini

    [reorder]
    1 = muc:toto@conference.example.com
    2 = muc:example@muc.example.im
    3 = dynamic:robert@example.org

The ``[tab number]`` must be at least ``1``; if the range is not entirely
covered, e.g.:

.. code-block:: ini

    [reorder]
    1 = muc:toto@conference.example.com
    3 = dynamic:robert@example.org

Poezio will insert gaps between the tabs in order to keep the specified
numbering (so in this case, there will be a tab 1, a tab 3, but no tab 2).


The ``[tab type]`` must be one of:

- ``muc`` (for multi-user chats)
- ``private`` (for chats with a specific user inside a multi-user chat)
- ``dynamic`` (for normal, dynamic conversations tabs)
- ``static`` (for conversations with a specific resource)

And finally, the ``[tab name]`` must be:

- For a type ``muc``, the bare JID of the room
- For a type ``private``, the full JID of the user (room JID with the username as a resource)
- For a type ``dynamic``, the bare JID of the contact
- For a type ``static``, the full JID of the contact
"""
61

Maxime Buquet's avatar
Maxime Buquet committed
62 63
from slixmpp import InvalidJID, JID

64 65
from poezio import tabs
from poezio.decorators import command_args_parser
66
from poezio.plugin import BasePlugin
67
from poezio.config import config
mathieui's avatar
mathieui committed
68

69
TEXT_TO_TAB = {
mathieui's avatar
mathieui committed
70 71 72 73 74 75 76
    'muc': tabs.MucTab,
    'private': tabs.PrivateTab,
    'dynamic': tabs.DynamicConversationTab,
    'static': tabs.StaticConversationTab,
    'empty': tabs.GapTab
}

77 78 79 80 81 82 83 84
TAB_TO_TEXT = {
    tabs.MucTab: 'muc',
    tabs.DynamicConversationTab: 'dynamic',
    tabs.PrivateTab: 'private',
    tabs.StaticConversationTab: 'static',
    tabs.GapTab: 'empty'
}

mathieui's avatar
mathieui committed
85

mathieui's avatar
mathieui committed
86
def parse_config(tab_config):
mathieui's avatar
mathieui committed
87
    result = {}
mathieui's avatar
mathieui committed
88
    for option in tab_config.options('reorder'):
mathieui's avatar
mathieui committed
89 90 91 92
        if not option.isdecimal():
            continue
        pos = int(option)
        if pos in result or pos <= 0:
mathieui's avatar
mathieui committed
93
            return None
mathieui's avatar
mathieui committed
94

95 96 97 98 99
        spec = tab_config.get(option, default=':').split(':', maxsplit=1)
        # Gap tabs are recreated automatically if there's a gap in indices.
        if spec == 'empty':
            return None
        typ, name = spec
100
        if typ not in TEXT_TO_TAB:
mathieui's avatar
mathieui committed
101
            return None
102
        result[pos] = (TEXT_TO_TAB[typ], name)
mathieui's avatar
mathieui committed
103 104 105

    return result

mathieui's avatar
mathieui committed
106

107 108 109 110 111 112
def check_tab(tab):
    for cls, rep in TAB_TO_TEXT.items():
        if isinstance(tab, cls):
            return rep
    return ''

mathieui's avatar
mathieui committed
113

114 115 116 117 118 119
def parse_runtime_tablist(tablist):
    props = []
    i = 0
    for tab in tablist[1:]:
        i += 1
        result = check_tab(tab)
120 121 122
        if result == 'empty':
            props.append((i, 'empty'))
        elif result:
123
            props.append((i, '%s:%s' % (result, tab.jid.full)))
124 125
    return props

mathieui's avatar
mathieui committed
126

mathieui's avatar
mathieui committed
127
class Plugin(BasePlugin):
128 129
    """reorder plugin"""

mathieui's avatar
mathieui committed
130
    def init(self):
mathieui's avatar
mathieui committed
131 132 133 134 135 136 137 138 139
        self.api.add_command(
            'reorder',
            self.command_reorder,
            help='Reorder all tabs using the pre-defined'
            ' layout from the configuration file.')
        self.api.add_command(
            'save_order',
            self.command_save_order,
            help='Save the current tab layout')
140 141

    @command_args_parser.ignored
142 143 144 145
    def command_save_order(self) -> None:
        """
        /save_order
        """
146 147 148 149
        conf = parse_runtime_tablist(self.core.tabs)
        for key, value in conf:
            self.config.set(key, value)
        self.api.information('Tab order saved', 'Info')
mathieui's avatar
mathieui committed
150 151

    @command_args_parser.ignored
152
    def command_reorder(self) -> None:
mathieui's avatar
mathieui committed
153 154 155 156 157
        """
        /reorder
        """
        tabs_spec = parse_config(self.config)
        if not tabs_spec:
158 159
            self.api.information('Invalid reorder config', 'Error')
            return None
mathieui's avatar
mathieui committed
160

mathieui's avatar
mathieui committed
161 162
        old_tabs = self.core.tabs.get_tabs()
        roster = old_tabs.pop(0)
mathieui's avatar
mathieui committed
163

164 165
        create_gaps = config.get('create_gaps')

mathieui's avatar
mathieui committed
166
        new_tabs = [roster]
mathieui's avatar
mathieui committed
167 168
        last = 0
        for pos in sorted(tabs_spec):
169
            if create_gaps and pos > last + 1:
mathieui's avatar
mathieui committed
170 171 172
                new_tabs += [
                    tabs.GapTab(self.core) for i in range(pos - last - 1)
                ]
Maxime Buquet's avatar
Maxime Buquet committed
173 174 175
            cls, jid = tabs_spec[pos]
            try:
                jid = JID(jid)
176 177 178 179 180 181 182 183
                tab = self.core.tabs.by_name_and_class(str(jid), cls=cls)
                if tab and tab in old_tabs:
                    new_tabs.append(tab)
                    old_tabs.remove(tab)
                else:
                    self.api.information('Tab %s not found. Creating it' % jid, 'Warning')
                    # TODO: Add support for MucTab. Requires nickname.
                    if cls in (tabs.DynamicConversationTab, tabs.StaticConversationTab):
184 185
                        new_tab = cls(self.core, jid)
                        new_tabs.append(new_tab)
186 187
                    else:
                        new_tabs.append(tabs.GapTab(self.core))
188 189 190 191 192 193
            except:
                self.api.information('Failed to create tab \'%s\'.' % jid, 'Error')
                if create_gaps:
                    new_tabs.append(tabs.GapTab(self.core))
            finally:
                last = pos
mathieui's avatar
mathieui committed
194 195 196 197 198

        for tab in old_tabs:
            if tab:
                new_tabs.append(tab)

Maxime Buquet's avatar
Maxime Buquet committed
199 200
        # TODO: Ensure we don't break poezio and call this with whatever
        # tablist we have. The roster tab at least needs to be in there.
mathieui's avatar
mathieui committed
201
        self.core.tabs.replace_tabs(new_tabs)
mathieui's avatar
mathieui committed
202
        self.core.refresh_window()
203 204

        return None