about summary refs log tree commit diff stats
path: root/src/ui/window_list.c
blob: c9ae8ba90c6450bce81bc8ecc0c0673d81a28fd0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title>Python: module ranger.container.environment</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head><body bgcolor="#f0f0f8">

<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
<tr bgcolor="#7799ee">
<td valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<br><big><big><strong><a href="ranger.html"><font color="#ffffff">ranger</font></a>.<a href="ranger.container.html"><font color="#ffffff">container</font></a>.environment</strong></big></big></font></td
><td align=right valign=bottom
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:/home/hut/work/ranger/ranger/container/environment.py">/home/hut/work/ranger/ranger/container/environment.py</a></font></td></tr></table>
    <p></p>
<p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ee77aa">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Classes</strong></big></font></td></tr>
    
<tr><td bgcolor="#ee77aa"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</td>
<td width="100%"><dl>
<dt><font face="helvetica, arial"><a href="ranger.shared.settings.html#SettingsAware">ranger.shared.settings.SettingsAware</a>(<a href="builtins.html#object">builtins.object</a>)
</font></dt><dd>
<dl>
<dt><font face="helvetica, arial"><a href="ranger.container.environment.html#Environment">Environment</a>
</font></dt></dl>
</dd>
</dl>
 <p>
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
<tr bgcolor="#ffc8d8">
<td colspan=3 valign=bottom>&nbsp;<br>
<font color="#000000" face="helvetica, arial"><a name="Environment">class <strong>Environment</strong></a>(<a href="ranger.shared.settings.html#SettingsAware">ranger.shared.settings.SettingsAware</a>)</font></td></tr>
    
<tr bgcolor="#ffc8d8"><td rowspan=2><tt>&nbsp;&nbsp;&nbsp;</tt></td>
<td colspan=2><tt>A&nbsp;collection&nbsp;of&nbsp;data&nbsp;which&nbsp;is&nbsp;relevant&nbsp;for&nbsp;more&nbsp;than<br>
one&nbsp;class.<br>&nbsp;</tt></td></tr>
<tr><td>&nbsp;</td>
<td width="100%"><dl><dt>Method resolution order:</dt>
<dd><a href="ranger.container.environment.html#Environment">Environment</a></dd>
<dd><a href="ranger.shared.settings.html#SettingsAware">ranger.shared.settings.SettingsAware</a></dd>
<dd><a href="builtins.html#object">builtins.object</a></dd>
</dl>
<hr>
Methods defined here:<br>
<dl><dt><a name="Environment-__init__"><strong>__init__</strong></a>(self, path)</dt></dl>

<dl><dt><a name="Environment-assign_correct_cursor_positions"><strong>assign_correct_cursor_positions</strong></a>(self)</dt><dd><tt>Assign&nbsp;correct&nbsp;cursor&nbsp;positions&nbsp;for&nbsp;subdirectories</tt></dd></dl>

<dl><dt><a name="Environment-at_level"><strong>at_level</strong></a>(self, level)</dt><dd><tt>Returns&nbsp;the&nbsp;FileSystemObject&nbsp;at&nbsp;the&nbsp;given&nbsp;level.<br>
level&nbsp;1&nbsp;=&gt;&nbsp;preview<br>
level&nbsp;0&nbsp;=&gt;&nbsp;current&nbsp;file/directory<br>
level&nbsp;&lt;0&nbsp;=&gt;&nbsp;parent&nbsp;directories</tt></dd></dl>

<dl><dt><a name="Environment-enter_dir"><strong>enter_dir</strong></a>(self, path, history<font color="#909090">=True</font>)</dt><dd><tt>Enter&nbsp;given&nbsp;path</tt></dd></dl>

<dl><dt><a name="Environment-garbage_collect"><strong>garbage_collect</strong></a>(self)</dt><dd><tt>Delete&nbsp;unused&nbsp;directory&nbsp;objects</tt></dd></dl>

<dl><dt><a name="Environment-get_directory"><strong>get_directory</strong></a>(self, path)</dt><dd><tt>Get&nbsp;the&nbsp;directory&nbsp;object&nbsp;at&nbsp;the&nbsp;given&nbsp;path</tt></dd></dl>

<dl><dt><a name="Environment-get_selection"><strong>get_selection</strong></a>(self)</dt></dl>

