]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into TimePath/guide
authorTimePath <andrew.hardaker1995@gmail.com>
Mon, 24 Aug 2015 03:03:57 +0000 (13:03 +1000)
committerTimePath <andrew.hardaker1995@gmail.com>
Mon, 24 Aug 2015 03:12:06 +0000 (13:12 +1000)
12 files changed:
1  2 
qcsrc/common/buffs.qh
qcsrc/common/items/item.qh
qcsrc/common/mapinfo.qh
qcsrc/common/nades.qh
qcsrc/common/oo.qh
qcsrc/common/weapons/all.qh
qcsrc/menu/xonotic/datasource.qc
qcsrc/menu/xonotic/dialog_media_guide.qc
qcsrc/menu/xonotic/dialog_media_guide_description.qc
qcsrc/menu/xonotic/dialog_media_guide_entries.qc
qcsrc/menu/xonotic/dialog_media_guide_topics.qc
qcsrc/menu/xonotic/dialog_multiplayer_media.qc

index 87dff12a0c4b54d78d331a3cec3995a8292ed17c,ddf5722816dfe1c989c252a631e49f93abcaf31e..e0bda4c677c8c6e025eb720df1d577de330f7817
@@@ -33,12 -33,8 +33,11 @@@ CLASS(Buff, Pickup
        ATTRIB(Buff, m_prettyName, string, "Buff")
        ATTRIB(Buff, m_skin, int, 0)
        ATTRIB(Buff, m_sprite, string, "")
-       METHOD(Buff, display, void(entity this, void(string name, string icon) returns))
-       void Buff_display(entity this, void(string name, string icon) returns) {
++      METHOD(Buff, display, void(entity this, void(string name, string icon) returns)) {
 +              returns(this.m_prettyName, sprintf("/gfx/hud/%s/buff_%s", cvar_string("menu_skin"), this.m_name));
 +      }
  #ifdef SVQC
-       METHOD(Buff, m_time, float(entity))
+       METHOD(Buff, m_time, float(entity));
        float Buff_m_time(entity this) { return cvar(strcat("g_buffs_", this.netname, "_time")); }
  #endif
  ENDCLASS(Buff)
index a976da5caab83193f82f358c23069acff8e67e5c,0b17f35b2c1277fb60ebb687893da9bec20a4ff0..16db39728561035115b66942a4620ae25b51458a
@@@ -5,14 -5,8 +5,12 @@@
  /** If you register a new item, make sure to add it to all.inc */
  CLASS(GameItem, Object)
      ATTRIB(GameItem, m_id, int, 0)
 -    METHOD(GameItem, show, void(entity this));
 -    void GameItem_show(entity this) { print("A game item\n"); }
 +    ATTRIB(GameItem, m_name, string, string_null)
 +    ATTRIB(GameItem, m_icon, string, string_null)
-     METHOD(GameItem, display, void(entity this, void(string name, string icon) returns))
-     void GameItem_display(entity this, void(string name, string icon) returns) {
++    METHOD(GameItem, display, void(entity this, void(string name, string icon) returns)) {
 +        returns(this.m_name, this.m_icon ? sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon) : string_null);
 +    }
-     METHOD(GameItem, show, void(entity this))
-     void GameItem_show(entity this) { print("A game item\n"); }
++    METHOD(GameItem, show, void(entity this)) { print("A game item\n"); }
      void ITEM_HANDLE(Show, entity this) { this.show(this); }
  ENDCLASS(GameItem)
  
index b8efb9d90c1275f88b29ec148171515c509cf6fe,e9979379c9dd67ba8813d9fe0474886882516438..19799df740069178ccc37aed0d8385a0c6ba169b
@@@ -19,13 -19,6 +19,13 @@@ CLASS(Gametype, Object
      ATTRIB(Gametype, model2, string, string_null)
      /** game type description */
      ATTRIB(Gametype, gametype_description, string, string_null)
-     METHOD(Gametype, describe, string(entity this))
-     string Gametype_describe(entity this) { return this.gametype_description; }
 +
-     METHOD(Gametype, display, void(entity this, void(string name, string icon) returns))
-     void Gametype_display(entity this, void(string, string) returns) { returns(this.message, strcat("gametype_", this.mdl)); }
++    METHOD(Gametype, describe, string(entity this)) { return this.gametype_description; }
 +
++    METHOD(Gametype, display, void(entity this, void(string name, string icon) returns)) {
++        returns(this.message, strcat("gametype_", this.mdl));
++    }
 +
      CONSTRUCTOR(Gametype, string hname, string sname, string g_name, bool gteamplay, string defaults, string gdescription)
      {
          CONSTRUCT(Gametype);
index b876c86c9c097f28022f721ee483e12daadf7098,01eb7fff2c8e2730fa1a8eca1062571c4467c977..50f2e57e8067c212e34239305d6e513fa7ec685b
@@@ -37,10 -36,6 +37,9 @@@ CLASS(Nade, Object
      ATTRIB(Nade, m_icon, string, "nade_normal")
      ATTRIBARRAY(Nade, m_projectile, int, 2)
      ATTRIBARRAY(Nade, m_trail, string, 2)
-     METHOD(Nade, display, void(entity this, void(string name, string icon) returns))
-     void Nade_display(entity this, void(string name, string icon) returns) {
++    METHOD(Nade, display, void(entity this, void(string name, string icon) returns)) {
 +        returns(this.m_name, sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.m_icon));
 +    }
  ENDCLASS(Nade)
  
  REGISTER_NADE(NULL);
index 3cbd8450dfa232555dc384c50f540424acfe5277,add63b0cd819c2ec728d55a7b4c92eac0cb45fd3..3c7237ef408adb7ea2237536a175f4d19476a126
@@@ -116,21 -115,7 +117,21 @@@ STATIC_INIT(RegisterClasses) { Register
  #define spawn_static(this)
  #define spawn_1(this)
  #define _vtbl NULL
 -CLASS(Object, ); ENDCLASS(Object)
 +CLASS(Object, );
-     METHOD(Object, describe, string(entity this))
-     string Object_describe(entity this) {
++    METHOD(Object, describe, string(entity this)) {
 +        string s = _("No description");
 +        if (cvar("developer")) {
 +            for (int i = 0, n = numentityfields(); i < n; ++i) {
 +                string value = getentityfieldstring(i, this);
 +                if (value != "") s = sprintf("%s\n%s = %s", s, entityfieldname(i), value);
 +            }
 +      }
 +        return s;
 +    }
-     METHOD(Object, display, void(entity this, void(string name, string icon) returns))
-     void Object_display(entity this, void(string name, string icon) returns) { returns(sprintf("entity %i", this), "nopreview_map"); }
++    METHOD(Object, display, void(entity this, void(string name, string icon) returns)) {
++        returns(sprintf("entity %i", this), "nopreview_map");
++    }
 +ENDCLASS(Object)
  #undef spawn_static
  #undef spawn_1
  #undef _vtbl
index e1ff03941c6453c9b8b684dbd4b8d1e94b0fc564,ac839a53a047f6006d5b16e9f41403fb61199033..4b6a714499ecf1a90ab12d4c631b452ee2f26253
@@@ -183,11 -183,6 +183,10 @@@ CLASS(Weapon, Object
      /** M: wepname   : human readable name */
      ATTRIB(Weapon, message, string, "AOL CD Thrower");
  
-       METHOD(Weapon, display, void(entity this, void(string name, string icon) returns))
-       void Weapon_display(entity this, void(string name, string icon) returns) {
++      METHOD(Weapon, display, void(entity this, void(string name, string icon) returns)) {
 +              returns(this.message, this.model2 ? sprintf("/gfx/hud/%s/%s", cvar_string("menu_skin"), this.model2) : string_null);
 +      }
 +
        CONSTRUCTOR(Weapon,
                bool(int) function,
                .int ammotype,
index 3c484e3ceb1ee0e6511cb5901d6df1450f22ac7e,0000000000000000000000000000000000000000..db3b77a016cf6abaf3d7d1a86d16653051d97afb
mode 100644,000000..100644
--- /dev/null
@@@ -1,23 -1,0 +1,23 @@@
-     METHOD(DataSource, getEntry, entity(int i, void(string name, string icon) returns))
 +#ifndef DATASOURCE_H
 +#define DATASOURCE_H
 +CLASS(DataSource, Object)
 +    /**
 +     * get entry `i` passing `name` and `icon` through `returns` if it is not null
 +     * returns `DataSource_false` if out of bounds
 +     * otherwise returns an entity or `DataSource_true`
 +     */
-     METHOD(DataSource, indexOf, int(string find))
++    METHOD(DataSource, getEntry, entity(int i, void(string name, string icon) returns));
 +    /** return the index of the first match for `find`. optional */
-     METHOD(DataSource, reload, int(string filter))
++    METHOD(DataSource, indexOf, int(string find));
 +    /** reload all entries matching `filter` returning how many matches were found */
-     METHOD(DataSource, destroy, void(entity))
++    METHOD(DataSource, reload, int(string filter));
 +    /** cleanup on shutdown. optional */
++    METHOD(DataSource, destroy, void(entity));
 +    entity DataSource_true;
 +    entity DataSource_false;
 +    INIT_STATIC(DataSource) {
 +        DataSource_true = NEW(Object);
 +        DataSource_false = NULL;
 +    }
 +ENDCLASS(DataSource)
 +#endif
index e16a48780bcfd6118103155e79be1c523ec27df0,0000000000000000000000000000000000000000..1e208ce1b638563e7cec0b601370ea5faeea705c
mode 100644,000000..100644
--- /dev/null
@@@ -1,254 -1,0 +1,233 @@@
-     METHOD(TopicSource, getEntry, entity(int, void(string, string)))
-     entity TopicSource_getEntry(int i, void(string, string) returns) {
 +#ifndef DIALOG_MEDIA_GUIDE_H
 +#define DIALOG_MEDIA_GUIDE_H
 +#include "datasource.qc"
 +
 +#define TOPICS(X) \
 +    X(NEW(GametypeSource),  _("Gametypes"), "gametype_dm") \
 +    X(NEW(WeaponSource),    _("Weapons"),   "gametype_ka") \
 +    X(NEW(ItemSource),      _("Items"),     "gametype_kh") \
 +    X(NEW(BuffSource),      _("Buffs"),     "gametype_dom") \
 +    X(NEW(NadeSource),      _("Nades"),     "gametype_ft") \
 +    X(NEW(MapSource),       _("Maps"),      "gametype_ctf") \
 +    if (cvar("developer")) X(NEW(DebugSource), _("Debug"), "gametype_ons") \
 +    /**/
 +CLASS(TopicSource, DataSource)
-     METHOD(TopicSource, reload, int(string))
-     int TopicSource_reload(string filter) {
++    METHOD(TopicSource, getEntry, entity(int i, void(string, string) returns)) {
 +        int idx = 0;
 +        #define TOPIC(src, name, icon) if (idx++ == i) { if (returns) returns(name, icon); return DataSource_true; }
 +        TOPICS(TOPIC);
 +        #undef TOPIC
 +        if (returns) returns("undefined", "undefined");
 +        return DataSource_false;
 +    }
-     METHOD(DebugSource, getEntry, entity(int, void(string, string)))
-     entity DebugSource_getEntry(int i, void(string, string) returns) {
++    METHOD(TopicSource, reload, int(string filter)) {
 +        int n = 0;
 +        #define TOPIC(src, name, icon) n++;
 +        TOPICS(TOPIC);
 +        #undef TOPIC
 +        return n;
 +    }
 +ENDCLASS(TopicSource)
 +
 +CLASS(DebugSource, DataSource)
 +    .entity nextdebug;
 +    entity find_debug() {
 +        entity head = NULL, tail = NULL;
 +        for (entity it = NULL; (it = nextent(it)); ) {
 +            if (!it.instanceOfObject) continue;
 +            if (it.instanceOfItem) continue;
 +            if (it.classname == "Object") continue;
 +            if (it.classname == "vtbl") continue;
 +            if (!tail) {
 +                tail = head = it;
 +            } else {
 +                tail.nextdebug = it;
 +                tail = it;
 +            }
 +        }
 +        return head;
 +    }
 +    string DebugSource_activeFilter = "";
-     METHOD(DebugSource, reload, int(string))
-     int DebugSource_reload(string filter) {
++    METHOD(DebugSource, getEntry, entity(int i, void(string, string) returns)) {
 +        int idx = 0;
 +        entity e;
 +        for (e = find_debug(); e; e = e.nextdebug) {
 +            if (strstrofs(sprintf("entity %i", e), DebugSource_activeFilter, 0) < 0) continue;
 +            if (idx++ == i) break;
 +        }
 +        if (returns) e.display(e, returns);
 +        return e;
 +    }
-     METHOD(GametypeSource, getEntry, entity(int, void(string, string)))
-     entity GametypeSource_getEntry(int i, void(string, string) returns) {
++    METHOD(DebugSource, reload, int(string filter)) {
 +        DebugSource_activeFilter = filter;
 +        int idx = 0;
 +        entity e;
 +        for (e = find_debug(); e; e = e.nextdebug) {
 +            if (strstrofs(sprintf("entity %i", e), DebugSource_activeFilter, 0) < 0) continue;
 +            idx++;
 +        }
 +        return idx;
 +    }
 +ENDCLASS(DebugSource)
 +
 +#include "../../common/mapinfo.qh"
 +CLASS(GametypeSource, DataSource)
-     METHOD(GametypeSource, reload, int(string))
-     int GametypeSource_reload(string filter) { return MAPINFO_TYPE_COUNT; }
++    METHOD(GametypeSource, getEntry, entity(int i, void(string, string) returns)) {
 +        entity e = MAPINFO_TYPES[i];
 +        if (returns) e.display(e, returns);
 +        return e;
 +    }
-     METHOD(ItemSource, getEntry, entity(int, void(string, string)))
-     entity ItemSource_getEntry(int i, void(string, string) returns) {
++    METHOD(GametypeSource, reload, int(string filter)) { return MAPINFO_TYPE_COUNT; }
 +ENDCLASS(GametypeSource)
 +
 +#include "../../common/items/all.qh"
 +CLASS(ItemSource, DataSource)
-     METHOD(ItemSource, reload, int(string))
-     int ItemSource_reload(string filter) { return ITEM_COUNT; }
++    METHOD(ItemSource, getEntry, entity(int i, void(string, string) returns)) {
 +        entity e = ITEMS[i];
 +        if (returns) e.display(e, returns);
 +        return e;
 +    }
-     METHOD(BuffSource, getEntry, entity(int, void(string, string)))
-     entity BuffSource_getEntry(int i, void(string, string) returns) {
++    METHOD(ItemSource, reload, int(string filter)) { return ITEM_COUNT; }
 +ENDCLASS(ItemSource)
 +
 +#include "../../common/buffs.qh"
 +CLASS(BuffSource, DataSource)
-     METHOD(BuffSource, reload, int(string))
-     int BuffSource_reload(string filter) { return BUFFS_COUNT; }
++    METHOD(BuffSource, getEntry, entity(int i, void(string, string) returns)) {
 +        entity e = BUFFS[i];
 +        if (returns) e.display(e, returns);
 +        return e;
 +    }
-     METHOD(NadeSource, getEntry, entity(int, void(string, string)))
-     entity NadeSource_getEntry(int i, void(string, string) returns) {
++    METHOD(BuffSource, reload, int(string filter)) { return BUFFS_COUNT; }
 +ENDCLASS(BuffSource)
 +
 +#include "../../common/nades.qh"
 +CLASS(NadeSource, DataSource)
-     METHOD(NadeSource, reload, int(string))
-     int NadeSource_reload(string filter) { return NADES_COUNT; }
++    METHOD(NadeSource, getEntry, entity(int i, void(string, string) returns)) {
 +        entity e = NADES[i];
 +        if (returns) e.display(e, returns);
 +        return e;
 +    }
-     METHOD(WeaponSource, getEntry, entity(int, void(string, string)))
-     entity WeaponSource_getEntry(int i, void(string, string) returns) {
++    METHOD(NadeSource, reload, int(string filter)) { return NADES_COUNT; }
 +ENDCLASS(NadeSource)
 +
 +#include "../../common/weapons/all.qh"
 +CLASS(WeaponSource, DataSource)
-     METHOD(WeaponSource, reload, int(string))
-     int WeaponSource_reload(string filter) { return WEP_COUNT; }
++    METHOD(WeaponSource, getEntry, entity(int i, void(string, string) returns)) {
 +        entity e = weapon_info[i];
 +        if (returns) e.display(e, returns);
 +        return e;
 +    }
-     METHOD(MapSource, getEntry, entity(int, void(string, string)))
-     entity MapSource_getEntry(int i, void(string, string) returns)
-     {
++    METHOD(WeaponSource, reload, int(string filter)) { return WEP_COUNT; }
 +ENDCLASS(WeaponSource)
 +
 +CLASS(MapSource, DataSource)
-     METHOD(MapSource, indexOf, int(string))
-     int MapSource_indexOf(string s)
-     {
++    METHOD(MapSource, getEntry, entity(int i, void(string, string) returns)) {
 +        if (!MapInfo_Get_ByID(i)) return DataSource_false;
 +        string path = strcat("/maps/", MapInfo_Map_bspname);
 +        string img = draw_PictureSize(path) ? path : "nopreview_map";
 +        if (returns) returns(MapInfo_Map_titlestring, img);
 +        MapInfo_ClearTemps();
 +        return DataSource_true;
 +    }
-     METHOD(MapSource, reload, int(string))
-     int MapSource_reload(string s)
-     {
++    METHOD(MapSource, indexOf, int(string s)) {
 +        MapInfo_FindName(s);
 +        return MapInfo_FindName_firstResult;
 +    }
-     METHOD(MapSource, destroy, void(entity))
-     void MapSource_destroy(entity this) { MapInfo_Shutdown(); }
++    METHOD(MapSource, reload, int(string s)) {
 +        MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, 0, 0);
 +        if (s) MapInfo_FilterString(s);
 +        return MapInfo_count;
 +    }
-     METHOD(XonoticGuideTab, fill, void(entity))
-     METHOD(XonoticGuideTab, topicChangeNotify, void(entity, entity))
-     METHOD(XonoticGuideTab, entryChangeNotify, void(entity, entity))
++    METHOD(MapSource, destroy, void(entity this)) { MapInfo_Shutdown(); }
 +ENDCLASS(MapSource)
 +
 +#include "dialog_media_guide_topics.qc"
 +#include "dialog_media_guide_entries.qc"
 +#include "dialog_media_guide_description.qc"
 +#include "tab.qc"
 +CLASS(XonoticGuideTab, XonoticTab)
 +    ATTRIB(XonoticGuideTab, rows, float, 21)
 +    ATTRIB(XonoticGuideTab, columns, float, 6)
 +      ATTRIB(XonoticGuideTab, intendedWidth, float, 1)
++    METHOD(XonoticGuideTab, fill, void(entity));
++    METHOD(XonoticGuideTab, topicChangeNotify, void(entity, entity));
++    METHOD(XonoticGuideTab, entryChangeNotify, void(entity, entity));
 +
 +    ATTRIB(XonoticGuideTab, topicList, entity, NEW(XonoticTopicList, NEW(TopicSource)))
 +    ATTRIB(XonoticGuideTab, entryList, entity, NEW(XonoticEntryList, NULL))
 +    ATTRIB(XonoticGuideTab, descriptionPane, entity, NEW(XonoticGuideDescription))
 +
 +    INIT(XonoticGuideTab) {
 +        this.configureDialog(this);
 +    }
 +ENDCLASS(XonoticGuideTab)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +void XonoticGuideTab_fill(entity this)
 +{
 +    entity topics = this.topicList;
 +        topics.onChange = XonoticGuideTab_topicChangeNotify;
 +        topics.onChangeEntity = this;
 +    entity entries = this.entryList;
 +        entries.onChange = XonoticGuideTab_entryChangeNotify;
 +        entries.onChangeEntity = this;
 +    entity filter = entries.stringFilterBox = makeXonoticInputBox(false, string_null);
 +        filter.keyDown = MapList_StringFilterBox_keyDown;
 +        filter.onChange = MapList_StringFilterBox_Change;
 +        filter.onChangeEntity = entries;
 +    entries.controlledTextbox = filter;
 +    entity description = this.descriptionPane;
 +
 +    int
 +    col = 0, width = 1.5;
 +    this.gotoRC(this, 0, col);
 +        this.TD(this, 1, width, makeXonoticHeaderLabel(_("Topic")));
 +    this.TR(this);
 +        this.TD(this, this.rows - 1, width, topics);
 +
 +    col += width, width = 2;
 +    this.gotoRC(this, 0, col); this.setFirstColumn(this, this.currentColumn);
 +        this.TD(this, 1, width, makeXonoticHeaderLabel(_("Entry")));
 +    this.TR(this);
 +        this.TD(this, this.rows - 1 - 1, width, entries);
 +    this.gotoRC(this, this.rows - 1, col);
 +        this.TD(this, 1, 0.3, makeXonoticTextLabel(0, _("Filter:")));
 +        this.TD(this, 1, width - 0.3, filter);
 +
 +    col += width, width = 2.5;
 +    this.gotoRC(this, 0, col); this.setFirstColumn(this, this.currentColumn);
 +        this.TD(this, 1, width, makeXonoticHeaderLabel(_("Description")));
 +    this.TR(this);
 +        this.TD(this, this.rows - 1, width, description);
 +
 +    this.topicChangeNotify(topics, this);
 +}
 +
 +void XonoticGuideTab_topicChangeNotify(entity, entity this)
 +{
 +    entity topics = this.topicList;
 +    entity entries = this.entryList;
 +    int i = topics.selectedItem;
 +    int idx = 0;
 +    entity found = NULL;
 +    #define TOPIC(src, name, icon) if (idx++ == i) { static entity e; if (!e) e = src; found = e; break; }
 +    do { TOPICS(TOPIC); } while (0);
 +    #undef TOPIC
 +    entries.source = found;
 +    entries.refilter(entries);
 +    entries.setSelected(entries, 0);
 +}
 +
 +void XonoticGuideTab_entryChangeNotify(entity, entity this)
 +{
 +    entity desc = this.descriptionPane;
 +    entity entries = this.entryList;
 +    entity e = entries.source.getEntry(entries.selectedItem, func_null);
 +    string s = e.describe(e);
 +    if (cvar("developer")) { s = sprintf("entity %i\n%s", e, s); }
 +    desc.setDescription(desc, s);
 +}
 +
 +#endif
index 6b1e596cd71c1041d64eb7dbd9bb18ae59e7a0ad,0000000000000000000000000000000000000000..cb94cfa53ab3edf745cac4fbfea2f8f57447c7f9
mode 100644,000000..100644
--- /dev/null
@@@ -1,56 -1,0 +1,52 @@@
-     METHOD(XonoticGuideDescription, setDescription, void(entity, string))
 +#ifndef DIALOG_MEDIA_GUIDE_DESCRIPTION_H
 +#define DIALOG_MEDIA_GUIDE_DESCRIPTION_H
 +#include "credits.qc"
 +CLASS(XonoticGuideDescription, XonoticListBox)
 +      ATTRIB(XonoticGuideDescription, rowsPerItem, float, 1)
 +      ATTRIB(XonoticGuideDescription, selectionDoesntMatter, bool, true)
 +
-       METHOD(XonoticGuideDescription, resizeNotify, void(entity, vector, vector, vector, vector))
-     void XonoticGuideDescription_resizeNotify(entity this, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
-     {
++    METHOD(XonoticGuideDescription, setDescription, void(entity, string));
 +    ATTRIB(XonoticGuideDescription, description, string, string_null)
 +
-     METHOD(XonoticGuideDescription, drawListBoxItem, void(entity, int, vector, bool, bool))
-     void XonoticGuideDescription_drawListBoxItem(entity this, int i, vector absSize, bool isSelected, bool isFocused)
-     {
++      METHOD(XonoticGuideDescription, resizeNotify, void(entity this, vector relOrigin, vector relSize, vector absOrigin, vector absSize)) {
 +        super.resizeNotify(this, relOrigin, relSize, absOrigin, absSize);
 +
 +        this.realFontSize_y = this.fontSize / (absSize.y * this.itemHeight);
 +        this.realFontSize_x = this.fontSize / (absSize.x * (1 - this.controlWidth));
 +        this.realUpperMargin = 0.5 * (1 - this.realFontSize.y);
 +        this.setDescription(this, this.description);
 +    }
 +
 +    INIT(XonoticGuideDescription) {
 +        this.configureXonoticListBox(this);
 +    }
 +
 +    ATTRIB(XonoticGuideDescription, descriptionWrapped, string, string_null)
 +    void XonoticGuideDescription_setDescription(entity this, string desc)
 +    {
 +        string current = this.description;
 +        if (current && current != desc) strunzone(current);
 +        this.description = strzone(desc);
 +
 +        string currentWrapped = this.descriptionWrapped;
 +        if (currentWrapped) strunzone(currentWrapped);
 +        string wrapped = "";
 +        for (int i = 0, n = tokenizebyseparator(desc, "\n"); i < n; ++i) {
 +            string line = "";
 +            for (getWrappedLine_remaining = argv(i); getWrappedLine_remaining; ) {
 +                string s = getWrappedLine(1, this.realFontSize, draw_TextWidth_WithColors);
 +                line = sprintf("%s\n%s", line, s);
 +            }
 +            wrapped = strcat(wrapped, line);
 +        }
 +        this.descriptionWrapped = strzone(wrapped);
 +
 +        this.nItems = tokenizebyseparator(wrapped, "\n");
 +    }
 +
++    METHOD(XonoticGuideDescription, drawListBoxItem, void(entity this, int i, vector absSize, bool isSelected, bool isFocused)) {
 +        tokenizebyseparator(this.descriptionWrapped, "\n");
 +        draw_Text(this.realUpperMargin * eY, argv(i), this.realFontSize, '1 1 1', 1, 0);
 +    }
 +ENDCLASS(XonoticGuideDescription)
 +#endif
index 1c2faa7bb1942231460cf910f36e2281dac091bf,0000000000000000000000000000000000000000..b131b61ea85bc856d6bc3a6828c2f25bed225caa
mode 100644,000000..100644
--- /dev/null
@@@ -1,127 -1,0 +1,127 @@@
-     METHOD(XonoticEntryList, drawListBoxItem, void(entity, int, vector, bool, bool))
-     METHOD(XonoticEntryList, keyDown, float(entity, float, float, float))
-     METHOD(XonoticEntryList, refilter, void(entity))
-     METHOD(XonoticEntryList, resizeNotify, void(entity, vector, vector, vector, vector))
-     METHOD(XonoticEntryList, setSelected, void(entity, int))
 +#ifndef DIALOG_MEDIA_GUIDE_ENTRIES_H
 +#define DIALOG_MEDIA_GUIDE_ENTRIES_H
 +#include "datasource.qc"
 +#include "listbox.qc"
 +CLASS(XonoticEntryList, XonoticListBox)
 +    ATTRIB(XonoticEntryList, alphaBG, float, 0)
 +    ATTRIB(XonoticEntryList, itemAbsSize, vector, '0 0 0')
 +    ATTRIB(XonoticEntryList, origin, vector, '0 0 0')
 +    ATTRIB(XonoticEntryList, realFontSize, vector, '0 0 0')
 +    ATTRIB(XonoticEntryList, realUpperMargin1, float, 0)
 +    ATTRIB(XonoticEntryList, realUpperMargin2, float, 0)
 +    ATTRIB(XonoticEntryList, rowsPerItem, float, 4)
 +    ATTRIB(XonoticEntryList, stringFilterBox, entity, NULL)
 +    ATTRIB(XonoticEntryList, stringFilter, string, string_null)
 +    ATTRIB(XonoticEntryList, typeToSearchString, string, string_null)
 +    ATTRIB(XonoticEntryList, typeToSearchTime, float, 0)
 +
++    METHOD(XonoticEntryList, drawListBoxItem, void(entity, int, vector, bool, bool));
++    METHOD(XonoticEntryList, keyDown, float(entity, float, float, float));
++    METHOD(XonoticEntryList, refilter, void(entity));
++    METHOD(XonoticEntryList, resizeNotify, void(entity, vector, vector, vector, vector));
++    METHOD(XonoticEntryList, setSelected, void(entity, int));
 +
 +    ATTRIB(XonoticEntryList, source, DataSource, NULL)
 +
 +    CONSTRUCTOR(XonoticEntryList, DataSource _source) {
 +        CONSTRUCT(XonoticEntryList);
 +        this.source = _source;
 +        this.configureXonoticListBox(this);
 +        this.refilter(this);
 +    }
 +
 +ENDCLASS(XonoticEntryList)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +string XonoticEntryList_cb_name, XonoticEntryList_cb_icon;
 +void XonoticEntryList_cb(string _name, string _icon) {
 +    XonoticEntryList_cb_name = _name;
 +    XonoticEntryList_cb_icon = _icon;
 +}
 +
 +void XonoticEntryList_drawListBoxItem(entity this, int i, vector absSize, bool isSelected, bool isFocused)
 +{
 +    if (!this.source) return;
 +    if (!this.source.getEntry(i, XonoticEntryList_cb)) return;
 +    string name = XonoticEntryList_cb_name;
 +    string icon = XonoticEntryList_cb_icon;
 +    if (isSelected) {
 +        draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 +    } else if (isFocused) {
 +        this.focusedItemAlpha = getFadedAlpha(this.focusedItemAlpha, SKINALPHA_LISTBOX_FOCUSED, SKINFADEALPHA_LISTBOX_FOCUSED);
 +        draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_FOCUSED, this.focusedItemAlpha);
 +    }
 +    vector sz = draw_PictureSize(icon);
 +    if (!sz) sz = '1 1 0';
 +    float szr = sz.x / sz.y;
 +    if (strstrofs(icon, "map", 0) >= 0) szr = 4 / 3;
 +    float asr = this.itemAbsSize.x / this.itemAbsSize.y;
 +    sz.y = 1; sz.x = szr / asr;
 +    draw_Picture('0 0 0', icon, sz, '1 1 1', SKINALPHA_LISTBOX_SELECTED);
 +    string s = draw_TextShortenToWidth(strdecolorize(name), 1 - sz.x - 2 * this.realFontSize.x, 0, this.realFontSize);
 +    draw_Text(this.realUpperMargin1 * eY + (sz.x + 0.5 * this.realFontSize.x) * eX, s, this.realFontSize, '1 1 1', SKINALPHA_TEXT, 0);
 +}
 +
 +float XonoticEntryList_keyDown(entity this, float scan, float ascii, float shift)
 +{
 +    if (this.nItems <= 0) {
 +        return super.keyDown(this, scan, ascii, shift);
 +    } else if ((ascii >= 32 || scan == K_BACKSPACE) && this.source.indexOf) {
 +        string save;
 +        if (scan == K_BACKSPACE) {
 +            save = substring(this.typeToSearchString, 0, strlen(this.typeToSearchString) - 1);
 +        } else {
 +            string ch = chr(ascii);
 +            save = (time > this.typeToSearchTime) ? ch : strcat(this.typeToSearchString, ch);
 +        }
 +        if (this.typeToSearchString) strunzone(this.typeToSearchString);
 +        this.typeToSearchString = strzone(save);
 +        this.typeToSearchTime = time + 0.5;
 +        if (strlen(this.typeToSearchString)) {
 +            int idx = this.source.indexOf(this.typeToSearchString);
 +            if (idx >= 0) this.setSelected(this, idx);
 +        }
 +    } else if (shift & S_CTRL && scan == 'f') {
 +        this.parent.setFocus(this.parent, this.stringFilterBox);
 +    } else if (shift & S_CTRL && scan == 'u') {
 +        this.stringFilterBox.setText(this.stringFilterBox, "");
 +        if (this.stringFilter) strunzone(this.stringFilter);
 +        this.stringFilter = string_null;
 +        this.refilter(this);
 +    }
 +    return super.keyDown(this, scan, ascii, shift);
 +}
 +
 +void XonoticEntryList_refilter(entity this)
 +{
 +    if (!this.source) {
 +        this.nItems = 0;
 +        return;
 +    }
 +    this.nItems = this.source.reload(this.stringFilter);
 +    for (int i = 0, n = this.nItems; i < n; ++i) {
 +        if (this.source.getEntry(i, XonoticEntryList_cb)) {
 +            draw_PreloadPicture(XonoticEntryList_cb_icon);
 +        }
 +    }
 +}
 +
 +void XonoticEntryList_resizeNotify(entity this, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +    this.itemAbsSize = '0 0 0';
 +    super.resizeNotify(this, relOrigin, relSize, absOrigin, absSize);
 +
 +    this.realFontSize_y = this.fontSize / (this.itemAbsSize_y = (absSize.y * this.itemHeight));
 +    this.realFontSize_x = this.fontSize / (this.itemAbsSize_x = (absSize.x * (1 - this.controlWidth)));
 +    this.realUpperMargin1 = 0.5 * (1 - 2.5 * this.realFontSize.y);
 +    this.realUpperMargin2 = this.realUpperMargin1 + 1.5 * this.realFontSize.y;
 +}
 +
 +void XonoticEntryList_setSelected(entity this, int i)
 +{
 +    super.setSelected(this, i);
 +    this.onChange(this, this.onChangeEntity);
 +}
 +#endif
index 7168185ccbe7a7f28bd02809c54277f0e2287cc5,0000000000000000000000000000000000000000..6121deadcb5aa27317d5ba43d3babbef8ac67589
mode 100644,000000..100644
--- /dev/null
@@@ -1,94 -1,0 +1,94 @@@
-     METHOD(XonoticTopicList, clickListBoxItem, void(entity, float, vector))
-     METHOD(XonoticTopicList, drawListBoxItem, void(entity, int, vector, bool, bool))
-     METHOD(XonoticTopicList, keyDown, bool(entity, float, float, float))
-     METHOD(XonoticTopicList, resizeNotify, void(entity, vector, vector, vector, vector))
-     METHOD(XonoticTopicList, setSelected, void(entity, int))
 +#ifndef DIALOG_MEDIA_GUIDE_TOPICS_H
 +#define DIALOG_MEDIA_GUIDE_TOPICS_H
 +#include "datasource.qc"
 +#include "listbox.qc"
 +CLASS(XonoticTopicList, XonoticListBox)
 +    ATTRIB(XonoticTopicList, columnIconOrigin, float, 0)
 +    ATTRIB(XonoticTopicList, columnIconSize, float, 0)
 +    ATTRIB(XonoticTopicList, columnNameOrigin, float, 0)
 +    ATTRIB(XonoticTopicList, columnNameSize, float, 0)
 +    ATTRIB(XonoticTopicList, realFontSize, vector, '0 0 0')
 +    ATTRIB(XonoticTopicList, realUpperMargin, float, 0)
 +    ATTRIB(XonoticTopicList, rowsPerItem, float, 3)
 +
++    METHOD(XonoticTopicList, clickListBoxItem, void(entity, float, vector));
++    METHOD(XonoticTopicList, drawListBoxItem, void(entity, int, vector, bool, bool));
++    METHOD(XonoticTopicList, keyDown, bool(entity, float, float, float));
++    METHOD(XonoticTopicList, resizeNotify, void(entity, vector, vector, vector, vector));
++    METHOD(XonoticTopicList, setSelected, void(entity, int));
 +
 +    ATTRIB(XonoticTopicList, source, DataSource, NULL)
 +
 +    CONSTRUCTOR(XonoticTopicList, DataSource _source) {
 +      CONSTRUCT(XonoticTopicList);
 +      this.source = _source;
 +      this.nItems = _source.reload("");
 +      this.configureXonoticListBox(this);
 +    }
 +ENDCLASS(XonoticTopicList)
 +#endif
 +
 +#ifdef IMPLEMENTATION
 +
 +void XonoticTopicList_clickListBoxItem(entity this, float i, vector where)
 +{
 +    m_play_click_sound(MENU_SOUND_SELECT);
 +}
 +
 +string XonoticTopicList_cb_name, XonoticTopicList_cb_icon;
 +void XonoticTopicList_cb(string _name, string _icon) {
 +    XonoticTopicList_cb_name = _name;
 +    XonoticTopicList_cb_icon = _icon;
 +}
 +
 +void XonoticTopicList_drawListBoxItem(entity this, int i, vector absSize, bool isSelected, bool isFocused)
 +{
 +    if (!this.source) return;
 +    if (!this.source.getEntry(i, XonoticTopicList_cb)) return;
 +    string icon = XonoticTopicList_cb_icon;
 +    string name = XonoticTopicList_cb_name;
 +    if (isSelected) {
 +        draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_SELECTED, SKINALPHA_LISTBOX_SELECTED);
 +    } else if (isFocused) {
 +        this.focusedItemAlpha = getFadedAlpha(this.focusedItemAlpha, SKINALPHA_LISTBOX_FOCUSED, SKINFADEALPHA_LISTBOX_FOCUSED);
 +        draw_Fill('0 0 0', '1 1 0', SKINCOLOR_LISTBOX_FOCUSED, this.focusedItemAlpha);
 +    }
 +    draw_Picture(this.columnIconOrigin * eX, icon, this.columnIconSize * eX + eY, '1 1 1', SKINALPHA_LISTBOX_SELECTED);
 +    vector save_fontscale = draw_fontscale;
 +    float f = draw_CondensedFontFactor(name, false, this.realFontSize, 1);
 +    draw_fontscale.x *= f;
 +    vector fs = this.realFontSize;
 +    fs.x *= f;
 +    draw_Text(this.realUpperMargin * eY + this.columnNameOrigin * eX, name, fs, '1 1 1', SKINALPHA_TEXT, 0);
 +    draw_fontscale = save_fontscale;
 +}
 +
 +bool XonoticTopicList_keyDown(entity this, float scan, float ascii, float shift)
 +{
 +    if (scan == K_ENTER || scan == K_KP_ENTER) {
 +        m_play_click_sound(MENU_SOUND_EXECUTE);
 +        return true;
 +    }
 +    return super.keyDown(this, scan, ascii, shift);
 +}
 +
 +void XonoticTopicList_resizeNotify(entity this, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
 +{
 +    this.itemAbsSize = '0 0 0';
 +    super.resizeNotify(this, relOrigin, relSize, absOrigin, absSize);
 +
 +    this.realFontSize_y = this.fontSize / (this.itemAbsSize_y = (absSize.y * this.itemHeight));
 +    this.realFontSize_x = this.fontSize / (this.itemAbsSize_x = (absSize.x * (1 - this.controlWidth)));
 +    this.realUpperMargin = 0.5 * (1 - this.realFontSize.y);
 +    this.columnIconOrigin = 0;
 +    this.columnIconSize = this.itemAbsSize.y / this.itemAbsSize.x;
 +    this.columnNameOrigin = this.columnIconOrigin + this.columnIconSize + (0.5 * this.realFontSize.x);
 +    this.columnNameSize = 1 - this.columnIconSize - (1.5 * this.realFontSize.x);
 +}
 +
 +void XonoticTopicList_setSelected(entity this, int i)
 +{
 +    super.setSelected(this, i);
 +    this.onChange(this, this.onChangeEntity);
 +}
 +#endif
index 0000d1d608917a8e65e52c64ad1968b7a2cfacbb,abecd1ed5bd18e27576748b3ebc0f6dd0c2287e8..40c434aff79ade4d9645671a4bf344d4bac6c0a7
@@@ -2,10 -2,10 +2,10 @@@
  #define DIALOG_MULTIPLAYER_MEDIA_H
  #include "tab.qc"
  CLASS(XonoticMediaTab, XonoticTab)
-       METHOD(XonoticMediaTab, fill, void(entity))
+       METHOD(XonoticMediaTab, fill, void(entity));
        ATTRIB(XonoticMediaTab, intendedWidth, float, 0.9)
        ATTRIB(XonoticMediaTab, rows, float, 23)
 -      ATTRIB(XonoticMediaTab, columns, float, 3)
 +      ATTRIB(XonoticMediaTab, columns, float, 4)
        ATTRIB(XonoticMediaTab, name, string, "Media")
  ENDCLASS(XonoticMediaTab)
  entity makeXonoticMediaTab();