Added property name generation system using dictionary attacks and added a UI dialog that allows you to search for property names and apply them to templates

This commit is contained in:
Aruki 2018-02-13 00:30:35 -07:00
parent ebab154a38
commit 3d72c9e4b2
24 changed files with 21666 additions and 335 deletions

20072
resources/WordList.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -231,7 +231,8 @@ HEADERS += \
IProgressNotifier.h \
IUIRelay.h \
Resource/CResTypeFilter.h \
GameProject/COpeningBanner.h
GameProject/COpeningBanner.h \
Resource/Script/CPropertyNameGenerator.h
# Source Files
SOURCES += \
@ -340,4 +341,5 @@ SOURCES += \
CompressionUtil.cpp \
IUIRelay.cpp \
GameProject\COpeningBanner.cpp \
IProgressNotifier.cpp
IProgressNotifier.cpp \
Resource/Script/CPropertyNameGenerator.cpp

View File

@ -30,7 +30,7 @@ public:
mTaskCount = Math::Max(mTaskCount, TaskIndex + 1);
}
void Report(int StepIndex, int StepCount, const TString& rkStepDesc)
void Report(int StepIndex, int StepCount, const TString& rkStepDesc = "")
{
ASSERT(mTaskCount >= 1);

View File

@ -6,6 +6,7 @@
class IUIRelay
{
public:
virtual void AsyncMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage) = 0;
virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) = 0;
};
extern IUIRelay *gpUIRelay;

View File

@ -223,7 +223,11 @@ void CMasterTemplate::RenameProperty(IPropertyTemplate *pTemp, const TString& rk
{
u32 ID = pTemp->PropertyID();
if (ID <= 0xFF) ID = CreatePropertyID(pTemp);
RenameProperty(ID, rkNewName);
}
void CMasterTemplate::RenameProperty(u32 ID, const TString& rkNewName)
{
// Master name list
auto NameIt = smPropertyNames.find(ID);
TString Original;
@ -249,17 +253,15 @@ void CMasterTemplate::RenameProperty(IPropertyTemplate *pTemp, const TString& rk
}
}
std::vector<TString> CMasterTemplate::XMLsUsingID(u32 ID)
void CMasterTemplate::XMLsUsingID(u32 ID, std::vector<TString>& rOutList)
{
auto InfoIt = smIDMap.find(ID);
if (InfoIt != smIDMap.end())
{
const SPropIDInfo& rkInfo = InfoIt->second;
return rkInfo.XMLList;
rOutList = rkInfo.XMLList;
}
else
return std::vector<TString>();
}
const std::vector<IPropertyTemplate*>* CMasterTemplate::TemplatesWithMatchingID(IPropertyTemplate *pTemp)

View File

@ -69,7 +69,8 @@ public:
static u32 CreatePropertyID(IPropertyTemplate *pTemp);
static void AddProperty(IPropertyTemplate *pTemp, const TString& rkTemplateName = "");
static void RenameProperty(IPropertyTemplate *pTemp, const TString& rkNewName);
static std::vector<TString> XMLsUsingID(u32 ID);
static void RenameProperty(u32 ID, const TString& rkNewName);
static void XMLsUsingID(u32 ID, std::vector<TString>& rOutList);
static const std::vector<IPropertyTemplate*>* TemplatesWithMatchingID(IPropertyTemplate *pTemp);
};

View File