<dl><dt><a name="Environment-history_go"><strong>history_go</strong></a>(self, relative)</dt><dd><tt>Move&nbsp;relative&nbsp;in&nbsp;history</tt></dd></dl>

<dl><dt><a name="Environment-key_append"><strong>key_append</strong></a>(self, key)</dt><dd><tt>Append&nbsp;a&nbsp;key&nbsp;to&nbsp;the&nbsp;keybuffer</tt></dd></dl>

<dl><dt><a name="Environment-key_clear"><strong>key_clear</strong></a>(self)</dt><dd><tt>Clear&nbsp;the&nbsp;keybuffer</tt></dd></dl>

<hr>
Data and other attributes defined here:<br>
<dl><dt><strong>cf</strong> = None</dl>

<dl><dt><strong>copy</strong> = None</dl>

<dl><dt><strong>cut</strong> = None</dl>

<dl><dt><strong>directories</strong> = None</dl>

<dl><dt><strong>history</strong> = None</dl>

<dl><dt><strong>keybuffer</strong> = None</dl>

<dl><dt><strong>last_search</strong> = None</dl>

<dl><dt><strong>path</strong> = None</dl>

<dl><dt><strong>pathway</strong> = None</dl>

<dl><
/*
 * window_list.c
 * vim: expandtab:ts=4:sts=4:sw=4
 *
 * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
 * Copyright (C) 2019 - 2020 Michael Vetter <jubalh@iodoru.org>
 *
 * This file is part of Profanity.
 *
 * Profanity is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Profanity is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Profanity.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give permission to
 * link the code of portions of this program with the OpenSSL library under
 * certain conditions as described in each individual source file, and
 * distribute linked combinations including the two.
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception, you
 * may extend this exception to your version of the file(s), but you are not
 * obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version. If you delete this exception statement from all
 * source files in the program, then also delete it here.
 *
 */

#include "config.h"

#include <string.h>
#include <assert.h>
#include <stdlib.h>

#include <glib.h>

#include "common.h"
#include "config/preferences.h"
#include "config/theme.h"
#include "plugins/plugins.h"
#include "ui/ui.h"
#include "ui/window_list.h"
#include "xmpp/xmpp.h"
#include "xmpp/roster_list.h"
#include "tools/http_upload.h"

static GHashTable* windows;
static int current;
static Autocomplete wins_ac;
static Autocomplete wins_close_ac;

static int _wins_cmp_num(gconstpointer a, gconstpointer b);
static int _wins_get_next_available_num(GList* used);

void
wins_init(void)
{
    windows = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)win_free);

    ProfWin* console = win_create_console();
    g_hash_table_insert(windows, GINT_TO_POINTER(1), console);

    current = 1;

    wins_ac = autocomplete_new();
    autocomplete_add(wins_ac, "console");

    wins_close_ac = autocomplete_new();
    autocomplete_add(wins_close_ac, "all");
    autocomplete_add(wins_close_ac, "read");
}

ProfWin*
wins_get_console(void)
{
    return g_hash_table_lookup(windows, GINT_TO_POINTER(1));
}

gboolean
wins_chat_exists(const char* const barejid)
{
    ProfChatWin* chatwin = wins_get_chat(barejid);
    return (chatwin != NULL);
}

ProfChatWin*
wins_get_chat(const char* const barejid)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_CHAT) {
            ProfChatWin* chatwin = (ProfChatWin*)window;
            if (g_strcmp0(chatwin->barejid, barejid) == 0) {
                g_list_free(values);
                return chatwin;
            }
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    return NULL;
}

static gint
_cmp_unsubscribed_wins(ProfChatWin* a, ProfChatWin* b)
{
    return g_strcmp0(a->barejid, b->barejid);
}

GList*
wins_get_chat_unsubscribed(void)
{
    GList* result = NULL;
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_CHAT) {
            ProfChatWin* chatwin = (ProfChatWin*)window;
            PContact contact = roster_get_contact(chatwin->barejid);
            if (contact == NULL) {
                result = g_list_insert_sorted(result, chatwin, (GCompareFunc)_cmp_unsubscribed_wins);
            }
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    return result;
}

ProfConfWin*
wins_get_conf(const char* const roomjid)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_CONFIG) {
            ProfConfWin* confwin = (ProfConfWin*)window;
            if (g_strcmp0(confwin->roomjid, roomjid) == 0) {
                g_list_free(values);
                return confwin;
            }
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    return NULL;
}

