]> git.rm.cloudns.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Guide: add a free text section
authorTimePath <andrew.hardaker1995@gmail.com>
Sat, 3 Oct 2015 10:51:18 +0000 (20:51 +1000)
committerTimePath <andrew.hardaker1995@gmail.com>
Sat, 3 Oct 2015 10:51:18 +0000 (20:51 +1000)
qcsrc/menu/classes.inc
qcsrc/menu/xonotic/dialog_media_guide.qc [deleted file]
qcsrc/menu/xonotic/dialog_media_guide_description.qc [deleted file]
qcsrc/menu/xonotic/dialog_media_guide_entries.qc [deleted file]
qcsrc/menu/xonotic/dialog_media_guide_topics.qc [deleted file]
qcsrc/menu/xonotic/guide/description.qc [new file with mode: 0644]
qcsrc/menu/xonotic/guide/entries.qc [new file with mode: 0644]
qcsrc/menu/xonotic/guide/pages.qh [new file with mode: 0644]
qcsrc/menu/xonotic/guide/tab.qc [new file with mode: 0644]
qcsrc/menu/xonotic/guide/topics.qc [new file with mode: 0644]

index 44a15d676a6dc747e3d6c3cab96ae7600373ff47..c5d60b170dc9a0509d25c82bb6e4dff621e6d0a6 100644 (file)
@@ -62,9 +62,6 @@
 #include "xonotic/dialog_hudpanel_vote.qc"
 #include "xonotic/dialog_hudpanel_weapons.qc"
 #include "xonotic/dialog_hudsetup_exit.qc"
-#include "xonotic/dialog_media_guide.qc"
-#include "xonotic/dialog_media_guide_entries.qc"
-#include "xonotic/dialog_media_guide_topics.qc"
 #include "xonotic/dialog_monstertools.qc"
 #include "xonotic/dialog_multiplayer.qc"
 #include "xonotic/dialog_multiplayer_create.qc"
 #include "xonotic/dialog_singleplayer_winner.qc"
 #include "xonotic/dialog_teamselect.qc"
 #include "xonotic/gametypelist.qc"
+#include "xonotic/guide/description.qc"
+#include "xonotic/guide/entries.qc"
+#include "xonotic/guide/tab.qc"
+#include "xonotic/guide/topics.qc"
 #include "xonotic/image.qc"
 #include "xonotic/inputbox.qc"
 #include "xonotic/keybinder.qc"