@ -0,0 +1,211 @@
#include "CPropertyNameGenerator.h"
#include "IUIRelay.h"
#include "Core/Resource/Factory/CTemplateLoader.h"
#include "Core/Resource/Script/CMasterTemplate.h"
#include <Common/Hash/CCRC32.h>
/** Default constructor */
CPropertyNameGenerator::CPropertyNameGenerator()
: mWordListLoadStarted(false)
, mWordListLoadFinished(false)
, mIsRunning(false)
, mFinishedRunning(false)
{
}
void CPropertyNameGenerator::Warmup()
{
// Clear output from previous runs
ASSERT(!mWordListLoadStarted || mWordListLoadFinished);
mWordListLoadFinished = false;
mWordListLoadStarted = true;
mWords.clear();
// Load the word list from the file
FILE* pListFile = fopen("../resources/WordList.txt", "r");
ASSERT(pListFile);
while (!feof(pListFile))
{
char WordBuffer[256];
fgets(&WordBuffer[0], 256, pListFile);
// Capitalize first letter
if (WordBuffer[0] >= 'a' && WordBuffer[0] <= 'z')
{
WordBuffer[0] -= 0x20;
}
SWord Word;
Word.Word = TString(WordBuffer).Trimmed();
Word.Usages = 0;
mWords.push_back(Word);
}
fclose(pListFile);
mWordListLoadFinished = true;
}
void CPropertyNameGenerator::Generate(const SPropertyNameGenerationParameters& rkParams, IProgressNotifier* pProgress)
{
// Make sure all prerequisite data is loaded!
ASSERT(!mIsRunning);
ASSERT(rkParams.TypeNames.size() > 0);
mGeneratedNames.clear();
mIsRunning = true;
mFinishedRunning = false;
// If we haven't loaded the word list yet, load it.
// If we are still loading the word list, wait until we're finished.
if (!mWordListLoadFinished)
{
if (mWordListLoadStarted)
while (!mWordListLoadFinished) {}
else
Warmup();
}
// Calculate the number of steps involved in this task.
const int kNumWords = mWords.size();
const int kMaxWords = rkParams.MaxWords;
int TestsDone = 0;
int TotalTests = 1;
for (int i = 0; i < kMaxWords; i++)
TotalTests *= kNumWords;
pProgress->SetOneShotTask("Generating property names");
pProgress->Report(TestsDone, TotalTests);
// Configure params needed to run the name generation!
bool WriteToLog = rkParams.PrintToLog;
bool SaveResults = true;
// The prefix only needs to be hashed this one time
CCRC32 PrefixHash;
PrefixHash.Hash( *rkParams.Prefix );
// Use a stack to keep track of the current word we are on. We can use this
// to cache the hash of a word and then re-use it later instead of recaculating
// the same hashes over and over. Init the stack with the first word.
struct SWordCache
{
int WordIndex;
CCRC32 Hash;
};
std::vector<SWordCache> WordCache;
SWordCache FirstWord { -1, CCRC32() };
WordCache.push_back(FirstWord);
while ( true )
{
// Increment the current word, handle wrapping back to 0, and update cached hashes as needed.
int RecalcIndex = WordCache.size() - 1;
WordCache.back().WordIndex++;
while (WordCache[RecalcIndex].WordIndex >= kNumWords)
{
WordCache[RecalcIndex].WordIndex = 0;
if (RecalcIndex > 0)
{
RecalcIndex--;
WordCache[RecalcIndex].WordIndex++;
}
else
{
SWordCache NewWord { 0, CCRC32() };
WordCache.push_back(NewWord);
}
}
// If we've hit the word limit, break out and end the name generation system.
if (WordCache.size() > kMaxWords)
break;
// Now that all words are updated, calculate the new hashes.
CCRC32 LastValidHash = (RecalcIndex > 0 ? WordCache[RecalcIndex-1].Hash : PrefixHash);
for (; RecalcIndex < WordCache.size(); RecalcIndex++)
{
int Index = WordCache[RecalcIndex].WordIndex;
LastValidHash.Hash( *mWords[Index].Word );
WordCache[RecalcIndex].Hash = LastValidHash;
}
// We got our hash yay! Now hash the suffix and then we can test with each type name
CCRC32 BaseHash = LastValidHash;
BaseHash.Hash( *rkParams.Suffix );
for (int TypeIdx = 0; TypeIdx < rkParams.TypeNames.size(); TypeIdx++)
{
CCRC32 FullHash = BaseHash;
FullHash.Hash( *rkParams.TypeNames[TypeIdx] );
u32 PropertyID = FullHash.Digest();
// Check if this hash is a property ID - it's valid if there are any XMLs using this ID
SGeneratedPropertyName PropertyName;
CMasterTemplate::XMLsUsingID(PropertyID, PropertyName.XmlList);
if (PropertyName.XmlList.size() > 0)
{
// Generate a string with the complete name. (We wait to do this until now to avoid needless string allocation)
PropertyName.Name = rkParams.Prefix;
for (int WordIdx = 0; WordIdx < WordCache.size(); WordIdx++)
{
int Index = WordCache[WordIdx].WordIndex;
PropertyName.Name += mWords[Index].Word;
}
PropertyName.Name += rkParams.Suffix;
PropertyName.Type = rkParams.TypeNames[TypeIdx];
PropertyName.ID = PropertyID;
if (SaveResults)
{
mGeneratedNames.push_back(PropertyName);
// Check if we have too many saved results. This can cause memory issues and crashing.
// If we have too many saved results, then to avoid crashing we will force enable log output.
if (mGeneratedNames.size() > 9999)
{
gpUIRelay->AsyncMessageBox("Warning", "There are over 10,000 results. To avoid memory issues, results will no longer print to the screen. Check the log for the rest of the output.");
WriteToLog = true;
SaveResults = false;
}
}
// Log this out
if ( WriteToLog )
{
TString DelimitedXmlList;
for (int XmlIdx = 0; XmlIdx < PropertyName.XmlList.size(); XmlIdx++)
{
DelimitedXmlList += PropertyName.XmlList[XmlIdx] + "\n";
}
TString LogMsg = TString::Format("%s [%s] : 0x%08X\n", *PropertyName.Name, *PropertyName.Type, PropertyName.ID) + DelimitedXmlList;
Log::Write(LogMsg);
}
}
}
// Every 250 tests, check with the progress notifier. Update the progress
// bar and check whether the user has requested to cancel the operation.
TestsDone++;
if ( (TestsDone % 250) == 0 )
{
if (pProgress->ShouldCancel())
break;
pProgress->Report(TestsDone, TotalTests);
}
}
mIsRunning = false;
mFinishedRunning = true;
}

View File