ProfMucWin*
wins_get_muc(const char* const roomjid)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_MUC) {
            ProfMucWin* mucwin = (ProfMucWin*)window;
            if (g_strcmp0(mucwin->roomjid, roomjid) == 0) {
                g_list_free(values);
                return mucwin;
            }
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    return NULL;
}

ProfPrivateWin*
wins_get_private(const char* const fulljid)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_PRIVATE) {
            ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
            if (g_strcmp0(privatewin->fulljid, fulljid) == 0) {
                g_list_free(values);
                return privatewin;
            }
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    return NULL;
}

ProfPluginWin*
wins_get_plugin(const char* const tag)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_PLUGIN) {
            ProfPluginWin* pluginwin = (ProfPluginWin*)window;
            if (g_strcmp0(pluginwin->tag, tag) == 0) {
                g_list_free(values);
                return pluginwin;
            }
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    return NULL;
}

void
wins_close_plugin(char* tag)
{
    ProfWin* toclose = wins_get_by_string(tag);
    if (toclose == NULL) {
        return;
    }

    int index = wins_get_num(toclose);
    ui_close_win(index);

    wins_tidy();
}

GList*
wins_get_private_chats(const char* const roomjid)
{
    GList* result = NULL;
    GString* prefix = g_string_new(roomjid);
    g_string_append(prefix, "/");
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_PRIVATE) {
            ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
            if (roomjid == NULL || g_str_has_prefix(privatewin->fulljid, prefix->str)) {
                result = g_list_append(result, privatewin);
            }
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    g_string_free(prefix, TRUE);
    return result;
}

void
wins_private_nick_change(const char* const roomjid, const char* const oldnick, const char* const newnick)
{
    Jid* oldjid = jid_create_from_bare_and_resource(roomjid, oldnick);

    ProfPrivateWin* privwin = wins_get_private(oldjid->fulljid);
    if (privwin) {
        free(privwin->fulljid);

        Jid* newjid = jid_create_from_bare_and_resource(roomjid, newnick);
        privwin->fulljid = strdup(newjid->fulljid);
        win_println((ProfWin*)privwin, THEME_THEM, "!", "** %s is now known as %s.", oldjid->resourcepart, newjid->resourcepart);

        autocomplete_remove(wins_ac, oldjid->fulljid);
        autocomplete_remove(wins_close_ac, oldjid->fulljid);
        autocomplete_add(wins_ac, newjid->fulljid);
        autocomplete_add(wins_close_ac, newjid->fulljid);

        jid_destroy(newjid);
    }

    jid_destroy(oldjid);
}

void
wins_change_nick(const char* const barejid, const char* const oldnick, const char* const newnick)
{
    ProfChatWin* chatwin = wins_get_chat(barejid);
    if (chatwin) {
        if (oldnick) {
            autocomplete_remove(wins_ac, oldnick);
            autocomplete_remove(wins_close_ac, oldnick);
        }
        autocomplete_add(wins_ac, newnick);
        autocomplete_add(wins_close_ac, newnick);
    }
}

void
wins_remove_nick(const char* const barejid, const char* const oldnick)
{
    ProfChatWin* chatwin = wins_get_chat(barejid);
    if (chatwin) {
        if (oldnick) {
            autocomplete_remove(wins_ac, oldnick);
            autocomplete_remove(wins_close_ac, oldnick);
        }
    }
}

ProfWin*
wins_get_current(void)
{
    if (windows) {
        return g_hash_table_lookup(windows, GINT_TO_POINTER(current));
    } else {
        return NULL;
    }
}

GList*
wins_get_nums(void)
{
    return g_hash_table_get_keys(windows);
}

void
wins_set_current_by_num(int i)
{
    ProfWin* window = g_hash_table_lookup(windows, GINT_TO_POINTER(i));
    if (window) {
        current = i;
        if (window->type == WIN_CHAT) {
            ProfChatWin* chatwin = (ProfChatWin*)window;
            assert(chatwin->memcheck == PROFCHATWIN_MEMCHECK);
            chatwin->unread = 0;
            plugins_on_chat_win_focus(chatwin->barejid);
        } else if (window->type == WIN_MUC) {
            ProfMucWin* mucwin = (ProfMucWin*)window;
            assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
            mucwin->unread = 0;
            mucwin->unread_mentions = FALSE;
            mucwin->unread_triggers = FALSE;
            plugins_on_room_win_focus(mucwin->roomjid);
        } else if (window->type == WIN_PRIVATE) {
            ProfPrivateWin* privatewin = (ProfPrivateWin*)window;
            privatewin->unread = 0;
        }

        // if we switched to console
        if (current == 0) {
            // remove all alerts
            cons_clear_alerts();
        } else {
            // remove alert from window where we switch to
            cons_remove_alert(window);
            // if there a no more alerts left
            if (!cons_has_alerts()) {
                // dont highlight console (no news there)
                ProfWin* conswin = wins_get_console();
                status_bar_active(1, conswin->type, "console");
            }
        }
    }
}

