|
From: <cn...@us...> - 2024-07-17 14:27:34
|
Revision: 1497
http://sourceforge.net/p/seq/svn/1497
Author: cn187
Date: 2024-07-17 14:27:28 +0000 (Wed, 17 Jul 2024)
Log Message:
-----------
Add Filter List/Edit Window
Modified Paths:
--------------
showeq/branches/cn187_devel/src/Makefile.am
showeq/branches/cn187_devel/src/editor.cpp
showeq/branches/cn187_devel/src/filter.cpp
showeq/branches/cn187_devel/src/filter.h
showeq/branches/cn187_devel/src/filtermgr.cpp
showeq/branches/cn187_devel/src/filtermgr.h
showeq/branches/cn187_devel/src/interface.cpp
showeq/branches/cn187_devel/src/interface.h
showeq/branches/cn187_devel/src/spawnlistcommon.cpp
Added Paths:
-----------
showeq/branches/cn187_devel/src/filterlistwindow.cpp
showeq/branches/cn187_devel/src/filterlistwindow.h
showeq/branches/cn187_devel/src/toolbaricons.cpp
showeq/branches/cn187_devel/src/toolbaricons.h
Modified: showeq/branches/cn187_devel/src/Makefile.am
===================================================================
--- showeq/branches/cn187_devel/src/Makefile.am 2024-07-15 01:00:22 UTC (rev 1496)
+++ showeq/branches/cn187_devel/src/Makefile.am 2024-07-17 14:27:28 UTC (rev 1497)
@@ -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/branches/cn187_devel/src/editor.cpp
===================================================================
--- showeq/branches/cn187_devel/src/editor.cpp 2024-07-15 01:00:22 UTC (rev 1496)
+++ showeq/branches/cn187_devel/src/editor.cpp 2024-07-17 14:27:28 UTC (rev 1497)
@@ -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/branches/cn187_devel/src/filter.cpp
===================================================================
--- showeq/branches/cn187_devel/src/filter.cpp 2024-07-15 01:00:22 UTC (rev 1496)
+++ showeq/branches/cn187_devel/src/filter.cpp 2024-07-17 14:27:28 UTC (rev 1497)
@@ -45,6 +45,19 @@
//#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 QObject
@@ -182,6 +195,27 @@
(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);
@@ -193,9 +227,9 @@
// 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);
@@ -474,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)
@@ -755,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()
Modified: showeq/branches/cn187_devel/src/filter.h
===================================================================
--- showeq/branches/cn187_devel/src/filter.h 2024-07-15 01:00:22 UTC (rev 1496)
+++ showeq/branches/cn187_devel/src/filter.h 2024-07-17 14:27:28 UTC (rev 1497)
@@ -43,7 +43,73 @@
#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;
@@ -72,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(); }
@@ -107,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);
@@ -136,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/branches/cn187_devel/src/filterlistwindow.cpp (from rev 1496, showeq/branches/cn187_devel/src/filtermgr.cpp)
===================================================================
--- showeq/branches/cn187_devel/src/filterlistwindow.cpp (rev 0)
+++ showeq/branches/cn187_devel/src/filterlistwindow.cpp 2024-07-17 14:27:28 UTC (rev 1497)
@@ -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/branches/cn187_devel/src/filterlistwindow.h
===================================================================
--- showeq/branches/cn187_devel/src/filterlistwindow.h (rev 0)
+++ showeq/branches/cn187_devel/src/filterlistwindow.h 2024-07-17 14:27:28 UTC (rev 1497)
@@ -0,0 +1,171 @@
+/*
+ * filterlistwindow.h
+ * 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
+ */
+
+#ifndef FILTERLISTWINDOW_H
+#define FILTERLISTWINDOW_H
+
+#include <QDialog>
+#include <QMainWindow>
+#include <QAbstractItemModel>
+#include <QEvent>
+#include <QStatusBar>
+
+#include "filter.h"
+
+typedef QHash<QString, QString> FilterFieldMap;
+
+class FilterModel;
+
+class FilterListWindow : public QMainWindow
+{
+ Q_OBJECT
+
+ public:
+ FilterListWindow(QString filename, QWidget* parent=nullptr,
+ Qt::WindowFlags flags = Qt::WindowFlags());
+ ~FilterListWindow();
+
+ protected:
+ QWidget* createTab(QString name);
+ void setTabLabel(uint8_t type, int count);
+
+ protected slots:
+ void editItem(QModelIndex index);
+ void tabButtonClicked(QAction* action);
+ void createItem(uint8_t type);
+ void deleteItem(uint8_t type);
+ void load();
+ void loadFile();
+ void save();
+ void saveAs();
+
+ private:
+ QString m_filename;
+ QTabWidget* m_tabWidget;
+ QStatusBar* m_statusBar;
+ Filters* m_filters;
+ FilterTypes* m_types;
+ QHash<uint8_t, QTreeView*> m_views;
+ QHash<uint8_t, FilterModel*> m_models;
+
+
+};
+
+class FilterModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+ public:
+ FilterModel(Filters* filters, uint8_t type, QObject* parent=nullptr);
+ virtual ~FilterModel();
+
+ virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+ virtual QModelIndex parent(const QModelIndex &index) const;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const;
+
+ void addFilter(QString filterPattern);
+ void removeFilter(QModelIndex selection);
+
+ private:
+ Filters* m_filters;
+ uint8_t m_type;
+};
+
+
+class FilterFormField : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ FilterFormField(QString name, QString labeltext = QString(), QWidget* parent=nullptr);
+
+ QString m_name;
+ QString m_labeltext;
+ QCheckBox* m_check;
+ QLabel* m_label;
+ QLineEdit* m_edit;
+
+ public slots:
+ bool eventFilter(QObject* object, QEvent* event);
+ void stateChanged(int state);
+};
+
+//SubClassing QCheckBox so we can control the sequence of check/uncheck/partial when
+//clicking "Toggle All"
+class ToggleAllCheckBox : public QCheckBox
+{
+ protected:
+ virtual void nextCheckState() override;
+
+};
+
+
+
+class FilterDialog : public QDialog
+{
+ Q_OBJECT
+
+ public:
+
+ static QString getFilter(QWidget* parent, const QString& title,
+ const QString& filterString, bool* ok=nullptr,
+ Qt::WindowFlags flags = Qt::WindowFlags(),
+ Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
+
+ protected:
+ FilterDialog(QWidget* parent=nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
+ ~FilterDialog();
+
+ void setData(const QString filterString);
+ void createForm();
+
+ QHash<QString, FilterFormField*> m_filterFields;
+ ToggleAllCheckBox* m_toggleAll;
+
+ QString m_spawnFilterString;
+ FilterFieldMap m_spawnFilterMap;
+
+ QString m_filterString;
+ int m_fieldCount;
+ int m_fieldsCheckedCount;
+ bool m_hasTrailingColon;
+
+ signals:
+ void stateChanged(int state);
+
+ protected slots:
+ void resetForm();
+ void acceptDialog();
+ void fieldToggled(bool checked);
+ void toggleAllToggled(int state);
+
+};
+
+// helper functions
+void FilterString2FilterFieldMap(const QString filterString, FilterFieldMap* map);
+QString FilterFieldMap2FilterString(FilterFieldMap* map);
+
+
+#endif
Modified: showeq/branches/cn187_devel/src/filtermgr.cpp
===============================================================...
[truncated message content] |