@ -0,0 +1,86 @@
#ifndef CPROPERTYNAMEGENERATOR_H
#define CPROPERTYNAMEGENERATOR_H
#include "Core/IProgressNotifier.h"
#include <Common/Common.h>
/** Parameters for using the name generator */
struct SPropertyNameGenerationParameters
{
/** Maximum number of words per name; name generation will complete when all possibilities have been checked */
int MaxWords;
/** Prefix to include at the beginning of every name */
TString Prefix;
/** Suffix to include at the end of every name */
TString Suffix;
/** List of valid type suffixes */
std::vector<TString> TypeNames;
/** Whether to print the output from the generation process to the log */
bool PrintToLog;
};
/** A generated property name */
struct SGeneratedPropertyName
{
TString Name;
TString Type;
u32 ID;
std::vector<TString> XmlList;
};
/** Generates property names and validates them against know property IDs. */
class CPropertyNameGenerator
{
/** Whether we have started loading the word list */
bool mWordListLoadStarted;
/** Whether the word list has been fully loaded */
bool mWordListLoadFinished;
/** Whether the generation process is running */
bool mIsRunning;
/** Whether the generation process finished running */
bool mFinishedRunning;
/** List of words */
struct SWord
{
TString Word;
int Usages;
};
std::vector<SWord> mWords;
/** List of output generated property names */
std::list<SGeneratedPropertyName> mGeneratedNames;
/** List of word indices */
std::vector<int> mWordIndices;
public:
/** Default constructor */
CPropertyNameGenerator();
/** Prepares the generator for running name generation */
void Warmup();
/** Run the name generation system */
void Generate(const SPropertyNameGenerationParameters& rkParams, IProgressNotifier* pProgressNotifier);
/** Accessors */
bool IsRunning() const
{
return mIsRunning;
}
const std::list<SGeneratedPropertyName>& GetOutput() const
{
return mGeneratedNames;
}
};
#endif // CPROPERTYNAMEGENERATOR_H

View File

@ -315,10 +315,10 @@ const char* HashablePropTypeName(EPropertyType Prop)
case eFloatProperty: return "float";
case eStringProperty: return "string";
case eColorProperty: return "Color";
case eVector3Property: return "Vector3f";
case eSoundProperty: return "SfxId";
case eVector3Property: return "Vector";
case eSoundProperty: return "sound";
case eAssetProperty: return "asset";
case eMayaSplineProperty: return "MayaSpline";
case eMayaSplineProperty: return "spline";
// All other types are either invalid or need a custom reimplementation because they can return multiple strings (like struct)
default:

View File

@ -338,7 +338,7 @@ public:
const char* GetTypeNameString() const
{
return (Game() < eCorruptionProto ? "AnimationParameters" : "CharacterAnimationSet");
return (Game() < eCorruptionProto ? "AnimationSet" : "CharacterAnimationSet");
}
IMPLEMENT_TEMPLATE_CLONE(TCharacterTemplate)

View File