ProfWin*
wins_get_by_num(int i)
{
    return g_hash_table_lookup(windows, GINT_TO_POINTER(i));
}

ProfWin*
wins_get_by_string(const char* str)
{
    if (g_strcmp0(str, "console") == 0) {
        ProfWin* conswin = wins_get_console();
        return conswin;
    }

    if (g_strcmp0(str, "xmlconsole") == 0) {
        ProfXMLWin* xmlwin = wins_get_xmlconsole();
        return (ProfWin*)xmlwin;
    }

    ProfChatWin* chatwin = wins_get_chat(str);
    if (chatwin) {
        return (ProfWin*)chatwin;
    }

    jabber_conn_status_t conn_status = connection_get_status();
    if (conn_status == JABBER_CONNECTED) {
        char* barejid = roster_barejid_from_name(str);
        if (barejid) {
            ProfChatWin* chatwin = wins_get_chat(barejid);
            if (chatwin) {
                return (ProfWin*)chatwin;
            }
        }
    }

    ProfMucWin* mucwin = wins_get_muc(str);
    if (mucwin) {
        return (ProfWin*)mucwin;
    }

    ProfPrivateWin* privwin = wins_get_private(str);
    if (privwin) {
        return (ProfWin*)privwin;
    }

    ProfPluginWin* pluginwin = wins_get_plugin(str);
    if (pluginwin) {
        return (ProfWin*)pluginwin;
    }

    return NULL;
}

ProfWin*
wins_get_next(void)
{
    // get and sort win nums
    GList* keys = g_hash_table_get_keys(windows);
    keys = g_list_sort(keys, _wins_cmp_num);
    GList* curr = keys;

    // find our place in the list
    while (curr) {
        if (current == GPOINTER_TO_INT(curr->data)) {
            break;
        }
        curr = g_list_next(curr);
    }

    // if there is a next window return it
    curr = g_list_next(curr);
    if (curr) {
        int next = GPOINTER_TO_INT(curr->data);
        g_list_free(keys);
        return wins_get_by_num(next);
        // otherwise return the first window (console)
    } else {
        g_list_free(keys);
        return wins_get_console();
    }
}

ProfWin*
wins_get_previous(void)
{
    // get and sort win nums
    GList* keys = g_hash_table_get_keys(windows);
    keys = g_list_sort(keys, _wins_cmp_num);
    GList* curr = keys;

    // find our place in the list
    while (curr) {
        if (current == GPOINTER_TO_INT(curr->data)) {
            break;
        }
        curr = g_list_next(curr);
    }

    // if there is a previous window return it
    curr = g_list_previous(curr);
    if (curr) {
        int previous = GPOINTER_TO_INT(curr->data);
        g_list_free(keys);
        return wins_get_by_num(previous);
        // otherwise return the last window
    } else {
        int new_num = GPOINTER_TO_INT(g_list_last(keys)->data);
        g_list_free(keys);
        return wins_get_by_num(new_num);
    }
}

int
wins_get_num(ProfWin* window)
{
    GList* keys = g_hash_table_get_keys(windows);
    GList* curr = keys;

    while (curr) {
        gconstpointer num_p = curr->data;
        ProfWin* curr_win = g_hash_table_lookup(windows, num_p);
        if (curr_win == window) {
            g_list_free(keys);
            return GPOINTER_TO_INT(num_p);
        }
        curr = g_list_next(curr);
    }

    g_list_free(keys);
    return -1;
}

int
wins_get_current_num(void)
{
    return current;
}

