reorder.py 5.71 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
        # Don't serialize gap tabs as they're recreated automatically
        if result != 'empty':
122
            props.append((i, '%s:%s' % (result, tab.jid.full)))
123 124
    return props

mathieui's avatar
mathieui committed
125

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

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

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

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

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

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

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

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

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

        return None