@ -0,0 +1,307 @@
#include "CGeneratePropertyNamesDialog.h"
#include "ui_CGeneratePropertyNamesDialog.h"
#include "Editor/Widgets/CCheckableTreeWidgetItem.h"
#include "UICommon.h"
#include <Core/Resource/Cooker/CTemplateWriter.h>
#include <QtConcurrent/QtConcurrent>
#include <iterator>
CGeneratePropertyNamesDialog::CGeneratePropertyNamesDialog(QWidget* pParent)
: QDialog(pParent)
, mpUI( new Ui::CGeneratePropertyNamesDialog )
, mFutureWatcher( this )
, mRunningNameGeneration( false )
, mCanceledNameGeneration( false )
{
mpUI->setupUi(this);
mNotifier.SetProgressBar( mpUI->ProgressBar );
connect( mpUI->AddSuffixButton, SIGNAL(pressed()), this, SLOT(AddSuffix()) );
connect( mpUI->RemoveSuffixButton, SIGNAL(pressed()), this, SLOT(DeleteSuffix()) );
connect( mpUI->StartButton, SIGNAL(pressed()), this, SLOT(StartGeneration()) );
connect( mpUI->CancelButton, SIGNAL(pressed()), this, SLOT(CancelGeneration()) );
connect( mpUI->CheckAllButton, SIGNAL(pressed()), this, SLOT(CheckAll()) );
connect( mpUI->UncheckAllButton, SIGNAL(pressed()), this, SLOT(UncheckAll()) );
connect( mpUI->ApplyButton, SIGNAL(pressed()), this, SLOT(ApplyChanges()) );
connect( mpUI->OutputTreeWidget, SIGNAL(CheckStateChanged(QTreeWidgetItem*)),
this, SLOT(OnTreeItemChecked(QTreeWidgetItem*)) );
connect( mpUI->OutputTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)),
this, SLOT(OnTreeItemDoubleClicked(QTreeWidgetItem*)) );
// Configure default tree view split sizes
// I don't know why it needs to be multiplied by 1.5, it just does
int TreeWidth = mpUI->OutputTreeWidget->width();
mpUI->OutputTreeWidget->setColumnWidth(0, TreeWidth * 1.5);
mpUI->OutputTreeWidget->setHeaderHidden(false);
// Allow the generator to initialize in the background while the user is getting set up
QtConcurrent::run(&mGenerator, &CPropertyNameGenerator::Warmup);
}
CGeneratePropertyNamesDialog::~CGeneratePropertyNamesDialog()
{
delete mpUI;
}
/** Close event override */
void CGeneratePropertyNamesDialog::closeEvent(QCloseEvent*)
{
if (mRunningNameGeneration)
{
CancelGeneration();
}
}
/** Add an item to the suffix list */
void CGeneratePropertyNamesDialog::AddSuffix()
{
QListWidgetItem* pNewItem = new QListWidgetItem("New Suffix", mpUI->TypeSuffixesListWidget);
pNewItem->setFlags( Qt::ItemIsEditable |
Qt::ItemIsEnabled |
Qt::ItemIsSelectable );
mpUI->TypeSuffixesListWidget->setCurrentItem(pNewItem, QItemSelectionModel::ClearAndSelect);
mpUI->TypeSuffixesListWidget->editItem(pNewItem);
}
/** Deletes an item from the suffix list */
void CGeneratePropertyNamesDialog::DeleteSuffix()
{
if (mpUI->TypeSuffixesListWidget->selectedItems().size() > 0)
{
int Row = mpUI->TypeSuffixesListWidget->currentRow();
delete mpUI->TypeSuffixesListWidget->takeItem(Row);
}
}
/** Start name generation */
void CGeneratePropertyNamesDialog::StartGeneration()
{
ASSERT(!mRunningNameGeneration);
mRunningNameGeneration = true;
mCanceledNameGeneration = false;
mTaskOutput.clear();
mCheckedItems.clear();
mpUI->OutputTreeWidget->clear();
// Configure the generator
SPropertyNameGenerationParameters Params;
for (int RowIdx = 0; RowIdx < mpUI->TypeSuffixesListWidget->count(); RowIdx++)
{
QString ItemText = mpUI->TypeSuffixesListWidget->item(RowIdx)->text();
Params.TypeNames.push_back( TO_TSTRING(ItemText) );
}
Params.MaxWords = mpUI->NumWordsSpinBox->value();
Params.Prefix = TO_TSTRING( mpUI->PrefixLineEdit->text() );
Params.Suffix = TO_TSTRING( mpUI->SuffixLineEdit->text() );
Params.PrintToLog = mpUI->LogOutputCheckBox->isChecked();
// Run the task and configure ourselves so we can update correctly
connect( &mFutureWatcher, SIGNAL(finished()), this, SLOT(GenerationComplete()) );
mFuture = QtConcurrent::run(&mGenerator, &CPropertyNameGenerator::Generate, Params, &mNotifier);
mFutureWatcher.setFuture(mFuture);
mUpdateTimer.start(500);
connect( &mUpdateTimer, SIGNAL(timeout()), this, SLOT(CheckForNewResults()) );
UpdateUI();
}
/** Cancel name generation */
void CGeneratePropertyNamesDialog::CancelGeneration()
{
mNotifier.SetCanceled(true);
mCanceledNameGeneration = true;
UpdateUI();
}
/** Called when name generation is complete */
void CGeneratePropertyNamesDialog::GenerationComplete()
{
mRunningNameGeneration = false;
mCanceledNameGeneration = false;
mNotifier.SetCanceled(false);
mUpdateTimer.stop();
mTaskOutput = QList<SGeneratedPropertyName>::fromStdList(
mGenerator.GetOutput()
);
mpUI->ProgressBar->setValue( mpUI->ProgressBar->maximum() );
disconnect( &mFutureWatcher, 0, this, 0 );
disconnect( &mUpdateTimer, 0, this, 0 );
UpdateUI();
}
/** Called when an item in the output tree has been checked or unchecked */
void CGeneratePropertyNamesDialog::OnTreeItemChecked(QTreeWidgetItem* pItem)
{
if (pItem->checkState(0) == Qt::Checked)
mCheckedItems.append(pItem);
else
mCheckedItems.removeOne(pItem);
UpdateUI();
}
/** Called when an item in the output tree has been double clicked */
void CGeneratePropertyNamesDialog::OnTreeItemDoubleClicked(QTreeWidgetItem* pItem)
{
// Check whether this is an XML path
if (pItem->parent() != nullptr)
{
QString Text = pItem->text(0);
if (Text.endsWith(".xml"))
{
TString TStrText = TO_TSTRING(Text);
TString DirPath = "../templates/" + TStrText.GetFileDirectory();
TString AbsPath = FileUtil::MakeAbsolute(DirPath) + TStrText.GetFileName();
UICommon::OpenInExternalApplication( TO_QSTRING(AbsPath) );
}
}
}
/** Check all items in the output tree */
void CGeneratePropertyNamesDialog::CheckAll()
{
mpUI->OutputTreeWidget->blockSignals(true);
mCheckedItems.clear();
mCheckedItems.reserve( mpUI->OutputTreeWidget->topLevelItemCount() );
for (int RowIdx = 0; RowIdx < mpUI->OutputTreeWidget->topLevelItemCount(); RowIdx++)
{
QTreeWidgetItem* pItem = mpUI->OutputTreeWidget->topLevelItem(RowIdx);
pItem->setCheckState( 0, Qt::Checked );
mCheckedItems << pItem;
}
mpUI->OutputTreeWidget->blockSignals(false);
UpdateUI();
}
/** Uncheck all items in the output tree */
void CGeneratePropertyNamesDialog::UncheckAll()
{
mpUI->OutputGroupBox->blockSignals(true);
for (int RowIdx = 0; RowIdx < mpUI->OutputTreeWidget->topLevelItemCount(); RowIdx++)
{
QTreeWidgetItem* pItem = mpUI->OutputTreeWidget->topLevelItem(RowIdx);
pItem->setCheckState( 0, Qt::Unchecked );
}
mCheckedItems.clear();
mpUI->OutputTreeWidget->blockSignals(false);
UpdateUI();
}
/** Apply generated names on selected items */
void CGeneratePropertyNamesDialog::ApplyChanges()
{
// make sure the user really wants to do this
QString WarningText =
QString("Are you sure you want to rename %1 %2? This operation cannot be undone.")
.arg(mCheckedItems.size())
.arg(mCheckedItems.size() == 1 ? "property" : "properties");
bool ReallyRename = UICommon::YesNoQuestion(this, "Warning", WarningText);
if (!ReallyRename)
{
return;
}
// Perform rename operation
for (int ItemIdx = 0; ItemIdx < mCheckedItems.size(); ItemIdx++)
{
QTreeWidgetItem* pItem = mCheckedItems[ItemIdx];
u32 ID = TO_TSTRING( pItem->text(2) ).ToInt32();
QString NewName = pItem->text(0);
CMasterTemplate::RenameProperty( ID, TO_TSTRING(NewName) );
pItem->setText(3, NewName);
}
CTemplateWriter::SavePropertyList();
}
/** Check progress on name generation task and display results on the UI */
void CGeneratePropertyNamesDialog::CheckForNewResults()
{
const std::list<SGeneratedPropertyName>& rkOutput = mGenerator.GetOutput();
QTreeWidget* pTreeWidget = mpUI->OutputTreeWidget;
int CurItemCount = pTreeWidget->topLevelItemCount();
// Add new items to the tree
if (rkOutput.size() > CurItemCount)
{
std::list<SGeneratedPropertyName>::const_iterator Iter = rkOutput.cbegin();
std::list<SGeneratedPropertyName>::const_iterator End = rkOutput.cend();
std::advance(Iter, CurItemCount);
for (; Iter != End; Iter++)
{
const SGeneratedPropertyName& rkName = *Iter;
// Add an item to the tree for this name
QStringList ColumnText;
ColumnText << TO_QSTRING( rkName.Name )
<< TO_QSTRING( rkName.Type )
<< TO_QSTRING( TString::HexString(rkName.ID) )
<< TO_QSTRING( CMasterTemplate::PropertyName(rkName.ID) );
QTreeWidgetItem* pItem = new CCheckableTreeWidgetItem(pTreeWidget, ColumnText);
pItem->setFlags(Qt::ItemIsEnabled |
Qt::ItemIsSelectable |
Qt::ItemIsUserCheckable);
pItem->setCheckState(0, Qt::Unchecked);
// Add children items
for (int XmlIdx = 0; XmlIdx < rkName.XmlList.size(); XmlIdx++)
{
QString XmlName = TO_QSTRING( rkName.XmlList[XmlIdx] );
ColumnText.clear();
ColumnText << XmlName;
QTreeWidgetItem* pChild = new QTreeWidgetItem(pItem, ColumnText);
pChild->setFlags(Qt::ItemIsEnabled);
pChild->setFirstColumnSpanned(true);
}
}
}
UpdateUI();
}
/** Updates the enabled status of various widgets */
void CGeneratePropertyNamesDialog::UpdateUI()
{
mpUI->TypeSuffixesGroupBox->setEnabled( !mRunningNameGeneration );
mpUI->SettingsGroupBox->setEnabled( !mRunningNameGeneration );
mpUI->StartButton->setEnabled( !mRunningNameGeneration );
mpUI->CancelButton->setEnabled( mRunningNameGeneration && !mCanceledNameGeneration );
int TotalItems = mpUI->OutputTreeWidget->topLevelItemCount();
bool HasResults = TotalItems > 0;
bool HasCheckedResults = HasResults && !mCheckedItems.isEmpty();
mpUI->CheckAllButton->setEnabled( HasResults );
mpUI->UncheckAllButton->setEnabled( HasResults );
mpUI->ApplyButton->setEnabled( !mRunningNameGeneration && HasCheckedResults );
// Update label
if (HasResults)
{
mpUI->NumSelectedLabel->setText(
QString("%1 names, %2 selected")
.arg(TotalItems)
.arg(mCheckedItems.size())
);
}
else
mpUI->NumSelectedLabel->clear();
}