void
wins_close_by_num(int i)
{
    // console cannot be closed
    if (i != 1) {

        // go to console if closing current window
        if (i == current) {
            current = 1;
            ProfWin* window = wins_get_current();
            win_update_virtual(window);
        }

        ProfWin* window = wins_get_by_num(i);
        if (window) {
            // cancel upload proccesses of this window
            http_upload_cancel_processes(window);

            switch (window->type) {
            case WIN_CHAT:
            {
                ProfChatWin* chatwin = (ProfChatWin*)window;
                autocomplete_remove(wins_ac, chatwin->barejid);
                autocomplete_remove(wins_close_ac, chatwin->barejid);

                jabber_conn_status_t conn_status = connection_get_status();
                if (conn_status == JABBER_CONNECTED) {
                    PContact contact = roster_get_contact(chatwin->barejid);
                    if (contact) {
                        const char* nick = p_contact_name(contact);
                        if (nick) {
                            autocomplete_remove(wins_ac, nick);
                            autocomplete_remove(wins_close_ac, nick);
                        }
                    }
                }
                autocomplete_free(window->urls_ac);
                break;
            }
            case WIN_MUC:
            {
                ProfMucWin* mucwin = (ProfMucWin*)window;
                autocomplete_remove(wins_ac, mucwin->roomjid);
                autocomplete_remove(wins_close_ac, mucwin->roomjid);

                if (mucwin->last_msg_timestamp) {
                    g_date_time_unref(mucwin->last_msg_timestamp);
                }
                autocomplete_free(window->urls_ac);
                break;
            }
            case WIN_PRIVATE:
            {
                ProfPrivateWin* privwin = (ProfPrivateWin*)window;
                autocomplete_remove(wins_ac, privwin->fulljid);
                autocomplete_remove(wins_close_ac, privwin->fulljid);
                autocomplete_free(window->urls_ac);
                break;
            }
            case WIN_XML:
            {
                autocomplete_remove(wins_ac, "xmlconsole");
                autocomplete_remove(wins_close_ac, "xmlconsole");
                break;
            }
            case WIN_PLUGIN:
            {
                ProfPluginWin* pluginwin = (ProfPluginWin*)window;
                plugins_close_win(pluginwin->plugin_name, pluginwin->tag);
                autocomplete_remove(wins_ac, pluginwin->tag);
                autocomplete_remove(wins_close_ac, pluginwin->tag);
                break;
            }
            case WIN_CONFIG:
            default:
                break;
            }
        }

        g_hash_table_remove(windows, GINT_TO_POINTER(i));
        status_bar_inactive(i);
    }
}

gboolean
wins_is_current(ProfWin* window)
{
    ProfWin* current_window = wins_get_current();

    if (current_window == window) {
        return TRUE;
    } else {
        return FALSE;
    }
}

ProfWin*
wins_new_xmlconsole(void)
{
    GList* keys = g_hash_table_get_keys(windows);
    int result = _wins_get_next_available_num(keys);
    g_list_free(keys);
    ProfWin* newwin = win_create_xmlconsole();
    g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
    autocomplete_add(wins_ac, "xmlconsole");
    autocomplete_add(wins_close_ac, "xmlconsole");
    return newwin;
}

ProfWin*
wins_new_chat(const char* const barejid)
{
    GList* keys = g_hash_table_get_keys(windows);
    int result = _wins_get_next_available_num(keys);
    g_list_free(keys);
    ProfWin* newwin = win_create_chat(barejid);
    g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);

    autocomplete_add(wins_ac, barejid);
    autocomplete_add(wins_close_ac, barejid);
    PContact contact = roster_get_contact(barejid);
    if (contact) {
        const char* nick = p_contact_name(contact);
        if (nick) {
            autocomplete_add(wins_ac, nick);
            autocomplete_add(wins_close_ac, nick);
        }
    }
    newwin->urls_ac = autocomplete_new();

    return newwin;
}

ProfWin*
wins_new_muc(const char* const roomjid)
{
    GList* keys = g_hash_table_get_keys(windows);
    int result = _wins_get_next_available_num(keys);
    g_list_free(keys);
    ProfWin* newwin = win_create_muc(roomjid);
    g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
    autocomplete_add(wins_ac, roomjid);
    autocomplete_add(wins_close_ac, roomjid);
    newwin->urls_ac = autocomplete_new();

    return newwin;
}

