|
From: <cn...@us...> - 2024-08-21 19:59:17
|
Revision: 1528
http://sourceforge.net/p/seq/svn/1528
Author: cn187
Date: 2024-08-21 19:59:15 +0000 (Wed, 21 Aug 2024)
Log Message:
-----------
Redesign spawn filter edit window based on new add filter dialog
Modified Paths:
--------------
showeq/trunk/src/Makefile.am
showeq/trunk/src/editor.cpp
showeq/trunk/src/filter.cpp
showeq/trunk/src/filter.h
showeq/trunk/src/filtermgr.cpp
showeq/trunk/src/filtermgr.h
showeq/trunk/src/interface.cpp
showeq/trunk/src/interface.h
showeq/trunk/src/spawnlistcommon.cpp
Added Paths:
-----------
showeq/trunk/src/filterlistwindow.cpp
showeq/trunk/src/filterlistwindow.h
showeq/trunk/src/toolbaricons.cpp
showeq/trunk/src/toolbaricons.h
Modified: showeq/trunk/src/Makefile.am
===================================================================
--- showeq/trunk/src/Makefile.am 2024-08-21 19:58:52 UTC (rev 1527)
+++ showeq/trunk/src/Makefile.am 2024-08-21 19:59:15 UTC (rev 1528)
@@ -5,7 +5,7 @@
bin_PROGRAMS = showeq
showeq_SOURCES = main.cpp spawn.cpp spawnshell.cpp spawnlist.cpp spellshell.cpp \
- spelllist.cpp vpacket.cpp editor.cpp filter.cpp packetfragment.cpp packetstream.cpp \
+ spelllist.cpp vpacket.cpp editor.cpp filterlistwindow.cpp filter.cpp packetfragment.cpp packetstream.cpp \
packetinfo.cpp packet.cpp packetcapture.cpp packetformat.cpp interface.cpp compass.cpp \
map.cpp util.cpp experiencelog.cpp combatlog.cpp player.cpp skilllist.cpp \
statlist.cpp filtermgr.cpp mapcore.cpp category.cpp compassframe.cpp group.cpp \
@@ -15,10 +15,10 @@
datalocationmgr.cpp eqstr.cpp messages.cpp message.cpp messagefilter.cpp messagewindow.cpp \
messageshell.cpp terminal.cpp filteredspawnlog.cpp messagefilterdialog.cpp \
diagnosticmessages.cpp mapicon.cpp filternotifications.cpp netstream.cpp guildshell.cpp \
- guildlist.cpp bazaarlog.cpp mapicondialog.cpp packetcaptureprovider.cpp
+ guildlist.cpp bazaarlog.cpp mapicondialog.cpp packetcaptureprovider.cpp toolbaricons.cpp
showeq_moc_SRCS = bazaarlog.moc category.moc combatlog.moc compass.moc \
- compassframe.moc datetimemgr.moc editor.moc experiencelog.moc \
+ compassframe.moc datetimemgr.moc editor.moc filterlistwindow.moc experiencelog.moc \
filteredspawnlog.moc filtermgr.moc filternotifications.moc group.moc \
guild.moc guildlist.moc guildshell.moc interface.moc logger.moc \
map.moc mapicon.moc mapicondialog.moc messagefilter.moc \
@@ -38,6 +38,7 @@
$(srcdir)/compassframe.cpp: compassframe.moc
$(srcdir)/datetimemgr.cpp: datetimemgr.moc
$(srcdir)/editor.cpp: editor.moc
+$(srcdir)/filterlistwindow.cpp: filterlistwindow.moc
$(srcdir)/experiencelog.cpp: experiencelog.moc
$(srcdir)/filteredspawnlog.cpp: filteredspawnlog.moc
$(srcdir)/filtermgr.cpp: filtermgr.moc
@@ -113,7 +114,7 @@
EXTRA_DIST = h2info.pl
-noinst_HEADERS = classes.h compass.h everquest.h interface.h main.h map.h filter.h vpacket.h editor.h packet.h packetcapture.h packetcommon.h packetformat.h packetstream.h packetfragment.h packetinfo.h races.h skills.h spells.h util.h experiencelog.h combatlog.h spawn.h spawnshell.h spawnlist.h spellshell.h spelllist.h languages.h weapons.h weapons1.h weapons27.h weapons28.h weapons29.h weapons2a.h weapons2b.h weapons2c.h weapons2d.h weapons2e.h weapons2f.h weapons30.h weapons4e.h decode.h cgiconv.h skilllist.h statlist.h deity.h player.h crctab.h filtermgr.h point.h pointarray.h mapcore.h category.h compassframe.h group.h guild.h fixpt.h netdiag.h zones.h logger.h xmlconv.h xmlpreferences.h seqwindow.h seqlistview.h zonemgr.h spawnmonitor.h spawnpointlist.h typenames.h spawnlistcommon.h spawnlist2.h datetimemgr.h spawnlog.h packetlog.h datalocationmgr.h eqstr.h messages.h messagefilter.h messagewindow.h messageshell.h terminal.h filteredspawnlog.h messagefilterdialog.h diagnosticmessages.h mapicon.h mapicondialog.h mapicondialog.ui filternotifications.h netstream.h guildshell.h guildlist.h bazaarlog.h message.h s_everquest.h staticspells.h packetcaptureprovider.h mapcolors.h
+noinst_HEADERS = classes.h compass.h everquest.h interface.h main.h map.h filter.h vpacket.h editor.h filterlistwindow.h packet.h packetcapture.h packetcommon.h packetformat.h packetstream.h packetfragment.h packetinfo.h races.h skills.h spells.h util.h experiencelog.h combatlog.h spawn.h spawnshell.h spawnlist.h spellshell.h spelllist.h languages.h weapons.h weapons1.h weapons27.h weapons28.h weapons29.h weapons2a.h weapons2b.h weapons2c.h weapons2d.h weapons2e.h weapons2f.h weapons30.h weapons4e.h decode.h cgiconv.h skilllist.h statlist.h deity.h player.h crctab.h filtermgr.h point.h pointarray.h mapcore.h category.h compassframe.h group.h guild.h fixpt.h netdiag.h zones.h logger.h xmlconv.h xmlpreferences.h seqwindow.h seqlistview.h zonemgr.h spawnmonitor.h spawnpointlist.h typenames.h spawnlistcommon.h spawnlist2.h datetimemgr.h spawnlog.h packetlog.h datalocationmgr.h eqstr.h messages.h messagefilter.h messagewindow.h messageshell.h terminal.h filteredspawnlog.h messagefilterdialog.h diagnosticmessages.h mapicon.h mapicondialog.h mapicondialog.ui filternotifications.h netstream.h guildshell.h guildlist.h bazaarlog.h message.h s_everquest.h staticspells.h packetcaptureprovider.h mapcolors.h toolbaricons.h
CLEANFILES = $(nodist_showeq_SOURCES)
Modified: showeq/trunk/src/editor.cpp
===================================================================
--- showeq/trunk/src/editor.cpp 2024-08-21 19:58:52 UTC (rev 1527)
+++ showeq/trunk/src/editor.cpp 2024-08-21 19:59:15 UTC (rev 1528)
@@ -43,55 +43,10 @@
#include <QCloseEvent>
#include "util.h"
+#include "toolbaricons.h"
#include "editor.h"
-/* XPM */
-static const char *filesave[] = {
-" 14 14 4 1",
-". c #040404",
-"# c #808304",
-"a c #bfc2bf",
-"b c None",
-"..............",
-".#.aaaaaaaa.a.",
-".#.aaaaaaaa...",
-".#.aaaaaaaa.#.",
-".#.aaaaaaaa.#.",
-".#.aaaaaaaa.#.",
-".#.aaaaaaaa.#.",
-".##........##.",
-".############.",
-".##.........#.",
-".##......aa.#.",
-".##......aa.#.",
-".##......aa.#.",
-"b............."
-};
-
-/* XPM */
-static const char *fileopen[] = {
-" 16 13 5 1",
-". c #040404",
-"# c #808304",
-"a c None",
-"b c #f3f704",
-"c c #f3f7f3",
-"aaaaaaaaa...aaaa",
-"aaaaaaaa.aaa.a.a",
-"aaaaaaaaaaaaa..a",
-"a...aaaaaaaa...a",
-".bcb.......aaaaa",
-".cbcbcbcbc.aaaaa",
-".bcbcbcbcb.aaaaa",
-".cbcb...........",
-".bcb.#########.a",
-".cb.#########.aa",
-".b.#########.aaa",
-"..#########.aaaa",
-"...........aaaaa"
-};
-
EditorWindow::EditorWindow(const char *fileName)
: QMainWindow( 0 )
{
@@ -98,8 +53,8 @@
setObjectName("ShowEQ - Editor");
setAttribute(Qt::WA_DeleteOnClose);
QPixmap openIcon, saveIcon;
- openIcon = QPixmap( fileopen );
- saveIcon = QPixmap( filesave );
+ openIcon = ToolbarIcons::FileOpen();
+ saveIcon = ToolbarIcons::FileSave();
fileTools = new QToolBar(this);
fileTools->setWindowTitle( tr( "File Operations" ) );
Modified: showeq/trunk/src/filter.cpp
===================================================================
--- showeq/trunk/src/filter.cpp 2024-08-21 19:58:52 UTC (rev 1527)
+++ showeq/trunk/src/filter.cpp 2024-08-21 19:59:15 UTC (rev 1528)
@@ -33,10 +33,10 @@
#include <QFile>
#include <QTextStream>
+#include <QXmlStreamReader>
#define MAXLEN 5000
-#pragma message("Once our minimum supported Qt version is greater than 5.14, this check can be removed and ENDL replaced with Qt::endl")
#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
#define ENDL Qt::endl
#else
@@ -45,9 +45,22 @@
//#define DEBUG_FILTER
+#define X(a, b) #b,
+const QString FilterStringFieldName[FSF_Max] = {
+ FILTERSTRINGFIELD_TABLE
+};
+#undef X
+
+#define X(a, b) #b,
+const QString FilterStringInfoFieldName[FSIF_Max] = {
+ FILTERSTRINGINFOFIELD_TABLE
+};
+#undef X
+
+
//----------------------------------------------------------------------
// LoadXmlContentHandler declaration
-class LoadXmlContentHandler : public QXmlDefaultHandler
+class LoadXmlContentHandler : public QObject
{
public:
LoadXmlContentHandler(Filters& filters, const FilterTypes& types);
@@ -56,7 +69,7 @@
// QXmlContentHandler overrides
bool startDocument();
bool startElement( const QString&, const QString&, const QString& ,
- const QXmlAttributes& );
+ const QXmlStreamAttributes& );
bool characters(const QString& ch);
bool endElement( const QString&, const QString&, const QString& );
bool endDocument();
@@ -182,15 +195,41 @@
(const char*)regexString, minLevel, maxLevel);
#endif
+ // in theory, we have minLevel and maxLevel, so there should be no level range
+ // appended to the filter string. But there are situation where that can
+ // happen, so check for it and remove it if found. Otherwise, if
+ // we leave it attached, the filter will never match anything since the
+ // level range will be treated as part of the match string.
+
+ QString filterString;
+ QString workString = regexString;
+
+ // find the semi-colon that seperates the regex from the level info
+ int breakPoint = workString.indexOf(';');
+
+ // if no semi-colon, then it's all a regex
+ if (breakPoint == -1)
+ filterString = regexString;
+ else
+ // regex is the left most part of the string up to breakPoint characters
+ filterString = workString.left(breakPoint);
+
+ filterString = regexString;
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5,5,0))
+ if (!caseSensitive)
+ m_regexp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
+#else
m_regexp.setPatternSyntax(QRegExp::RegExp);
m_regexp.setCaseSensitivity(caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
+#endif
// For the pattern, save off the original. This is what will be saved
// during save operations. But the actual regexp we filter with will
// mark the # in spawn names as optional to aid in filter writing.
- m_regexpOriginalPattern = QString(regexString.toLatin1().data());
+ m_regexpOriginalPattern = filterString;
- QString fixedFilterPattern = regexString;
+ QString fixedFilterPattern = filterString;
fixedFilterPattern.replace("Name:", "Name:#?", Qt::CaseInsensitive);
m_regexp.setPattern(fixedFilterPattern);
@@ -244,7 +283,7 @@
bool FilterItem::isFiltered(const QString& filterString, uint8_t level) const
{
// check the main filter string
- if (m_regexp.indexIn(filterString) != -1)
+ if (filterString.indexOf(m_regexp) != -1)
{
// is there is a level range component to this filter
if ((m_minLevel > 0) || (m_maxLevel > 0))
@@ -469,6 +508,49 @@
}
}
+QString Filter::getFilterString(int index) const
+{
+ if (index >= m_filterItems.size())
+ return QString();
+
+ FilterItem* item = m_filterItems[index];
+ QString pattern = item->filterPattern();
+
+ return pattern;
+}
+
+QString Filter::getOrigFilterString(int index) const
+{
+ if (index >= m_filterItems.size())
+ return QString();
+
+ FilterItem* item = m_filterItems[index];
+ QString pattern = item->origFilterPattern();
+
+ return pattern;
+}
+
+int Filter::getMinLevel(int index) const
+{
+ if (index >= m_filterItems.size())
+ return -1;
+
+ FilterItem* item = m_filterItems[index];
+
+ return item->minLevel();
+}
+
+int Filter::getMaxLevel(int index) const
+{
+ if (index >= m_filterItems.size())
+ return -1;
+
+ FilterItem* item = m_filterItems[index];
+
+ return item->maxLevel();
+};
+
+
//----------------------------------------------------------------------
// Filters
Filters::Filters(const FilterTypes& types)
@@ -533,23 +615,61 @@
// load filters
m_file = filename;
+ if (filename.isEmpty())
+ return false;
+
// create XML content handler
LoadXmlContentHandler handler(*this, m_types);
// create a file object on the file
QFile xmlFile(filename);
+ xmlFile.open(QIODevice::ReadOnly);
+ if (!xmlFile.isOpen())
+ return false;
- // create an XmlInputSource on the file
- QXmlInputSource source(&xmlFile);
-
- // create an XML parser
- QXmlSimpleReader reader;
+ QXmlStreamReader reader(&xmlFile);
- // set the content handler
- reader.setContentHandler(&handler);
+ bool status = true;
+ while(!reader.atEnd() && status)
+ {
+ switch(reader.readNext())
+ {
+ case QXmlStreamReader::NoToken:
+ break;
+ case QXmlStreamReader::Invalid:
+ status = false;
+ break;
+ case QXmlStreamReader::StartDocument:
+ if (!handler.startDocument())
+ status = false;
+ break;
+ case QXmlStreamReader::EndDocument:
+ if (!handler.endDocument())
+ status = false;
+ break;
+ case QXmlStreamReader::StartElement:
+ if (!handler.startElement(QString(), QString(), reader.name().toString(), reader.attributes()))
+ status = false;
+ break;
+ case QXmlStreamReader::EndElement:
+ if (!handler.endElement(QString(), QString(), reader.name().toString()))
+ status = false;
+ break;
+ case QXmlStreamReader::Characters:
+ if (!handler.characters(reader.text().toString()))
+ status = false;
+ break;
+ case QXmlStreamReader::Comment:
+ case QXmlStreamReader::DTD:
+ case QXmlStreamReader::EntityReference:
+ case QXmlStreamReader::ProcessingInstruction:
+ break;
+ }
+ }
- // parse the file
- return reader.parse(source);
+ xmlFile.close();
+
+ return status;
}
bool Filters::save(const QString& filename) const
@@ -565,8 +685,13 @@
QTextStream out(&file);
// set the output encoding to be UTF8
+#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
+ out.setEncoding(QStringConverter::Utf8);
+#else
out.setCodec("UTF-8");
+#endif
+
// set the number output to be left justified decimal
out.setIntegerBase(10);
out.setFieldAlignment(QTextStream::AlignLeft);
@@ -707,6 +832,59 @@
filter->remFilter(filterPattern);
}
+int Filters::numFilters(uint8_t type) const
+{
+ uint32_t mask = (1 << type);
+ FilterMap::const_iterator it = m_filters.find(mask);
+
+ if (it == m_filters.end()) return 0;
+
+ return it->second->numFilters();
+}
+
+QString Filters::getFilterString(uint8_t type, int index) const
+{
+ uint32_t mask = (1 << type);
+ FilterMap::const_iterator it = m_filters.find(mask);
+
+ if (it == m_filters.end()) return QString();
+
+ return it->second->getFilterString(index);
+}
+
+QString Filters::getOrigFilterString(uint8_t type, int index) const
+{
+ uint32_t mask = (1 << type);
+ FilterMap::const_iterator it = m_filters.find(mask);
+
+ if (it == m_filters.end()) return QString();
+
+ return it->second->getOrigFilterString(index);
+}
+
+int Filters::getMinLevel(uint8_t type, int index) const
+{
+ uint32_t mask = (1 << type);
+ FilterMap::const_iterator it = m_filters.find(mask);
+
+ if (it == m_filters.end()) return -1;
+
+ return it->second->getMinLevel(index);
+}
+
+int Filters::getMaxLevel(uint8_t type, int index) const
+{
+ uint32_t mask = (1 << type);
+ FilterMap::const_iterator it = m_filters.find(mask);
+
+ if (it == m_filters.end()) return -1;
+
+ return it->second->getMaxLevel(index);
+}
+
+
+
+
///////////////////////////////////
// FilterTypes
FilterTypes::FilterTypes()
@@ -823,7 +1001,7 @@
bool LoadXmlContentHandler::startElement(const QString&,
const QString&,
const QString& name,
- const QXmlAttributes& attr)
+ const QXmlStreamAttributes& attr)
{
if (name == "oldfilter")
{
@@ -843,21 +1021,13 @@
if (name == "level")
{
- int index;
-
- // first check for a min
- index = attr.index("min");
-
// if min attribute was found, use it
- if (index != -1)
- m_currentMinLevel = uint8_t(attr.value(index).toUShort());
+ if (!attr.value("min").isEmpty())
+ m_currentMinLevel = uint8_t(attr.value("min").toString().toUShort());
- // then check for a max
- index = attr.index("max");
-
// if max attribute was found, use it
- if (index != -1)
- m_currentMaxLevel = uint8_t(attr.value(index).toUShort());
+ if (!attr.value("max").isEmpty())
+ m_currentMaxLevel = uint8_t(attr.value("max").toString().toUShort());
// done
return true;
@@ -865,17 +1035,16 @@
if (name == "section")
{
- int index = attr.index("name");
// section is only valid if a name is specified
- if (index == -1)
+ if (attr.value("name").isEmpty())
return false;
// get the current type for the name
- m_currentType = m_types.type(attr.value(index));
+ m_currentType = m_types.type(attr.value("name").toString());
return true;
}
-
+
return true;
}
Modified: showeq/trunk/src/filter.h
===================================================================
--- showeq/trunk/src/filter.h 2024-08-21 19:58:52 UTC (rev 1527)
+++ showeq/trunk/src/filter.h 2024-08-21 19:59:15 UTC (rev 1528)
@@ -33,13 +33,83 @@
#include <QString>
#include <QList>
-#include <QRegExp>
-#include <QXmlAttributes>
#include <QTextStream>
+#if (QT_VERSION >= QT_VERSION_CHECK(5,5,0))
+#include <QRegularExpression>
+#else
+#include <QRegExp>
+#endif
+
#include <map>
+
//--------------------------------------------------
+// defines and enums
+
+// HumanReadableName, FilterStringFieldName
+#define FILTERSTRINGFIELD_TABLE \
+ X(Name, Name) \
+ X(Level, Level) \
+ X(Race, Race) \
+ X(Class, Class) \
+ X(NPC, NPC) \
+ X(X, X) \
+ X(Y, Y) \
+ X(Z, Z) \
+ X(Light, Light) \
+ X(Deity, Deity) \
+ X(RaceTeam, RTeam) \
+ X(DeityTeam, DTeam) \
+ X(Type, Type) \
+ X(LastName, LastName) \
+ X(Guild, Guild) \
+ X(SpawnTime, Spawn) \
+ X(Info, Info) \
+ X(GM, GM)
+
+// HumanReadableName, FilterStringFieldName
+#define FILTERSTRINGINFOFIELD_TABLE \
+ X(Light, Light) \
+ X(Head, H) \
+ X(Chest, C) \
+ X(Arms, A) \
+ X(Waist, W) \
+ X(Gloves, G) \
+ X(Legs, L) \
+ X(Feet, F) \
+ X(Primary, 1) \
+ X(Secondary, 2)
+
+
+#define X(a, b) FSF_##a,
+enum FilterStringField
+{
+ FILTERSTRINGFIELD_TABLE
+ FSF_Max
+};
+#undef X
+
+#define X(a, b) FSIF_##a,
+enum FilterStringInfoField
+{
+ FILTERSTRINGINFOFIELD_TABLE
+ FSIF_Max
+};
+#undef X
+
+extern const QString FilterStringFieldName[FSF_Max];
+extern const QString FilterStringInfoFieldName[FSIF_Max];
+
+
+// special handling for min/max level, which aren't part of regex filter string
+#define FSF_MINLEVEL_NAME "MinLevel"
+#define FSF_MINLEVEL_LABEL "Min Level"
+#define FSF_MAXLEVEL_NAME "MaxLevel"
+#define FSF_MAXLEVEL_LABEL "Max Level"
+
+
+//--------------------------------------------------
// forward declarations
class FilterItem;
class Filter;
@@ -68,6 +138,7 @@
QString name() const { return m_regexp.pattern(); }
QString filterPattern() const { return m_regexp.pattern(); }
+ QString origFilterPattern() const { return m_regexpOriginalPattern; }
uint8_t minLevel() const { return m_minLevel; }
uint8_t maxLevel() const { return m_maxLevel; }
bool valid() { return m_regexp.isValid(); }
@@ -76,7 +147,11 @@
void init(const QString& filterPattern, bool caseSensitive, uint8_t minLevel,
uint8_t maxLevel);
+#if (QT_VERSION >= QT_VERSION_CHECK(5,5,0))
+ QRegularExpression m_regexp;
+#else
QRegExp m_regexp;
+#endif
QString m_regexpOriginalPattern;
uint8_t m_minLevel;
uint8_t m_maxLevel;
@@ -99,6 +174,12 @@
void listFilters(void);
void setCaseSensitive(bool caseSensitive);
+ int numFilters() const { return m_filterItems.size(); }
+ QString getFilterString(int index) const;
+ QString getOrigFilterString(int index) const;
+ int getMinLevel(int index) const;
+ int getMaxLevel(int index) const;
+
private:
FilterItem* findFilter(const QString& filterPattern);
@@ -128,7 +209,11 @@
uint8_t minLevel = 0, uint8_t maxLevel = 0);
void remFilter(uint8_t type, const QString& filterString);
- protected:
+ int numFilters(uint8_t type) const;
+ QString getFilterString(uint8_t type, int index) const;
+ QString getOrigFilterString(uint8_t type, int index)const;
+ int getMinLevel(uint8_t type, int index) const;
+ int getMaxLevel(uint8_t type, int index) const;
protected:
QString m_file;
Copied: showeq/trunk/src/filterlistwindow.cpp (from rev 1527, showeq/trunk/src/filtermgr.cpp)
===================================================================
--- showeq/trunk/src/filterlistwindow.cpp (rev 0)
+++ showeq/trunk/src/filterlistwindow.cpp 2024-08-21 19:59:15 UTC (rev 1528)
@@ -0,0 +1,1297 @@
+/*
+ * filterlistwindow.cpp
+ * Copyright 2024 by the respective ShowEQ Developers
+ *
+ * This file is part of ShowEQ.
+ * http://www.sourceforge.net/projects/seq
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <QWidget>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+#include <QTabWidget>
+#include <QTreeView>
+#include <QDebug>
+#include <QHeaderView>
+#include <QLineEdit>
+#include <QLabel>
+#include <QGridLayout>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QPushButton>
+#include <QCheckBox>
+#include <QToolBar>
+#include <QAction>
+#include <QMenuBar>
+#include <QMenu>
+#include <QFileDialog>
+
+#include "diagnosticmessages.h"
+#include "filtermgr.h"
+#include "filterlistwindow.h"
+#include "toolbaricons.h"
+
+
+
+#define FILTERLISTCOLUMN_TABLE \
+ X(FilterString) \
+ X(MinLevel) \
+ X(MaxLevel)
+
+#define X(a) FLC_##a,
+enum FilterListColumn {
+ FILTERLISTCOLUMN_TABLE
+ FLC_Max
+};
+#undef X
+
+#define X(a) #a,
+const QString FilterListColumnName[] = {
+ FILTERLISTCOLUMN_TABLE
+};
+#undef X
+
+enum DataItemUserRole
+{
+ DIUR_FilterStringWithLevelRange=Qt::UserRole + 1,
+};
+
+
+FilterListWindow::FilterListWindow(QString filename, QWidget* parent, Qt::WindowFlags flags):
+ QMainWindow(parent, flags),
+ m_filename(filename),
+ m_tabWidget(nullptr),
+ m_statusBar(nullptr),
+ m_filters(nullptr),
+ m_types(nullptr)
+{
+ //setModal(true);
+ setAttribute(Qt::WA_DeleteOnClose);
+ setWindowTitle(filename);
+
+ m_types = new FilterTypes;
+ uint8_t type;
+ uint32_t mask;
+ #define X(a, b, c) m_types->registerType(#a, type, mask);
+ FILTER_TYPE_TABLE
+ #undef X
+
+ m_filters = new Filters(*m_types);
+
+ QMenuBar* menubar = new QMenuBar(this);
+ setMenuBar(menubar);
+
+ QMenu* fileMenu = new QMenu("&File", this);
+
+#if (QT_VERSION >= QT_VERSION_CHECK(6,3,0))
+ fileMenu->addAction(ToolbarIcons::FileOpen(), "&Open", Qt::CTRL|Qt::Key_O, this, SLOT(load()));
+ fileMenu->addAction(ToolbarIcons::FileSave(), "&Save", Qt::CTRL|Qt::Key_S, this, SLOT(save()));
+ fileMenu->addSeparator();
+ fileMenu->addAction("&Close", Qt::CTRL|Qt::Key_W, this, SLOT(close()));
+#else
+ fileMenu->addAction(ToolbarIcons::FileOpen(), "&Open", this, SLOT(load()), Qt::CTRL|Qt::Key_O);
+ fileMenu->addAction(ToolbarIcons::FileSave(), "&Save", this, SLOT(save()), Qt::CTRL|Qt::Key_S);
+ fileMenu->addSeparator();
+ fileMenu->addAction("&Close", this, SLOT(close()), Qt::CTRL|Qt::Key_W);
+#endif
+
+ menubar->addMenu(fileMenu);
+
+ QToolBar* toolbar = new QToolBar(this);
+ toolbar->addAction(ToolbarIcons::FileOpen(), "Open File", this, SLOT(load()));
+ toolbar->addAction(ToolbarIcons::FileSave(), "Save File", this, SLOT(save()));
+
+ addToolBar(toolbar);
+
+ m_tabWidget = new QTabWidget(this);
+
+ QStringList tabs = {
+ #define X(a, b, c) #a,
+ FILTER_TYPE_TABLE
+ };
+ #undef X
+
+ for (uint8_t i=0; i < SIZEOF_FILTERS; ++i)
+ {
+ QWidget* t = createTab(m_types->name(i));
+ m_tabWidget->addTab(t, "&" + m_types->name(i));
+ }
+
+ setCentralWidget(m_tabWidget);
+
+ m_statusBar = new QStatusBar(this);
+ setStatusBar(m_statusBar);
+
+ loadFile();
+
+ show();
+
+ m_statusBar->showMessage("Ready", 2000);
+
+}
+
+FilterListWindow::~FilterListWindow()
+{
+ if (m_filters)
+ delete m_filters;
+
+ if (m_types)
+ delete m_types;
+
+ qDeleteAll(m_models);
+ m_models.clear();
+
+ qDeleteAll(m_views);
+ m_views.clear();
+}
+
+void FilterListWindow::setTabLabel(uint8_t type, int count)
+{
+#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
+ if (!m_tabWidget || !m_tabWidget->tabBar())
+ return;
+
+ m_tabWidget->tabBar()->setTabText(type, QString("&") + m_types->name(type)
+#else
+ if (!m_tabWidget)
+ return;
+
+ m_tabWidget->setTabText(type, QString("&") + m_types->name(type)
+#endif
+ + " (" + QString::number(count) + ")");
+}
+
+QWidget* FilterListWindow::createTab(QString name)
+{
+ uint8_t type = m_types->type(name);
+
+ QWidget* w = new QWidget(this);
+ w->setObjectName(name);
+ QVBoxLayout* l = new QVBoxLayout(w);
+
+ QToolBar* tabButtons = new QToolBar(w);
+
+ QAction* tmpAction = new QAction("+", nullptr);
+ tmpAction->setToolTip("Add New Item");
+ tmpAction->setProperty("type", m_types->type(name));
+ tmpAction->setProperty("action", "add");
+ tabButtons->addAction(tmpAction);
+
+ tmpAction = new QAction("-", nullptr);
+ tmpAction->setToolTip("Delete Selected Item");
+ tmpAction->setProperty("type", m_types->type(name));
+ tmpAction->setProperty("action", "delete");
+ tabButtons->addAction(tmpAction);
+
+ l->addWidget(tabButtons);
+ connect(tabButtons, SIGNAL(actionTriggered(QAction*)), this, SLOT(tabButtonClicked(QAction*)));
+
+
+ QTreeView* t = new QTreeView();
+ m_views[type] = t;
+ t->setRootIsDecorated(false);
+ t->setSelectionMode(QAbstractItemView::SingleSelection);
+ t->setSelectionBehavior(QAbstractItemView::SelectRows);
+ t->expandAll();
+ t->setItemsExpandable(false);
+
+ connect(t, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(editItem(QModelIndex)));
+ l->addWidget(t);
+
+ return w;
+}
+
+void FilterListWindow::tabButtonClicked(QAction* action)
+{
+ uint8_t type = action->property("type").toInt();
+ QString a = action->property("action").toString();
+ if (a == "add")
+ createItem(type);
+ else if (a == "delete")
+ deleteItem(type);
+}
+
+void FilterListWindow::createItem(uint8_t type)
+{
+ bool ok = false;
+ QString filterString;
+ filterString = FilterDialog::getFilter(this, "Add " + m_types->name(type) + " Filter", filterString, &ok);
+ if (ok)
+ {
+ m_models[type]->addFilter(filterString);
+ setTabLabel(type, m_models[type]->rowCount());
+ }
+}
+
+void FilterListWindow::deleteItem(uint8_t type)
+{
+ QModelIndexList selectedRows = m_views[type]->selectionModel()->selectedRows();
+ if (selectedRows.size())
+ {
+ QModelIndex selected = selectedRows[0]; //only one selection allowed, so we can take the first
+ m_models[type]->removeFilter(selected);
+ setTabLabel(type, m_models[type]->rowCount());
+ }
+}
+
+void FilterListWindow::editItem(QModelIndex index)
+{
+ //TODO rework the whole filterdialog/filtermodel/filters call chain
+ //to to better handle min/max level rather than this kludge
+ //
+ const QAbstractItemModel* model = index.model();
+ QString filterString = model->data(index, DIUR_FilterStringWithLevelRange).toString();
+ uint8_t type = index.internalId();
+ bool ok = false;
+ filterString = FilterDialog::getFilter(this, "Edit " + m_types->name(type) + " Filter", filterString, &ok);
+ if (ok)
+ {
+ m_models[type]->removeFilter(index);
+ m_models[type]->addFilter(filterString);
+ }
+}
+
+void FilterListWindow::load()
+{
+ QString fn = QFileDialog::getOpenFileName(this, "Open File",
+ m_filename, "XML Files (*.xml)");
+ if (fn.isEmpty())
+ {
+ m_statusBar->showMessage("File Open Cancelled", 2000);
+ return;
+ }
+
+ m_filename = fn;
+ setWindowTitle(m_filename);
+ loadFile();
+}
+
+void FilterListWindow::loadFile()
+{
+
+ for (uint8_t i=0; i < SIZEOF_FILTERS; ++i)
+ m_views[i]->setModel(nullptr);
+
+ qDeleteAll(m_models);
+ m_models.clear();
+
+ m_filters->load(m_filename);
+
+ for (int i=0; i < SIZEOF_FILTERS; ++i)
+ {
+ m_models[i] = new FilterModel(m_filters, i);
+ m_views[i]->setModel(m_models[i]);
+ setTabLabel(i, m_models[i]->rowCount());
+
+#if (QT_VERSION >= QT_VERSION_CHECK(5,0,0))
+ m_views[i]->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+#else
+ m_views[i]->header()->setResizeMode(QHeaderView::ResizeToContents);
+#endif
+ }
+
+ m_statusBar->showMessage(QString("Loaded %1").arg(m_filename), 2000);
+}
+
+void FilterListWindow::save()
+{
+ if (m_filename.isEmpty())
+ saveAs();
+ else
+ if (!m_filters->save())
+ m_statusBar->showMessage(QString("Could not write to %1").arg(m_filename),
+ 2000);
+ else
+ m_statusBar->showMessage(QString("Saved %1").arg(m_filename), 2000);
+}
+
+void FilterListWindow::saveAs()
+{
+ QString fn = QFileDialog::getSaveFileName(this, "Save File",
+ m_filename, "XML Files (*.xml)");
+ if (fn.isEmpty())
+ {
+ m_statusBar->showMessage("Save File Cancelled", 2000);
+ return;
+ }
+
+ m_filename = fn;
+ setWindowTitle(m_filename);
+ save();
+}
+
+
+FilterModel::FilterModel(Filters* filters, uint8_t type, QObject* parent) :
+ QAbstractItemModel(parent),
+ m_filters(filters),
+ m_type(type)
+{ }
+
+FilterModel::~FilterModel()
+{}
+
+QModelIndex FilterModel::index(int row, int column, const QModelIndex &parent) const
+{
+ return createIndex(row, column, m_type);
+}
+
+QModelIndex FilterModel::parent(const QModelIndex &index) const
+{
+ return QModelIndex();
+}
+
+int FilterModel::rowCount(const QModelIndex &parent) const
+{
+ return m_filters->numFilters(m_type);
+}
+
+int FilterModel::columnCount(const QModelIndex &parent) const
+{
+ return FLC_Max;
+}
+
+QVariant FilterModel::headerData(int section, Qt::Orientation, int role) const
+{
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return FilterListColumnName[section];
+
+ default:
+ return QVariant();
+ }
+}
+
+QVariant FilterModel::data(const QModelIndex &index, int role) const
+{
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch(index.column())
+ {
+ case FLC_FilterString:
+ return m_filters->getOrigFilterString(m_type, index.row());
+ case FLC_MinLevel:
+ return m_filters->getMinLevel(m_type, index.row());
+ case FLC_MaxLevel:
+ return m_filters->getMaxLevel(m_type, index.row());
+ default:
+ return QVariant();
+ }
+ case DIUR_FilterStringWithLevelRange:
+ {
+ int minLevel = m_filters->getMinLevel(m_type, index.row());
+ int maxLevel = m_filters->getMaxLevel(m_type, index.row());
+ QString filterString = m_filters->getOrigFilterString(m_type, index.row());
+ if (minLevel > 0 || maxLevel > 0)
+ {
+ filterString += ';';
+ if (minLevel > 0)
+ {
+ filterString += QString::number(minLevel);
+ if (maxLevel > 0)
+ filterString += "-" + QString::number(maxLevel);
+ else
+ filterString += "-" + QString::number(SHRT_MAX);
+ }
+ else
+ {
+ filterString += "0-" + QString::number(maxLevel);
+ }
+ }
+ return filterString;
+ }
+
+ default:
+ return QVariant();
+ }
+}
+
+void FilterModel::addFilter(QString filterPattern)
+{
+ //TODO rework the whole filterdialog/filtermodel/filters call chain
+ //to to better handle min/max level rather than this kludge
+ int minLevel = 0;
+ int maxLevel = 0;
+
+ QString workString = filterPattern;
+ int breakpoint = workString.indexOf(';');
+ if (breakpoint == -1)
+ {
+ beginInsertRows(QModelIndex(), rowCount(), 1);
+ m_filters->addFilter(m_type, filterPattern);
+ endInsertRows();
+ }
+ else
+ {
+ //this is basically a copy of the level string parsing code in FilterItem()
+ filterPattern = workString.left(breakpoint);
+ QString levelString = workString.mid(breakpoint+1);
+ breakpoint = levelString.indexOf('-');
+ bool ok;
+ int level;
+ if (breakpoint == -1)
+ {
+ level = levelString.toInt(&ok);
+ if (ok)
+ minLevel = level;
+ }
+ else
+ {
+ level = levelString.left(breakpoint).toInt(&ok);
+ if (ok)
+ minLevel = level;
+
+ levelString = levelString.mid(breakpoint+1);
+ if (levelString.isEmpty())
+ {
+ maxLevel = SHRT_MAX;
+ }
+ else
+ {
+ level = levelString.toInt(&ok);
+ if (ok)
+ maxLevel = level;
+ }
+ }
+ if (maxLevel < minLevel)
+ maxLevel = minLevel;
+
+
+ beginInsertRows(QModelIndex(), rowCount(), 1);
+ m_filters->addFilter(m_type, filterPattern, minLevel, maxLevel);
+ endInsertRows();
+
+
+ }
+
+ emit dataChanged(index(rowCount()-1, 0), index(rowCount(), 1));
+}
+
+void FilterModel::removeFilter(QModelIndex selection)
+{
+ beginRemoveRows(QModelIndex(), selection.row(), 1);
+ m_filters->remFilter(m_type, m_filters->getFilterString(m_type, selection.row()));
+ endRemoveRows();
+}
+
+
+FilterFormField::FilterFormField(QString name, QString labeltext, QWidget* parent) :
+ QWidget(parent),
+ m_name(name),
+ m_labeltext(labeltext),
+ m_check(nullptr),
+ m_label(nullptr),
+ m_edit(nullptr)
+{
+ if (m_labeltext.isNull())
+ m_labeltext = m_name;
+
+ m_check = new QCheckBox(this);
+ m_check->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
+
+ m_label = new QLabel(m_labeltext, this);
+ QSizePolicy tmpPolicy = m_label->sizePolicy();
+ tmpPolicy.setVerticalPolicy(QSizePolicy::Fixed);
+ m_label->setSizePolicy(tmpPolicy);
+
+ m_edit = new QLineEdit(this);
+ tmpPolicy = m_edit->sizePolicy();
+ tmpPolicy.setVerticalPolicy(QSizePolicy::Fixed);
+ m_edit->setSizePolicy(tmpPolicy);
+
+ QHBoxLayout* layout = new QHBoxLayout(this);
+ layout->addWidget(m_check);
+ layout->addWidget(m_label);
+ layout->addWidget(m_edit);
+
+ connect(m_check, SIGNAL(toggled(bool)), m_edit, SLOT(setEnabled(bool)));
+ m_edit->installEventFilter(this);
+}
+
+bool FilterFormField::eventFilter(QObject* object, QEvent* event)
+{
+ if (object == m_edit && event->type() == QEvent::MouseButtonPress)
+ {
+ m_check->setChecked(true);
+ //stateChanged(Qt::Checked);
+ m_edit->setFocus(Qt::MouseFocusReason);
+ return true;
+ }
+ return false;
+}
+
+void FilterFormField::stateChanged(int state)
+{
+ bool old_check_state = m_check->blockSignals(true);
+ bool old_edit_state = m_edit->blockSignals(true);
+ switch (state)
+ {
+ case Qt::Unchecked:
+ m_check->setChecked(false);
+ m_edit->setEnabled(false);
+ break;
+ case Qt::PartiallyChecked:
+ break;
+ case Qt::Checked:
+ m_check->setChecked(true);
+ m_edit->setEnabled(true);
+ break;
+ }
+ m_check->blockSignals(old_check_state);
+ m_edit->blockSignals(old_edit_state);
+}
+
+void ToggleAllCheckBox::nextCheckState()
+{
+ switch(checkState())
+ {
+ case Qt::Unchecked:
+ setCheckState(Qt::Checked);
+ break;
+ case Qt::PartiallyChecked:
+ setCheckState(Qt::Unchecked);
+ break;
+ case Qt::Checked:
+ setCheckState(Qt::Unchecked);
+ break;
+ }
+
+}
+
+FilterDialog::FilterDialog(QWidget* parent, Qt::WindowFlags flags) :
+ QDialog(parent, flags),
+ m_toggleAll(nullptr),
+ m_filterString(QString()),
+ m_fieldCount(0),
+ m_fieldsCheckedCount(0)
+{
+ //init m_spawnFilterMap
+ for (int field = FSF_Name; field < FSF_Max; ++field)
+ {
+ if (field == FSF_Info) continue;
+
+ QString name = FilterStringFieldName[field];
+ m_spawnFilterMap[name] = "";
+ }
+ //starting with Head since Light is handled above
+ for (int field = FSIF_Head; field < FSIF_Max; ++field)
+ {
+ QString name = FilterStringInfoFieldName[field];
+ m_spawnFilterMap[name] = "";
+ }
+ m_spawnFilterMap[FSF_MINLEVEL_NAME] = "";
+ m_spawnFilterMap[FSF_MAXLEVEL_NAME] = "";
+
+ createForm();
+}
+
+FilterDialog::~FilterDialog()
+{ }
+
+void FilterDialog::createForm()
+{
+
+ const int colspc_x = 20;
+ const int colspc_y = 1;
+
+ QVBoxLayout* pageLayout = new QVBoxLayout(this);
+ QGridLayout* gridLayout = new QGridLayout();
+
+ // info/instructions
+ QLabel* tmpLabel = new QLabel("All fields except '" FSF_MINLEVEL_LABEL "' and '" FSF_MAXLEVEL_LABEL "' accept Regular Expression syntax.", this);
+ tmpLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ pageLayout->addWidget(tmpLabel);
+ tmpLabel = new QLabel("For an exact level match or matching multiple levels using a RegEx, use the 'Level' field.", this);
+ tmpLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ pageLayout->addWidget(tmpLabel);
+ tmpLabel = new QLabel("To limit to a simple level range (no RegEx), use the '" FSF_MINLEVEL_LABEL "' and '" FSF_MAXLEVEL_LABEL "' fields.", this);
+ tmpLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ pageLayout->addWidget(tmpLabel);
+ tmpLabel = new QLabel("Any fields left blank or not checked will not be matched against.", this);
+ tmpLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ pageLayout->addWidget(tmpLabel);
+
+ pageLayout->addItem(new QSpacerItem(colspc_x, 25));
+
+ //using an extra widget so the spacing lines up
+ QWidget* toggleAllWidget = new QWidget();
+ QHBoxLayout* toggleAllLayout = new QHBoxLayout(toggleAllWidget);
+ m_toggleAll = new ToggleAllCheckBox();
+ m_toggleAll->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ m_toggleAll->setTristate(true);
+ toggleAllLayout->addWidget(m_toggleAll);
+ toggleAllLayout->addWidget(new QLabel("Toggle All", this));
+ connect(m_toggleAll, SIGNAL(stateChanged(int)), this, SLOT(toggleAllToggled(int)));
+
+ pageLayout->addLayout(gridLayout);
+ gridLayout->addWidget(toggleAllWidget, 0, 0);
+
+ #define X(a, b) #a,
+ const QString labels[] = {
+ FILTERSTRINGFIELD_TABLE
+ };
+ #undef X
+
+ for (int field = FSF_Name; field < FSF_Max; ++field)
+ {
+ if (field == FSF_Info) continue;
+
+ QString name = FilterStringFieldName[field];
+ QString label = labels[field];
+ m_filterFields[name] = new FilterFormField(name, label);
+ m_fieldCount++;
+
+ }
+
+ #define X(a, b) #a,
+ const QString info_labels[] = {
+ FILTERSTRINGINFOFIELD_TABLE
+ };
+ #undef X
+
+ // Starting with head, since Light is already created above.
+ for (int field = FSIF_Head; field < FSIF_Max; ++field)
+ {
+ QString name = FilterStringInfoFieldName[field];
+ QString label = info_labels[field];
+ m_filterFields[name] = new FilterFormField(name, label);
+ m_fieldCount++;
+ }
+
+ //not part of normal regex string, but still part of filter
+ m_filterFields[FSF_MINLEVEL_NAME] = new FilterFormField(FSF_MINLEVEL_NAME, FSF_MINLEVEL_LABEL);
+ m_filterFields[FSF_MAXLEVEL_NAME] = new FilterFormField(FSF_MAXLEVEL_NAME, FSF_MAXLEVEL_LABEL);
+ m_fieldCount += 2;
+
+ const QString formFieldOrder[] = { "Name", "LastName", "Guild", "Race", "Class",
+ "Deity", "Level", FSF_MINLEVEL_NAME, FSF_MAXLEVEL_NAME, "X", "Y", "Z", "NPC", "Type",
+ "GM", "RTeam", "DTeam", "Spawn", "Light",
+ //Info fields
+ "H", "C", "A", "W", "G", "L", "F", "1", "2" };
+
+ int row = 1; //toggle all is row 0
+ int col = 0;
+ for (auto fieldname : formFieldOrder)
+ {
+ gridLayout->addWidget(m_filterFields[fieldname], row, col++);
+
+ connect(m_toggleAll, SIGNAL(stateChanged(int)), m_filterFields[fieldname], SLOT(stateChanged(int)));
+ connect(m_filterFields[fieldname]->m_check, SIGNAL(toggled(bool)), this, SLOT(fieldToggled(bool)));
+
+ if (fieldname == "Guild" || fieldname == "Deity" ||
+ fieldname == FSF_MAXLEVEL_NAME || fieldname == "Z" ||
+ fieldname == "GM" || fieldname == "Spawn" ||
+ fieldname == "Light" ||
+ fieldname == "A" || fieldname == "L")
+ {
+ row++;
+ col = 0;
+ }
+ }
+
+ //buttons
+ QHBoxLayout* buttonLayout = new QHBoxLayout();
+ pageLayout->addItem(new QSpacerItem(colspc_x, 25));
+ pageLayout->addLayout(buttonLayout);
+
+ QPushButton* resetButton = new QPushButton("Reset");
+ resetButton->setDefault(false);
+ resetButton->setAutoDefault(false);
+ buttonLayout->addWidget(resetButton);
+ connect(resetButton, SIGNAL(clicked()), this, SLOT(resetForm()));
+
+ buttonLayout->addItem(new QSpacerItem(colspc_x, colspc_y));
+
+ QPushButton* okButton = new QPushButton("Ok");
+ okButton->setDefault(false);
+ okButton->setAutoDefault(false);
+ buttonLayout->addWidget(okButton);
+ connect(okButton, SIGNAL(clicked()), this, SLOT(acceptDialog()));
+
+ QPushButton* cancelButton = new QPushButton("Cancel");
+ cancelButton->setDefault(false);
+ cancelButton->setAutoDefault(false);
+ buttonLayout->addWidget(cancelButton);
+ connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
+}
+
+void FilterDialog::setData(const QString filterString)
+{
+ m_spawnFilterString = filterString;
+
+ FilterString2FilterFieldMap(filterString, &m_spawnFilterMap);
+
+ resetForm();
+}
+
+void FilterDialog::resetForm()
+{
+ m_fieldsCheckedCount = 0;
+ for (int field = FSF_Name; field < FSF_Max; ++field)
+ {
+ if (field == FSF_Info) continue;
+
+ QString name = FilterStringFieldName[field];
+
+ m_filterFields[name]->m_edit->setText(m_spawnFilterMap[name]);
+
+ if (m_filterFields[name]->m_edit->text().length())
+ {
+ m_filterFields[name]->stateChanged(Qt::Checked);
+ m_fieldsCheckedCount++;
+ }
+ else
+ {
+ m_filterFields[name]->stateChanged(Qt::Unchecked);
+ }
+ }
+
+ //starting with Head, since Light was handled above
+ for (int field = FSIF_Head; field < FSIF_Max; ++field)
+ {
+ QString name = FilterStringInfoFieldName[field];
+ m_filterFields[name]->m_edit->setText(m_spawnFilterMap[name]);
+
+ if (m_filterFields[name]->m_edit->text().length())
+ {
+ m_filterFields[name]->stateChanged(Qt::Checked);
+ m_fieldsCheckedCount++;
+ }
+ else
+ {
+ m_filterFields[name]->stateChanged(Qt::Unchecked);
+ }
+ }
+
+ //not part of normal regex string, but still part of filter
+ m_filterFields[FSF_MINLEVEL_NAME]->m_edit->setText(m_spawnFilterMap[FSF_MINLEVEL_NAME]);
+ if (m_filterFields[FSF_MINLEVEL_NAME]->m_edit->text().length())
+ {
+ m_filterFields[FSF_MINLEVEL_NAME]->stateChanged(Qt::Checked);
+ m_fieldsCheckedCount++;
+ }
+ else
+ {
+ m_filterFields[FSF_MINLEVEL_NAME]->stateChanged(Qt::Unchecked);
+ }
+ m_filterFields[FSF_MAXLEVEL_NAME]->m_edit->setText(m_spawnFilterMap[FSF_MAXLEVEL_NAME]);
+ if (m_filterFields[FSF_MAXLEVEL_NAME]->m_edit->text().length())
+ {
+ m_filterFields[FSF_MAXLEVEL_NAME]->stateChanged(Qt::Checked);
+ m_fieldsCheckedCount++;
+ }
+ else
+ {
+ m_filterFields[FSF_MAXLEVEL_NAME]->stateChanged(Qt::Unchecked);
+ }
+
+ bool old_state = m_toggleAll->blockSignals(true);
+ if (m_fieldsCheckedCount == 0)
+ m_toggleAll->setCheckState(Qt::Unchecked);
+ else if (m_fieldsCheckedCount == m_fieldCount)
+ m_toggleAll->setCheckState(Qt::Checked);
+ else
+ m_toggleAll->setCheckState(Qt::PartiallyChecked);
+ m_toggleAll->blockSignals(old_state);
+
+}
+
+void FilterDialog::acceptDialog()
+{
+ FilterFieldMap map;
+
+ //if enabled, add to map
+ for (int field = FSF_Name; field < FSF_Max; ++field)
+ {
+ if (field == FSF_Info) continue;
+
+ QString name = FilterStringFieldName[field];
+ if (m_filterFields[name]->m_edit->isEnabled())
+ map[name] = m_filterFields[name]->m_edit->text();
+ }
+ //starting with Head since Light is handled above
+ for (int field = FSIF_Head; field < FSIF_Max; ++field)
+ {
+ QString name = FilterStringInfoFieldName[field];
+ if (m_filterFields[name]->m_edit->isEnabled())
+ map[name] = m_filterFields[name]->m_edit->text();
+ }
+ //not part of normal regex string, but still part of filter
+ if (m_filterFields[FSF_MINLEVEL_NAME]->m_edit->isEnabled())
+ map[FSF_MINLEVEL_NAME] = m_filterFields[FSF_MINLEVEL_NAME]->m_edit->text();
+
+ if (m_filterFields[FSF_MAXLEVEL_NAME]->m_edit->isEnabled())
+ map[FSF_MAXLEVEL_NAME] = m_filterFields[FSF_MAXLEVEL_NAME]->m_edit->text();
+
+
+ m_filterString = FilterFieldMap2FilterString(&map);
+
+ done(QDialog::Accepted);
+}
+
+void FilterDialog::fieldToggled(bool checked)
+{
+ if (checked)
+ m_fieldsCheckedCount++;
+ else
+ m_fieldsCheckedCount--;
+
+ bool old_state = m_toggleAll->blockSignals(true);
+ if (m_fieldsCheckedCount == m_fieldCount)
+ m_toggleAll->setCheckState(Qt::Checked);
+ else if (m_fieldsCheckedCount == 0)
+ m_toggleAll->setCheckState(Qt::Unchecked);
+ else
+ m_toggleAll->setCheckState(Qt::PartiallyChecked);
+ m_toggleAll->blockSignals(old_state);
+}
+
+void FilterDialog::toggleAllToggled(int state)
+{
+ if (sender() != m_toggleAll)
+ return;
+
+ switch(state)
+ {
+ case Qt::Checked:
+ m_fieldsCheckedCount = m_fieldCount;
+ break;
+ case Qt::Unchecked:
+ m_fieldsCheckedCount = 0;
+ break;
+ case Qt::PartiallyChecked:
+ m_toggleAll->setCheckState(Qt::Checked);
+ m_fieldsCheckedCount = m_fieldCount;
+ }
+ emit stateChanged(m_toggleAll->checkState());
+}
+
+QString FilterDialog::getFilter(QWidget* parent, const QString& title,
+ const QString& filterString, bool* ok, Qt::WindowFlags flags,
+ Qt::InputMethodHints inputMethodHints)
+{
+ FilterDialog* dlg = new FilterDialog(parent, flags);
+ dlg->setWindowTitle(title);
+ dlg->setData(filterString);
+
+ const int ret = dlg->exec();
+ if (ok)
+ *ok = ret;
+
+ QString result;
+ if (ok)
+ result = dlg->m_filterString;
+
+ dlg->deleteLater();
+ return result;
+}
+
+
+void FilterString2FilterFieldMap(const QString filterString, FilterFieldMap* map)
+{
+ if (!map || !filterString.length())
+ return;
+
+ QString levelSuffix;
+ QString regex;
+ int minLevel = -1;
+ int maxLevel = -1;
+
+ int split = filterString.lastIndexOf(';');
+ if (split == -1)
+ {
+ regex = filterString;
+ }
+ else
+ {
+ regex = filterString.left(split);
+ levelSuffix = filterString.mid(split+1);
+ }
+
+
+ // parse level range string
+ if (levelSuffix.length())
+ {
+ auto range = levelSuffix.split('-');
+ bool ok = false;
+
+ if (range.size() == 1)
+ {
+ //no dash, only a single level specified - treat as exact match
+ int level = range[0].toInt(&ok);
+ if (ok)
+ {
+ minLevel = level;
+ maxLevel = level;
+ }
+ else
+ {
+ seqWarn("Could not parse level: %s", range[0].toLatin1().data());
+ }
+ }
+ else if (range.size() == 2)
+ {
+ //one dash, two fields - treat as range
+ int level = range[0].toInt(&ok);
+ if (ok)
+ minLevel = level;
+ else
+ seqWarn("Could not parse min level: %s", range[0].toLatin1().data());
+
+ ok = false;
+ level = range[1].toInt(&ok);
+ if (ok)
+ maxLevel = level;
+ else
+ seqWarn("Could not parse max level: %s", range[0].toLatin1().data());
+
+
+ // if range wasn't fully/correctly specified, use defaults
+ minLevel = (minLevel > -1) ? minLevel : 0;
+ maxLevel = (maxLevel > -1) ? maxLevel : SHRT_MAX;
+
+ }
+ else
+ {
+ seqWarn("Ignoring malformed level range string.");
+ }
+
+ if (maxLevel < minLevel)
+ {
+ int tmp = maxLevel;
+ maxLevel = minLevel;
+ minLevel = tmp;
+ }
+ }
+
+
+ //process filter string and set form/map fields
+ QStringList tokens = regex.split(":");
+
+ QStringList::const_iterator itr = tokens.begin();
+ for (; itr < tokens.end(); ++itr)
+ {
+ QString name = *itr;
+ if (!map->contains(name))
+ {
+ if (!name.length() && itr == tokens.end() - 1)
+ {
+ //filter string has an ending : that we can ignore
+ continue;
+ }
+
+ // Info isn't in the map, but we need to process it.
+ // Also, if there are multi-field wildcards, it could parse
+ // as a name of ".*"
+ // Otherwise, skip any unknown fields
+ if (name != "Info" && name != ".*")
+ {
+ seqWarn("Ignoring unknown filter string field: %s", name.toLatin1().data());
+ ++itr; // skip this field's data
+ continue;
+ }
+ }
+
+ // handle multi-field wildcards
+ if (name == ".*")
+ {
+ if (++itr == tokens.end())
+ break;
+
+ QString value = *itr;
+ if (map->contains(value) || value == "Info")
+ {
+ // value is the next specified field name, so we'll back the
+ // iterator back to 'name == ".*"' and restart the loop
+ --itr;
+ continue;
+ }
+ else
+ {
+ // we have a value but we don't know what field it belongs to.
+ // If the next field is set, we could figure it out by working
+ // backwards, but it's probably not worth the effort.
+ // So we're just going to warn and ignore it
+ seqWarn("A match value of \"%s\" was found, but no field was specified. Ignoring.",
+ value.toLatin1().data());
+ continue;
+ }
+ }
+
+ // get field data
+ if (++itr == tokens.end())
+ break;
+ QString value = *itr;
+ if (!value.trimmed().length())
+ continue;
+
+ if (name == "Name" && (value == "Door" || value == "Drop"))
+ {
+ //we add a colon to door and drop names, so it makes
+ //parsing a little more complicated.
+ value += ":";
+ if (++itr == tokens.end())
+ break;
+ value += *itr;
+ if (value.trimmed().length() <= 1)
+ continue;
+ }
+
+ if (name == "Info")
+ {
+ //Info field contains space-separated slot:item pairs, and the
+ //items themselves can also contain spaces. So special parsing
+ //is needed.
+ bool info_done = false;
+ QString subfield_name = value;
+ while (itr != tokens.end() && !info_done)
+ {
+ //strip multi field wildcards from name (note, order matters here)
+ subfield_name = subfield_name.remove("( | .* )");
+ subfield_name = subfield_name.remove(".*");
+
+ // Check the name against valid sub-fields, because we could
+ // be past the Info field and into the next main field
+ bool is_info_field = false;
+ for (int field = FSIF_Light; field < FSIF_Max; ++field)
+ {
+ if (subfield_name == FilterStringInfoFieldName[field])
+ {
+ is_info_field = true;
+ break;
+ }
+ }
+
+ if (!is_info_field)
+ {
+ info_done = true;
+ continue;
+ }
+
+ // get value
+ if (++itr == tokens.end())
+ break;
+ value = *itr;
+ if (!value.trimmed().length())
+ continue;
+
+ //replace multi field wildcards in value/next
+ value = value.replace("( | .* )", " ");
+
+ int delim = value.lastIndexOf(' ');
+ QString next_subfield_name = value.mid(delim+1);
+ value = value.left(delim);
+
+ (*map)[subfield_name] = value.trimmed();
+
+ subfield_name = next_subfield_name;
+
+ }
+
+ if (itr == tokens.end())
+ break;
+ }
+ else
+ {
+ (*map)[name] = value.trimmed();
+ }
+ }
+
+ if (minLevel > -1)
+ (*map)[FSF_MINLEVEL_NAME] = QString::number(minLevel);
+
+ if (maxLevel > -1)
+ (*map)[FSF_MAXLEVEL_NAME] = QString::number(maxLevel);
+
+}
+
+QString FilterFieldMap2FilterString(FilterFieldMap* map)
+{
+ if (!map)
+ return QString();
+
+ QString filterString;
+ bool wildcard = false;
+ bool has_first_match = false;
+
+ for (int field = FSF_Name; field < FSF_Max; ++field)
+ {
+ QString name = FilterStringFieldName[field];
+
+ if (name == "Info")
+ {
+ //info subfields need special handling
+ bool info_added = false;
+ bool info_wildcard = false;
+ for (int info_field = FSIF_Light; info_field < FSIF_Max; ++info_field)
+ {
+ QString subfield_name = FilterStringInfoFieldName[info_field];
+ if (!map->contains(subfield_name) || !(*map)[subfield_name].trimmed().length())
+ {
+ if (!info_wildcard)
+ {
+ info_wildcard = true;
+ }
+ continue;
+ }
+
+ QString value = (*map)[subfield_name];
+ value = value.trimmed();
+
+ if (!info_added)
+ {
+ if (wildcard)
+ {
+ wildcard = false;
+ filterString += ".*:Info:";
+ }
+ else
+ {
+ filterString += "Info:";
+ }
+ info_added = true;
+ }
+
+ if (info_wildcard)
+ {
+ info_wildcard = false;
+ // we need to handle 2 cases here
+ // 1. match-field ignore-field match-field
+ // 2. match-field matchfield
+ // If we naively insert .* like we do elsewhere, we'll
+ // wind up with " .* " which will never match case 2.
+ // But we also don't want to just not include spaces
+ // in the match, because we don't want to accidentally
+ // match a different field/value (especially with short
+ // field names like C or A.
+ if (filterString.length() && filterString.endsWith(" "))
+ {
+ filterString.chop(1);
+ filterString += "( | .* )";
+ }
+ else
+ {
+ filterString += ".*";
+ }
+ }
+
+ filterString += subfield_name;
+ filterString += ":";
+ filterString += value;
+ filterString += " ";
+ }
+ //end of Info loop, tidy up
+ if (info_added)
+ {
+ if (info_wildcard)
+ {
+ info_wildcard = false;
+ filterString += ".*:";
+ }
+ else
+ {
+ filterString += ":";
+ }
+ }
+ }
+ else
+ {
+ if (!map->contains(name) || !(*map)[name].trimmed().length())
+ {
+ if (has_first_match && !wildcard)
+ {
+ wildcard = true;
+ }
+ continue;
+ }
+
+ QString value = (*map)[name];
+ value = value.trimmed();
+
+ has_first_match = true;
+
+ if (wildcard)
+ {
+ wildcard = false;
+ filterString += ".*:";
+ }
+ filterString += name;
+ filterString += ":";
+
+ //Remove/change :'s depending on the field
+ if (name == "Spawn")
+ filterString += value.replace(':', '.');
+ else if (name != "Name")
+ filterString += value.remove(':');
+ else
+ filterString += value;
+
+ filterString += ":";
+
+ }
+ }
+
+ //min/max level are not part of normal regex string, but still part of filter
+ int minLevel = -1;
+ int maxLevel = -1;
+
+ if (map->contains(FSF_MINLEVEL_NAME))
+ {
+ QString value = (*map)[FSF_MINLEVEL_NAME];
+ value = value.trimmed();
+ bool ok = false;
+ int level = value.toInt(&ok);
+ if (ok)
+ minLevel = level;
+ }
+
+ if (map->contains(FSF_MAXLEVEL_NAME))
+ {
+ QString value = (*map)[FSF_MAXLEVEL_NAME];
+ value = value.trimmed();
+ bool ok = false;
+ int level = value.toInt(&ok);
+ if (ok)
+ maxLevel = level;
+ }
+
+ if (minLevel >= 0 || maxLevel >= 0)
+ {
+ minLevel = (minLevel >= 0) ? minLevel : 0;
+ maxLevel = (maxLevel >= 0) ? maxLevel : SHRT_MAX;
+
+ if (maxLevel < minLevel)
+ {
+ int tmp = maxLevel;
+ maxLevel = minLevel;
+ minLevel = tmp;
+ }
+
+ filterString += ";";
+ filterString += QString::number(minLevel);
+ filterString += "-";
+ filterString += QString::number(maxLevel);
+ }
+
+ return filterString;
+}
+
+
+#ifndef QMAKEBUILD
+#include "filterlistwindow.moc"
+#endif
Added: showeq/trunk/src/filterlistwind...
[truncated message content] |