View File

@ -0,0 +1,96 @@
#ifndef CGENERATEPROPERTYNAMESDIALOG_H
#define CGENERATEPROPERTYNAMESDIALOG_H
#include "CProgressBarNotifier.h"
#include <Core/Resource/Script/CPropertyNameGenerator.h>
#include <QDialog>
#include <QFuture>
#include <QFutureWatcher>
#include <QScopedPointer>
#include <QTimer>
#include <QTreeWidgetItem>
namespace Ui {
class CGeneratePropertyNamesDialog;
}
/**
* Dialog box for accessing property name generation functionality.
*/
class CGeneratePropertyNamesDialog : public QDialog
{
Q_OBJECT
Ui::CGeneratePropertyNamesDialog* mpUI;
/** The name generator */
CPropertyNameGenerator mGenerator;
/** Progress notifier for updating the progress bar */
CProgressBarNotifier mNotifier;
/** Future/future watcher for name generation task */
QFuture<void> mFuture;
QFutureWatcher<void> mFutureWatcher;
/** Timer for fetching updates from name generation task */
QTimer mUpdateTimer;
/** Copy of the output buffer from the name generator; only set after completion */
QList<SGeneratedPropertyName> mTaskOutput;
/** Checked items in the output tree widget */
QVector<QTreeWidgetItem*> mCheckedItems;
/** Whether name generation is running */
bool mRunningNameGeneration;
/** Whether name generation has been canceled */
bool mCanceledNameGeneration;
public:
explicit CGeneratePropertyNamesDialog(QWidget *pParent = 0);
~CGeneratePropertyNamesDialog();
public slots:
/** Close event override */
virtual void closeEvent(QCloseEvent* pEvent);
/** Add an item to the suffix list */
void AddSuffix();
/** Deletes an item from the suffix list */
void DeleteSuffix();
/** Start name generation */
void StartGeneration();
/** Cancel name generation */
void CancelGeneration();
/** Called when name generation is complete */
void GenerationComplete();
/** Called when an item in the output tree has been checked or unchecked */
void OnTreeItemChecked(QTreeWidgetItem* pItem);
/** Called when an item in the output tree has been double clicked */
void OnTreeItemDoubleClicked(QTreeWidgetItem* pItem);
/** Check all items in the output tree */
void CheckAll();
/** Uncheck all items in the output tree */
void UncheckAll();
/** Apply generated names on selected items */
void ApplyChanges();
/** Check progress on name generation task and display results on the UI */
void CheckForNewResults();
/** Updates the enabled status of various widgets */
void UpdateUI();
};
#endif // CGENERATEPROPERTYNAMESDIALOG_H