ProfWin*
wins_new_config(const char* const roomjid, DataForm* form, ProfConfWinCallback submit, ProfConfWinCallback cancel, const void* userdata)
{
    GList* keys = g_hash_table_get_keys(windows);
    int result = _wins_get_next_available_num(keys);
    g_list_free(keys);
    ProfWin* newwin = win_create_config(roomjid, form, submit, cancel, userdata);
    g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);

    return newwin;
}

ProfWin*
wins_new_private(const char* const fulljid)
{
    GList* keys = g_hash_table_get_keys(windows);
    int result = _wins_get_next_available_num(keys);
    g_list_free(keys);
    ProfWin* newwin = win_create_private(fulljid);
    g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
    autocomplete_add(wins_ac, fulljid);
    autocomplete_add(wins_close_ac, fulljid);
    newwin->urls_ac = autocomplete_new();

    return newwin;
}

ProfWin*
wins_new_plugin(const char* const plugin_name, const char* const tag)
{
    GList* keys = g_hash_table_get_keys(windows);
    int result = _wins_get_next_available_num(keys);
    g_list_free(keys);
    ProfWin* newwin = win_create_plugin(plugin_name, tag);
    g_hash_table_insert(windows, GINT_TO_POINTER(result), newwin);
    autocomplete_add(wins_ac, tag);
    autocomplete_add(wins_close_ac, tag);
    return newwin;
}

gboolean
wins_do_notify_remind(void)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (win_notify_remind(window)) {
            g_list_free(values);
            return TRUE;
        }
        curr = g_list_next(curr);
    }
    g_list_free(values);
    return FALSE;
}

int
wins_get_total_unread(void)
{
    int result = 0;
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        result += win_unread(window);
        curr = g_list_next(curr);
    }
    g_list_free(values);
    return result;
}

void
wins_resize_all(void)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;
    while (curr) {
        ProfWin* window = curr->data;
        win_resize(window);
        curr = g_list_next(curr);
    }
    g_list_free(values);

    ProfWin* current_win = wins_get_current();
    win_update_virtual(current_win);
}

void
wins_hide_subwin(ProfWin* window)
{
    win_hide_subwin(window);

    ProfWin* current_win = wins_get_current();
    win_refresh_without_subwin(current_win);
}

void
wins_show_subwin(ProfWin* window)
{
    win_show_subwin(window);

    // only mucwin and console have occupants/roster subwin
    if (window->type != WIN_MUC && window->type != WIN_CONSOLE) {
        return;
    }

    ProfWin* current_win = wins_get_current();
    win_refresh_with_subwin(current_win);
}

ProfXMLWin*
wins_get_xmlconsole(void)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_XML) {
            ProfXMLWin* xmlwin = (ProfXMLWin*)window;
            assert(xmlwin->memcheck == PROFXMLWIN_MEMCHECK);
            g_list_free(values);
            return xmlwin;
        }
        curr = g_list_next(curr);
    }

    g_list_free(values);
    return NULL;
}

GSList*
wins_get_chat_recipients(void)
{
    GSList* result = NULL;
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type == WIN_CHAT) {
            ProfChatWin* chatwin = (ProfChatWin*)window;
            result = g_slist_append(result, chatwin->barejid);
        }
        curr = g_list_next(curr);
    }
    g_list_free(values);
    return result;
}

GSList*
wins_get_prune_wins(void)
{
    GSList* result = NULL;
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (win_unread(window) == 0 && window->type != WIN_MUC && window->type != WIN_CONFIG && window->type != WIN_XML && window->type != WIN_CONSOLE) {
            result = g_slist_append(result, window);
        }
        curr = g_list_next(curr);
    }
    g_list_free(values);
    return result;
}

void
wins_lost_connection(void)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type != WIN_CONSOLE) {
            win_println(window, THEME_ERROR, "-", "Lost connection.");

            // if current win, set current_win_dirty
            if (wins_is_current(window)) {
                win_update_virtual(window);
            }
        }
        curr = g_list_next(curr);
    }
    g_list_free(values);
}

void
wins_reestablished_connection(void)
{
    GList* values = g_hash_table_get_values(windows);
    GList* curr = values;

    while (curr) {
        ProfWin* window = curr->data;
        if (window->type != WIN_CONSOLE) {
            win_println(window, THEME_TEXT, "-", "Connection re-established.");

            // if current win, set current_win_dirty
            if (wins_is_current(window)) {
                win_update_virtual(window);
            }
        }
        curr = g_list_next(curr);
    }
    g_list_free(values);
}

