reorder.py 5.55 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

mathieui's avatar
mathieui committed
95
        typ, name = tab_config.get(option, default=':').split(':', maxsplit=1)
96
        if typ not in TEXT_TO_TAB:
mathieui's avatar
mathieui committed
97
            return None
98
        result[pos] = (TEXT_TO_TAB[typ], name)
mathieui's avatar
mathieui committed
99 100 101

    return result

mathieui's avatar
mathieui committed
102

103 104 105 106 107 108
def check_tab(tab):
    for cls, rep in TAB_TO_TEXT.items():
        if isinstance(tab, cls):
            return rep
    return ''

mathieui's avatar
mathieui committed
109

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

mathieui's avatar
mathieui committed
122

mathieui's avatar
mathieui committed
123
class Plugin(BasePlugin):
124 125
    """reorder plugin"""

mathieui's avatar
mathieui committed
126
    def init(self):
mathieui's avatar
mathieui committed
127 128 129 130 131 132 133 134 135
        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')
136 137

    @command_args_parser.ignored
138 139 140 141
    def command_save_order(self) -> None:
        """
        /save_order
        """
142 143 144 145
        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
146 147

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

mathieui's avatar
mathieui committed
157 158
        old_tabs = self.core.tabs.get_tabs()
        roster = old_tabs.pop(0)
mathieui's avatar
mathieui committed
159

160 161
        create_gaps = config.get('create_gaps')

mathieui's avatar
mathieui committed
162
        new_tabs = [roster]
mathieui's avatar
mathieui committed
163 164
        last = 0
        for pos in sorted(tabs_spec):
165
            if create_gaps and pos > last + 1:
mathieui's avatar
mathieui committed
166 167 168
                new_tabs += [
                    tabs.GapTab(self.core) for i in range(pos - last - 1)
                ]
Maxime Buquet's avatar
Maxime Buquet committed
169 170 171
            cls, jid = tabs_spec[pos]
            try:
                jid = JID(jid)
172 173 174 175 176 177 178 179
                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):
180 181
                        new_tab = cls(self.core, jid)
                        new_tabs.append(new_tab)
182 183
                    else:
                        new_tabs.append(tabs.GapTab(self.core))
184 185 186 187 188 189
            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
190 191 192 193 194

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

Maxime Buquet's avatar
Maxime Buquet committed
195 196
        # 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
197
        self.core.tabs.replace_tabs(new_tabs)
mathieui's avatar
mathieui committed
198
        self.core.refresh_window()
199 200

        return None