View File

@ -0,0 +1,383 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CGeneratePropertyNamesDialog</class>
<widget class="QDialog" name="CGeneratePropertyNamesDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>693</width>
<height>604</height>
</rect>
</property>
<property name="windowTitle">
<string>Generate Property Names</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="1,3">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4" stretch="0,0">
<item>
<widget class="QGroupBox" name="SettingsGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="MaxWordsLabel">
<property name="text">
<string>Max words:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="NumWordsSpinBox">
<property name="value">
<number>2</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="PrefixLabel">
<property name="text">
<string>Prefix:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="PrefixLineEdit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="SuffixLabel">
<property name="text">
<string>Suffix:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="SuffixLineEdit"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="LogOutputCheckBox">
<property name="text">
<string>Print output to log</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="TypeSuffixesGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Types</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="TypeSuffixesListWidget">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<item>
<property name="text">
<string>bool</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>int</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>float</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>asset</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>choice</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>enum</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>string</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>sound</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
<item>
<property name="text">
<string>Color</string>
</property>
<property name="flags">
<set>ItemIsSelectable|ItemIsEditable|ItemIsEnabled</set>
</property>
</item>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="AddSuffixButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="Icons.qrc">
<normaloff>:/icons/Plus.png</normaloff>:/icons/Plus.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="RemoveSuffixButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="Icons.qrc">
<normaloff>:/icons/Minus v2.png</normaloff>:/icons/Minus v2.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="OutputGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Output</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="CCheckableTreeWidget" name="OutputTreeWidget">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="verticalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="indentation">
<number>10</number>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>ID</string>
</property>
</column>
<column>
<property name="text">
<string>Current</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="CheckAllButton">
<property name="text">
<string>Check All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="UncheckAllButton">
<property name="text">
<string>Uncheck All</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="NumSelectedLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="ApplyButton">
<property name="text">
<string>Apply</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QProgressBar" name="ProgressBar">
<property name="maximum">
<number>100000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="StartButton">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CCheckableTreeWidget</class>
<extends>QTreeWidget</extends>
<header>Editor/Widgets/CCheckableTreeWidget.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="Icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,53 @@
#ifndef CPROGRESSBARNOTIFIER_H
#define CPROGRESSBARNOTIFIER_H
#include <Math/MathUtil.h>
#include <Core/IProgressNotifier.h>
#include <QProgressBar>
/** Progress notifier class that updates a QProgressBar. */
class CProgressBarNotifier : public IProgressNotifier
{
/** The progress bar we are relaying updates to */
QProgressBar* mpProgressBar;
/** Whether the user has requested to cancel */
bool mCancel;
public:
CProgressBarNotifier()
: IProgressNotifier()
, mpProgressBar(nullptr)
, mCancel(false)
{}
inline void SetProgressBar(QProgressBar* pProgressBar)
{
mpProgressBar = pProgressBar;
}
inline void SetCanceled(bool ShouldCancel)
{
mCancel = ShouldCancel;
}
/** IProgressNotifier interface */
virtual bool ShouldCancel() const
{
return mCancel;
}
protected:
virtual void UpdateProgress(const TString &, const TString &, float ProgressPercent)
{
if (mpProgressBar)
{
int Alpha = Math::Lerp(mpProgressBar->minimum(), mpProgressBar->maximum(), ProgressPercent);
// Defer setValue call so it runs on the correct thread
QMetaObject::invokeMethod(mpProgressBar, "setValue", Qt::AutoConnection, Q_ARG(int, Alpha));
}
}
};
#endif // CPROGRESSBARNOTIFIER_H