void
wins_swap(int source_win, int target_win)
{
    ProfWin* source = g_hash_table_lookup(windows, GINT_TO_POINTER(source_win));
    ProfWin* console = wins_get_console();

    if (source) {
        ProfWin* target = g_hash_table_lookup(windows, GINT_TO_POINTER(target_win));

        // target window empty
        if (target == NULL) {
            g_hash_table_steal(windows, GINT_TO_POINTER(source_win));
            g_hash_table_insert(windows, GINT_TO_POINTER(target_win), source);
            status_bar_inactive(source_win);
            char* identifier = win_get_tab_identifier(source);
            if (win_unread(source) > 0) {
                status_bar_new(target_win, source->type, identifier);
            } else {
                status_bar_active(target_win, source->type, identifier);
            }
            free(identifier);
            if (wins_get_current_num() == source_win) {
                wins_set_current_by_num(target_win);
                ui_focus_win(console);
            }

            // target window occupied
        } else {
            g_hash_table_steal(windows, GINT_TO_POINTER(source_win));
            g_hash_table_steal(windows, GINT_TO_POINTER(target_win));
            g_hash_table_insert(windows, GINT_TO_POINTER(source_win), target);
            g_hash_table_insert(windows, GINT_TO_POINTER(target_win), source);
            char* source_identifier = win_get_tab_identifier(source);
            char* target_identifier = win_get_tab_identifier(target);
            if (win_unread(source) > 0) {
                status_bar_new(target_win, source->type, source_identifier);
            } else {
                status_bar_active(target_win, source->type, source_identifier);
            }
            if (win_unread(target) > 0) {
                status_bar_new(source_win, target->type, target_identifier);
            } else {
                status_bar_active(source_win, target->type, target_identifier);
            }
            free(source_identifier);
            free(target_identifier);
            if ((wins_get_current_num() == source_win) || (wins_get_current_num() == target_win)) {
                ui_focus_win(console);
            }
        }
    }
}

static int
_wins_cmp_num(gconstpointer a, gconstpointer b)
{
    int real_a = GPOINTER_TO_INT(a);
    int real_b = GPOINTER_TO_INT(b);

    if (real_a == 0) {
        real_a = 10;
    }

    if (real_b == 0) {
        real_b = 10;
    }

    if (real_a < real_b) {
        return -1;
    } else if (real_a == real_b) {
        return 0;
    } else {
        return 1;
    }
}

static int
_wins_get_next_available_num(GList* used)
{
    // only console used
    if (g_list_length(used) == 1) {
        return 2;
    } else {
        GList* sorted = NULL;
        GList* curr = used;
        while (curr) {
            sorted = g_list_insert_sorted(sorted, curr->data, _wins_cmp_num);
            curr = g_list_next(curr);
        }

        int result = 0;
        int last_num = 1;
        curr = sorted;
        // skip console
        curr = g_list_next(curr);
        while (curr) {
            int curr_num = GPOINTER_TO_INT(curr->data);

            if (((last_num != 9) && ((last_num + 1) != curr_num)) || ((last_num == 9) && (curr_num != 0))) {
                result = last_num + 1;
                if (result == 10) {
                    result = 0;
                }
                g_list_free(sorted);
                return (result);

            } else {
                last_num = curr_num;
                if (last_num == 0) {
                    last_num = 10;
                }
            }
            curr = g_list_next(curr);
        }
        result = last_num + 1;
        if (result == 10) {
            result = 0;
        }

        g_list_free(sorted);
        return result;
    }
}