diff --git a/qcsrc/menu/xonotic/dialog_media_guide.qc b/qcsrc/menu/xonotic/dialog_media_guide.qc
deleted file mode 100644 (file)
index e4f028e..0000000
+++ /dev/null
@@ -1,226 +0,0 @@
-#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(MonsterSource),   _("Monsters"),  "gametype_lms") \
-    X(NEW(VehicleSource),   _("Vehicles"),  "gametype_rc") \
-    X(NEW(TurretSource),    _("Turrets"),   "gametype_as") \
-    X(NEW(MutatorSource),   _("Mutators"),  "gametype_nb") \
-    X(NEW(MapSource),       _("Maps"),      "gametype_ctf") \
-    if (cvar("developer")) X(NEW(DebugSource), _("Debug"), "gametype_ons") \
-    /**/
-CLASS(TopicSource, DataSource)
-    METHOD(TopicSource, getEntry, entity(TopicSource this, 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(TopicSource, reload, int(TopicSource this, 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.instanceOfAnimHost) continue;
-            if (it.instanceOfDataSource) 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, getEntry, entity(DebugSource this, 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(DebugSource, reload, int(DebugSource this, 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)
-
-#define REGISTRY_SOURCE(id, arr, count) \
-CLASS(id, DataSource) \
-    METHOD(id, getEntry, entity(id this, int i, void(string, string) returns)) { \
-        entity e = arr[i]; \
-        if (returns) e.display(e, returns); \
-        return e; \
-    } \
-    METHOD(id, reload, int(id this, string filter)) { return count; } \
-ENDCLASS(id)
-
-#include "../../common/mapinfo.qh"
-REGISTRY_SOURCE(GametypeSource, MAPINFO_TYPES, MAPINFO_TYPE_COUNT)
-
-#include "../../common/items/all.qh"
-REGISTRY_SOURCE(ItemSource, ITEMS, ITEM_COUNT)
-
-#include "../../common/buffs.qh"
-REGISTRY_SOURCE(BuffSource, BUFFS, BUFFS_COUNT)
-
-#include "../../common/nades.qh"
-REGISTRY_SOURCE(NadeSource, NADES, NADES_COUNT)
-
-#include "../../common/weapons/all.qh"
-REGISTRY_SOURCE(WeaponSource, weapon_info, WEP_COUNT)
-
-#include "../../common/monsters/all.qh"
-REGISTRY_SOURCE(MonsterSource, monster_info, MON_COUNT)
-
-#include "../../common/vehicles/all.qh"
-REGISTRY_SOURCE(VehicleSource, vehicle_info, VEH_COUNT)
-
-#include "../../common/turrets/all.qh"
-REGISTRY_SOURCE(TurretSource, turret_info, TUR_COUNT)
-
-#include "../../common/mutators/base.qh"
-REGISTRY_SOURCE(MutatorSource, MUTATORS, MUTATORS_COUNT)
-
-CLASS(MapSource, DataSource)
-    METHOD(MapSource, getEntry, entity(MapSource this, 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, indexOf, int(MapSource this, string s)) {
-        MapInfo_FindName(s);
-        return MapInfo_FindName_firstResult;
-    }
-    METHOD(MapSource, reload, int(MapSource this, string s)) {
-        MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, 0, 0);
-        if (s) MapInfo_FilterString(s);
-        return MapInfo_count;
-    }
-    METHOD(MapSource, destroy, void(MapSource 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.source, 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
diff --git a/qcsrc/menu/xonotic/dialog_media_guide_description.qc b/qcsrc/menu/xonotic/dialog_media_guide_description.qc
deleted file mode 100644 (file)
index cb94cfa..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-#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, setDescription, void(entity, string));
-    ATTRIB(XonoticGuideDescription, description, string, string_null)
-
-       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
diff --git a/qcsrc/menu/xonotic/dialog_media_guide_entries.qc b/qcsrc/menu/xonotic/dialog_media_guide_entries.qc
deleted file mode 100644 (file)
index 3a091c4..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-#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(this.source, 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.source, 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.source, this.stringFilter);
-    for (int i = 0, n = this.nItems; i < n; ++i) {
-        if (this.source.getEntry(this.source, 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
diff --git a/qcsrc/menu/xonotic/dialog_media_guide_topics.qc b/qcsrc/menu/xonotic/dialog_media_guide_topics.qc
deleted file mode 100644 (file)
index c5a1044..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-#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(_source, "");
-       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(this.source, 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
diff --git a/qcsrc/menu/xonotic/guide/description.qc b/qcsrc/menu/xonotic/guide/description.qc
new file mode 100644 (file)
index 0000000..5df9d2e
--- /dev/null
@@ -0,0 +1,52 @@
+#ifndef DIALOG_MEDIA_GUIDE_DESCRIPTION_H
+#define DIALOG_MEDIA_GUIDE_DESCRIPTION_H
+#include "../listbox.qc"
+CLASS(XonoticGuideDescription, XonoticListBox)
+       ATTRIB(XonoticGuideDescription, rowsPerItem, float, 1)
+       ATTRIB(XonoticGuideDescription, selectionDoesntMatter, bool, true)
+
+    METHOD(XonoticGuideDescription, setDescription, void(entity, string));
+    ATTRIB(XonoticGuideDescription, description, string, string_null)
+
+       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
diff --git a/qcsrc/menu/xonotic/guide/entries.qc b/qcsrc/menu/xonotic/guide/entries.qc
new file mode 100644 (file)
index 0000000..f94b560
--- /dev/null
@@ -0,0 +1,127 @@
+#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(this.source, 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.source, 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.source, this.stringFilter);
+    for (int i = 0, n = this.nItems; i < n; ++i) {
+        if (this.source.getEntry(this.source, 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
diff --git a/qcsrc/menu/xonotic/guide/pages.qh b/qcsrc/menu/xonotic/guide/pages.qh
new file mode 100644 (file)
index 0000000..86150af
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef GUIDE_PAGES_H
+#define GUIDE_PAGES_H
+
+CLASS(GuidePage, Object);
+    ATTRIB(GuidePage, m_id, int, 0);
+    ATTRIB(GuidePage, m_title, string, "");
+    ATTRIB(GuidePage, m_description, string, "");
+    METHOD(GuidePage, describe, string(GuidePage this)) {
+        return this.m_description;
+    }
+    METHOD(GuidePage, display, void(GuidePage this, void(string name, string icon) returns)) {
+        returns(this.m_title, "nopreview_map");
+    }
+    CONSTRUCTOR(GuidePage, string _title) {
+        CONSTRUCT(GuidePage);
+        this.m_title = _title;
+    }
+ENDCLASS(GuidePage)
+
+void RegisterGuidePages();
+const int GUIDE_PAGES_MAX = 16;
+entity GUIDE_PAGES[GUIDE_PAGES_MAX], GUIDE_PAGES_first, GUIDE_PAGES_last;
+int GUIDE_PAGES_COUNT;
+#define REGISTER_GUIDE_PAGE(id, title) \
+    REGISTER(RegisterGuidePages, GUIDE_PAGE, GUIDE_PAGES, GUIDE_PAGES_COUNT, id, m_id, NEW(GuidePage, title))
+REGISTER_REGISTRY(RegisterGuidePages)
+
+REGISTER_GUIDE_PAGE(0, _("Intro")) { this.m_description = _(
+"Welcome to xonotic"
+);}
+
+#endif
diff --git a/qcsrc/menu/xonotic/guide/tab.qc b/qcsrc/menu/xonotic/guide/tab.qc
new file mode 100644 (file)
index 0000000..209abc8
--- /dev/null
@@ -0,0 +1,230 @@
+#ifndef DIALOG_MEDIA_GUIDE_H
+#define DIALOG_MEDIA_GUIDE_H
+#include "../datasource.qc"
+
+#define TOPICS(X) \
+    X(NEW(FreetextSource),  _("Guide"),     "gametype_tdm") \
+    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(MonsterSource),   _("Monsters"),  "gametype_lms") \
+    X(NEW(VehicleSource),   _("Vehicles"),  "gametype_rc") \
+    X(NEW(TurretSource),    _("Turrets"),   "gametype_as") \
+    X(NEW(MutatorSource),   _("Mutators"),  "gametype_nb") \
+    X(NEW(MapSource),       _("Maps"),      "gametype_ctf") \
+    if (cvar("developer")) X(NEW(DebugSource), _("Debug"), "gametype_ons") \
+    /**/
+CLASS(TopicSource, DataSource)
+    METHOD(TopicSource, getEntry, entity(TopicSource this, 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(TopicSource, reload, int(TopicSource this, 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.instanceOfAnimHost) continue;
+            if (it.instanceOfDataSource) 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, getEntry, entity(DebugSource this, 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(DebugSource, reload, int(DebugSource this, 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)
+
+#define REGISTRY_SOURCE(id, arr, count) \
+CLASS(id, DataSource) \
+    METHOD(id, getEntry, entity(id this, int i, void(string, string) returns)) { \
+        entity e = arr[i]; \
+        if (returns) e.display(e, returns); \
+        return e; \
+    } \
+    METHOD(id, reload, int(id this, string filter)) { return count; } \
+ENDCLASS(id)
+
+#include "pages.qh"
+REGISTRY_SOURCE(FreetextSource, GUIDE_PAGES, GUIDE_PAGES_COUNT)
+
+#include "../../../common/mapinfo.qh"
+REGISTRY_SOURCE(GametypeSource, MAPINFO_TYPES, MAPINFO_TYPE_COUNT)
+
+#include "../../../common/items/all.qh"
+REGISTRY_SOURCE(ItemSource, ITEMS, ITEM_COUNT)
+
+#include "../../../common/buffs.qh"
+REGISTRY_SOURCE(BuffSource, BUFFS, BUFFS_COUNT)
+
+#include "../../../common/nades.qh"
+REGISTRY_SOURCE(NadeSource, NADES, NADES_COUNT)
+
+#include "../../../common/weapons/all.qh"
+REGISTRY_SOURCE(WeaponSource, weapon_info, WEP_COUNT)
+
+#include "../../../common/monsters/all.qh"
+REGISTRY_SOURCE(MonsterSource, monster_info, MON_COUNT)
+
+#include "../../../common/vehicles/all.qh"
+REGISTRY_SOURCE(VehicleSource, vehicle_info, VEH_COUNT)
+
+#include "../../../common/turrets/all.qh"
+REGISTRY_SOURCE(TurretSource, turret_info, TUR_COUNT)
+
+#include "../../../common/mutators/base.qh"
+REGISTRY_SOURCE(MutatorSource, MUTATORS, MUTATORS_COUNT)
+
+CLASS(MapSource, DataSource)
+    METHOD(MapSource, getEntry, entity(MapSource this, 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, indexOf, int(MapSource this, string s)) {
+        MapInfo_FindName(s);
+        return MapInfo_FindName_firstResult;
+    }
+    METHOD(MapSource, reload, int(MapSource this, string s)) {
+        MapInfo_FilterGametype(MAPINFO_TYPE_ALL, 0, 0, 0, 0);
+        if (s) MapInfo_FilterString(s);
+        return MapInfo_count;
+    }
+    METHOD(MapSource, destroy, void(MapSource this)) { MapInfo_Shutdown(); }
+ENDCLASS(MapSource)
+
+#include "topics.qc"
+#include "entries.qc"
+#include "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.source, 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
diff --git a/qcsrc/menu/xonotic/guide/topics.qc b/qcsrc/menu/xonotic/guide/topics.qc
new file mode 100644 (file)
index 0000000..68d7f96
--- /dev/null
@@ -0,0 +1,94 @@
+#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(_source, "");
+       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(this.source, 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