View File

@ -25,6 +25,13 @@ public:
// Note: All function calls should be deferred with QMetaObject::invokeMethod to ensure
// that they run on the UI thread instead of whatever thread we happen to be on.
virtual void AsyncMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage)
{
QMetaObject::invokeMethod(this, "AsyncMessageBoxSlot", Qt::QueuedConnection,
Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)),
Q_ARG(QString, TO_QSTRING(rkMessage)) );
}
virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion)
{
bool RetVal;
@ -36,6 +43,11 @@ public:
}
public slots:
void AsyncMessageBoxSlot(const QString& rkInfoBoxTitle, const QString& rkMessage)
{
UICommon::InfoMsg(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkMessage);
}
bool AskYesNoQuestionSlot(const QString& rkInfoBoxTitle, const QString& rkQuestion)
{
return UICommon::YesNoQuestion(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkQuestion);

View File

@ -199,7 +199,11 @@ HEADERS += \
ResourceBrowser/CVirtualDirectoryTreeView.h \
CPropertyNameValidator.h \
Widgets/CSoftValidatorLineEdit.h \
Widgets/CValidityLabel.h
Widgets/CValidityLabel.h \
CGeneratePropertyNamesDialog.h \
CProgressBarNotifier.h \
Widgets/CCheckableTreeWidgetItem.h \
Widgets/CCheckableTreeWidget.h
# Source Files
SOURCES += \
@ -274,7 +278,8 @@ SOURCES += \
ResourceBrowser/CResourceTableView.cpp \
ResourceBrowser/CVirtualDirectoryModel.cpp \
ResourceBrowser/CVirtualDirectoryTreeView.cpp \
CPropertyNameValidator.cpp
CPropertyNameValidator.cpp \
CGeneratePropertyNamesDialog.cpp
# UI Files
FORMS += \
@ -300,4 +305,5 @@ FORMS += \
CProjectSettingsDialog.ui \
WorldEditor/CPoiMapSidebar.ui \
CProgressDialog.ui \
Widgets/CSelectResourcePanel.ui
Widgets/CSelectResourcePanel.ui \
CGeneratePropertyNamesDialog.ui

View File

@ -0,0 +1,22 @@
#ifndef CCHECKABLETREEWIDGET_H
#define CCHECKABLETREEWIDGET_H
#include <QTreeWidget>
/**
* QTreeWidget subclass that emits a signal when an item is checked or unchecked.
* Items must be instantiated as CCheckableTreeWidgetItem, not QTreeWidgetItem.
*/
class CCheckableTreeWidget : public QTreeWidget
{
Q_OBJECT
signals:
void CheckStateChanged(QTreeWidgetItem* pItem);
public:
CCheckableTreeWidget(QWidget* pParent = 0)
: QTreeWidget(pParent) {}
};
#endif // CCHECKABLETREEWIDGET_H

View File

@ -0,0 +1,59 @@
#ifndef CCHECKABLETREEWIDGETITEM_H
#define CCHECKABLETREEWIDGETITEM_H
#include "CCheckableTreeWidget.h"
#include <QTreeWidgetItem>
/** QTreeWidgetItem subclass that emits a signal when checked/unchecked. */
class CCheckableTreeWidgetItem : public QTreeWidgetItem
{
public:
/** Constructors */
CCheckableTreeWidgetItem(int type = Type)
: QTreeWidgetItem(type) {}
CCheckableTreeWidgetItem(const QStringList& strings, int type = Type)
: QTreeWidgetItem(strings, type) {}
CCheckableTreeWidgetItem(QTreeWidget* parent, int type = Type)
: QTreeWidgetItem(parent, type) {}
CCheckableTreeWidgetItem(QTreeWidget* parent, const QStringList& strings, int type = Type)
: QTreeWidgetItem(parent, strings, type) {}
CCheckableTreeWidgetItem(QTreeWidget* parent, QTreeWidgetItem* preceding, int type = Type)
: QTreeWidgetItem(parent, preceding, type) {}
CCheckableTreeWidgetItem(QTreeWidgetItem* parent, int type = Type)
: QTreeWidgetItem(parent, type) {}
CCheckableTreeWidgetItem(QTreeWidgetItem* parent, const QStringList& strings, int type = Type)
: QTreeWidgetItem(parent, strings, type) {}
CCheckableTreeWidgetItem(QTreeWidgetItem* parent, QTreeWidgetItem* preceding, int type = Type)
: QTreeWidgetItem(parent, preceding, type) {}
CCheckableTreeWidgetItem(const QTreeWidgetItem& other)
: QTreeWidgetItem(other) {}
/** setData override to catch check state changes */
virtual void setData(int Column, int Role, const QVariant& rkValue)
{
Qt::CheckState OldState = checkState(0);
QTreeWidgetItem::setData(Column, Role, rkValue);
Qt::CheckState NewState = checkState(0);
if (OldState != NewState)
{
CCheckableTreeWidget* pCheckableTree =
qobject_cast<CCheckableTreeWidget*>(treeWidget());
if (pCheckableTree)
{
pCheckableTree->CheckStateChanged(this);
}
}
}
};
#endif // CCHECKABLETREEWIDGETITEM_H

View File

@ -34,7 +34,8 @@ CTemplateEditDialog::CTemplateEditDialog(IPropertyTemplate *pTemplate, QWidget *
else
{
CTemplateLoader::LoadAllGames();
std::vector<TString> TemplateList = CMasterTemplate::XMLsUsingID(pTemplate->PropertyID());
std::vector<TString> TemplateList;
CMasterTemplate::XMLsUsingID(pTemplate->PropertyID(), TemplateList);
for (u32 iTemp = 0; iTemp < TemplateList.size(); iTemp++)
mpUI->TemplatesListWidget->addItem(TO_QSTRING(TemplateList[iTemp]));

View File

@ -36,6 +36,7 @@ CWorldEditor::CWorldEditor(QWidget *parent)
, mpArea(nullptr)
, mpWorld(nullptr)
, mpLinkDialog(new CLinkDialog(this, this))
, mpGeneratePropertyNamesDialog(new CGeneratePropertyNamesDialog(this))
, mIsMakingLink(false)
, mpNewLinkSender(nullptr)
, mpNewLinkReceiver(nullptr)
@ -174,6 +175,7 @@ CWorldEditor::CWorldEditor(QWidget *parent)
connect(ui->ActionUnlink, SIGNAL(triggered()), this, SLOT(OnUnlinkClicked()));
connect(ui->ActionEditLayers, SIGNAL(triggered()), this, SLOT(EditLayers()));
connect(ui->ActionGeneratePropertyNames, SIGNAL(triggered()), this, SLOT(GeneratePropertyNames()));
connect(ui->ActionDrawWorld, SIGNAL(triggered()), this, SLOT(ToggleDrawWorld()));
connect(ui->ActionDrawObjects, SIGNAL(triggered()), this, SLOT(ToggleDrawObjects()));
@ -1341,3 +1343,9 @@ void CWorldEditor::EditLayers()
Editor.SetArea(mpArea);
Editor.exec();
}
void CWorldEditor::GeneratePropertyNames()
{
// Launch property name generation dialog
mpGeneratePropertyNamesDialog->show();
}

View File

@ -8,6 +8,7 @@
#include "CScriptEditSidebar.h"
#include "CWorldInfoSidebar.h"
#include "Editor/INodeEditor.h"
#include "Editor/CGeneratePropertyNamesDialog.h"
#include "Editor/CGizmo.h"
#include "Editor/CSceneViewport.h"
@ -55,6 +56,7 @@ class CWorldEditor : public INodeEditor
CCollisionRenderSettingsDialog *mpCollisionDialog;
CLinkDialog *mpLinkDialog;
CGeneratePropertyNamesDialog* mpGeneratePropertyNamesDialog;
bool mIsMakingLink;
CScriptObject *mpNewLinkSender;
@ -177,6 +179,7 @@ private slots:
void DecrementGizmo();
void EditCollisionRenderSettings();
void EditLayers();
void GeneratePropertyNames();
signals:
void MapChanged(CWorld *pNewWorld, CGameArea *pNewArea);

View File

@ -339,11 +339,12 @@
<string>Tools</string>
</property>
<addaction name="ActionEditLayers"/>
<addaction name="ActionGeneratePropertyNames"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuTools"/>
<addaction name="menuView"/>
<addaction name="menuTools"/>
</widget>
<widget class="QToolBar" name="EditModeToolBar">
<property name="sizePolicy">
@ -767,6 +768,11 @@
<string>Project Settings</string>
</property>
</action>
<action name="ActionGeneratePropertyNames">
<property name="text">
<string>Generate Property Names</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -46,7 +46,7 @@ template<typename Type>
Type Lerp(const Type& rkA, const Type& rkB, float t)
{
Type Diff = rkB - rkA;
return rkA + (Diff * t);
return rkA + Type(Diff * t);
}
std::pair<bool,float> RayPlaneIntersection(const CRay& rkRay, const CPlane& rkPlane);

File diff suppressed because it is too large Load Diff