gboolean
wins_tidy(void)
{
    gboolean tidy_required = FALSE;
    // check for gaps
    GList* keys = g_hash_table_get_keys(windows);
    keys = g_list_sort(keys, _wins_cmp_num);

    // get last used
    GList* last = g_list_last(keys);
    int last_num = GPOINTER_TO_INT(last->data);

    // find first free num TODO - Will sort again
    int next_available = _wins_get_next_available_num(keys);

    // found gap (next available before last window)
    if (_wins_cmp_num(GINT_TO_POINTER(next_available), GINT_TO_POINTER(last_num)) < 0) {
        tidy_required = TRUE;
    }

    if (tidy_required) {
        status_bar_set_all_inactive();
        GHashTable* new_windows = g_hash_table_new_full(g_direct_hash,
                                                        g_direct_equal, NULL, (GDestroyNotify)win_free);

        int num = 1;
        GList* curr = keys;
        while (curr) {
            ProfWin* window = g_hash_table_lookup(windows, curr->data);
            char* identifier = win_get_tab_identifier(window);
            g_hash_table_steal(windows, curr->data);
            if (num == 10) {
                g_hash_table_insert(new_windows, GINT_TO_POINTER(0), window);
                if (win_unread(window) > 0) {
                    status_bar_new(0, window->type, identifier);
                } else {
                    status_bar_active(0, window->type, identifier);
                }
            } else {
                g_hash_table_insert(new_windows, GINT_TO_POINTER(num), window);
                if (win_unread(window) > 0) {
                    status_bar_new(num, window->type, identifier);
                } else {
                    status_bar_active(num, window->type, identifier);
                }
            }
            free(identifier);
            num++;
            curr = g_list_next(curr);
        }

        g_hash_table_destroy(windows);
        windows = new_windows;
        current = 1;
        ProfWin* console = wins_get_console();
        ui_focus_win(console);
        g_list_free(keys);
        return TRUE;
    } else {
        g_list_free(keys);
        return FALSE;
    }
}

GSList*
wins_create_summary(gboolean unread)
{
    if (unread && wins_get_total_unread() == 0) {
        return NULL;
    }

    GSList* result = NULL;

    GList* keys = g_hash_table_get_keys(windows);
    keys = g_list_sort(keys, _wins_cmp_num);
    GList* curr = keys;

    while (curr) {
        ProfWin* window = g_hash_table_lookup(windows, curr->data);
        if (!unread || (unread && win_unread(window) > 0)) {
            GString* line = g_string_new("");

            int ui_index = GPOINTER_TO_INT(curr->data);
            char* winstring = win_to_string(window);
            if (!winstring) {
                g_string_free(line, TRUE);
                continue;
            }

            g_string_append_printf(line, "%d: %s", ui_index, winstring);
            free(winstring);

            result = g_slist_append(result, strdup(line->str));
            g_string_free(line, TRUE);
        }

        curr = g_list_next(curr);
    }

    g_list_free(keys);

    return result;
}

char*
win_autocomplete(const char* const search_str, gboolean previous, void* context)
{
    return autocomplete_complete(wins_ac, search_str, TRUE, previous);
}

char*
win_close_autocomplete(const char* const search_str, gboolean previous, void* context)
{
    return autocomplete_complete(wins_close_ac, search_str, TRUE, previous);
}

void
win_reset_search_attempts(void)
{
    autocomplete_reset(wins_ac);
}

void
win_close_reset_search_attempts(void)
{
    autocomplete_reset(wins_close_ac);
}

void
wins_destroy(void)
{
    g_hash_table_destroy(windows);
    autocomplete_free(wins_ac);
    autocomplete_free(wins_close_ac);
}

ProfWin*
wins_get_next_unread(void)
{
    // get and sort win nums
    GList* values = g_hash_table_get_values(windows);
    values = g_list_sort(values, _wins_cmp_num);
    GList* curr = values;

    while (curr) {
        if (current == GPOINTER_TO_INT(curr->data)) {
            break;
        }

        ProfWin* window = curr->data;
        if (win_unread(window) > 0) {
            g_list_free(values);
            return window;
        }

        curr = g_list_next(curr);
    }

    g_list_free(values);
    return NULL;
}

void
wins_add_urls_ac(const ProfWin* const win, const ProfMessage* const message)
{
    GRegex* regex;
    GMatchInfo* match_info;

    regex = g_regex_new("(https?|aesgcm)://\\S+", 0, 0, NULL);
    g_regex_match(regex, message->plain, 0, &match_info);

    while (g_match_info_matches(match_info)) {
        gchar* word = g_match_info_fetch(match_info, 0);

        autocomplete_add_reverse(win->urls_ac, word);
        // for people who run profanity a long time, we don't want to waste a lot of memory
        autocomplete_remove_older_than_max_reverse(win->urls_ac, 20);

        g_free(word);
        g_match_info_next(match_info, NULL);
    }

    g_match_info_free(match_info);
    g_regex_unref(regex);
}

char*
wins_get_url(const char* const search_str, gboolean previous, void* context)
{
    ProfWin* win = (ProfWin*)context;

    return autocomplete_complete(win->urls_ac, search_str, FALSE, previous);
}