From a2242fe01818d707c1e8355017d551bb4924d85c Mon Sep 17 00:00:00 2001 From: RedLine AI Agent Date: Fri, 5 Sep 2025 19:54:14 +0000 Subject: [PATCH] Update app/mainwindow.cpp --- app/mainwindow.cpp | 1938 ++++++++++++++++++++++---------------------- 1 file changed, 969 insertions(+), 969 deletions(-) diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index f695b58..a79a918 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,969 +1,969 @@ -#include "mainwindow.h" -#include "ui_mainwindow.h" - -#include "aboutdialog.h" -#include "fastfile.h" -#include "highlighter_gsc.h" -#include "highlighter_cfg.h" -#include "highlighter_shock.h" -#include "highlighter_rumble.h" -#include "materialviewer.h" -#include "preferenceeditor.h" -#include "reportissuedialog.h" -#include "rumblefileviewer.h" -#include "rumblegraphviewer.h" -#include "soundviewer.h" -#include "stringtableviewer.h" -#include "techsetviewer.h" -#include "fastfile_factory.h" -#include "iwifile.h" -#include "ddsfile.h" -#include "statusbarmanager.h" -#include "ddsviewer.h" -#include "fastfileviewer.h" -#include "ipak_structs.h" -#include "iwiviewer.h" -#include "localstringviewer.h" -#include "imagewidget.h" -#include "zonefileviewer.h" -#include "techsetviewer.h" -#include "logmanager.h" - -#include - -MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), ui(new Ui::MainWindow) { - ui->setupUi(this); - - setAcceptDrops(true); - - mTypeMap = QMap(); - mTypeOrder = QStringList(); - mRawFileMap = QMap(); - //mImageMap = QMap(); - mTreeMap = QMap(); - mStrTableMap = QMap>>(); - mBSPVersion = 0; - mDiskLumpCount = 0; - mDiskLumpOrder = QVector(); - mLumps = QMap(); - mTreeWidget = new XTreeWidget(this); - mLogWidget = new QPlainTextEdit(this); - - mProgressBar = new QProgressBar(this); - mProgressBar->setMaximum(100); // Default max value - mProgressBar->setVisible(false); // Initially hidden - - connect(ui->actionRun_Tests, &QAction::triggered, this, [](bool checked) { - Q_UNUSED(checked); - }); - - connect(ui->actionReport_Issue, &QAction::triggered, this, [this](bool checked) { - Q_UNUSED(checked); - - ReportIssueDialog issueDialog("https://git.redline.llc", "njohnson", "XPlor", "4738c4d2efd123efac1506c68c59b285c646df9f", this); - if (issueDialog.exec() == QDialog::Accepted) { - - } - }); - - connect(&StatusBarManager::instance(), &StatusBarManager::statusUpdated, - this, &MainWindow::HandleStatusUpdate); - - connect(&StatusBarManager::instance(), &StatusBarManager::progressUpdated, - this, &MainWindow::HandleProgressUpdate); - - connect(&LogManager::instance(), &LogManager::entryAdded, - this, &MainWindow::HandleLogEntry); - - statusBar()->addPermanentWidget(mProgressBar); - - connect(ui->actionPreferences, &QAction::triggered, this, [this](bool checked) { - Q_UNUSED(checked); - - PreferenceEditor *prefEditor = new PreferenceEditor(this); - prefEditor->exec(); - }); - - ui->tabWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->tabWidget, &QTabWidget::customContextMenuRequested, this, [this](const QPoint &pos) { - if (pos.isNull()) - return; - - int tabIndex = ui->tabWidget->tabBar()->tabAt(pos); - QMenu *contextMenu = new QMenu(this); - - QAction *closeAction = new QAction("Close"); - contextMenu->addAction(closeAction); - connect(closeAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { - Q_UNUSED(checked); - - ui->tabWidget->removeTab(tabIndex); - }); - - QMenu *closeMultipleAction = new QMenu("Close Multiple Tabs"); - - QAction *closeAllAction = new QAction("Close All"); - closeMultipleAction->addAction(closeAllAction); - connect(closeAllAction, &QAction::triggered, this, [this](bool checked) { - Q_UNUSED(checked); - - ui->tabWidget->clear(); - }); - - QAction *closeAllButAction = new QAction("Close All BUT This"); - closeMultipleAction->addAction(closeAllButAction); - connect(closeAllButAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { - Q_UNUSED(checked); - - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (i != tabIndex) { - ui->tabWidget->removeTab(i); - } - } - }); - - QAction *closeLeftAction = new QAction("Close All to the Left"); - closeMultipleAction->addAction(closeLeftAction); - connect(closeLeftAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { - Q_UNUSED(checked); - - for (int i = 0; i < tabIndex; i++) { - ui->tabWidget->removeTab(i); - } - }); - - QAction *closeRightAction = new QAction("Close All to the Right"); - closeMultipleAction->addAction(closeRightAction); - connect(closeRightAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { - Q_UNUSED(checked); - - for (int i = tabIndex + 1; i < ui->tabWidget->count(); i++) { - ui->tabWidget->removeTab(i); - } - }); - - contextMenu->addMenu(closeMultipleAction); - - QPoint pt(pos); - contextMenu->exec(ui->tabWidget->mapToGlobal(pt)); - - delete contextMenu; - }); - - connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) { - ui->tabWidget->removeTab(index); - }); - - connect(mTreeWidget, &XTreeWidget::Cleared, this, [this]() { - ui->tabWidget->clear(); - }); - - connect(mTreeWidget, &XTreeWidget::RawFileSelected, this, [this](std::shared_ptr rawFile, const QString aParentName) { - QTabWidget *rawTabWidget = new QTabWidget(this); - rawTabWidget->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QPlainTextEdit *scriptEditor = new QPlainTextEdit(rawTabWidget); - scriptEditor->setAcceptDrops(false); - scriptEditor->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - scriptEditor->setFont(QFont("Consolas")); - - // if (rawFile->contents.isEmpty()) { - // scriptEditor->setPlainText("EMPTY"); - // } else { - // scriptEditor->setPlainText(rawFile->contents); - // } - - QString fileStem;// = rawFile->path.split('/').last(); - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete scriptEditor; - return; - } - } - - const int tabStopSpaces = 4; - QFontMetrics metrics(scriptEditor->font()); - scriptEditor->setTabStopDistance(tabStopSpaces * metrics.horizontalAdvance(' ')); - - QSyntaxHighlighter *highlighter = nullptr; - if (fileStem.contains(".gsc")) { - highlighter = new Highlighter_GSC(scriptEditor->document()); - } else if (fileStem.contains(".cfg")) { - highlighter = new Highlighter_CFG(scriptEditor->document()); - } else if (fileStem.contains(".rmb")) { - highlighter = new Highlighter_Rumble(scriptEditor->document()); - - RumbleGraphViewer *rmbGraphViewer = new RumbleGraphViewer(this); - rmbGraphViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - rmbGraphViewer->SetRumbleGraphFile(rawFile); - - rawTabWidget->addTab(rmbGraphViewer, "UI Editor"); - rawTabWidget->addTab(scriptEditor, "Text Editor"); - - ui->tabWidget->addTab(rawTabWidget, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_RUMBLE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - return; - } else if (fileStem.contains(".shock")) { - highlighter = new Highlighter_Shock(scriptEditor->document()); - } /*else if (rawFile->contents.left(6) == "RUMBLE") { - RumbleFileViewer *rmbFileViewer = new RumbleFileViewer(this); - rmbFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - rmbFileViewer->SetRumbleFile(rawFile); - - rawTabWidget->addTab(rmbFileViewer, "UI Editor"); - rawTabWidget->addTab(scriptEditor, "Text Editor"); - - ui->tabWidget->addTab(rawTabWidget, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_RUMBLE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - return; - }*/ else { - delete highlighter; - } - - ui->tabWidget->addTab(scriptEditor, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_RAWFILE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - // connect(mTreeWidget, &XTreeWidget::ImageSelected, this, [this](std::shared_ptr image, const QString aParentName) { - // ImageWidget *mImageWidget = new ImageWidget(this); - // mImageWidget->setAcceptDrops(false); - // mImageWidget->SetImage(image); - // mImageWidget->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - // QString fileStem = image->materialName; - // for (int i = 0; i < ui->tabWidget->count(); i++) { - // if (ui->tabWidget->tabText(i) == fileStem) { - // delete mImageWidget; - // return; - // } - // } - - // ui->tabWidget->addTab(mImageWidget, fileStem); - // ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_IMAGE)); - // ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - // }); - - // connect(mTreeWidget, &XTreeWidget::MenuSelected, this, [](std::shared_ptr menu, const QString aParentName) { - // Q_UNUSED(menu); - // }); - - connect(mTreeWidget, &XTreeWidget::MaterialSelected, this, [this](std::shared_ptr material, const QString aParentName) { - MaterialViewer *matViewer = new MaterialViewer(this); - matViewer->setAcceptDrops(false); - matViewer->SetMaterial(material); - matViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - // QString fileStem = material->name; - // for (int i = 0; i < ui->tabWidget->count(); i++) { - // if (ui->tabWidget->tabText(i) == fileStem) { - // delete matViewer; - // return; - // } - // } - - //ui->tabWidget->addTab(matViewer, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_MATERIAL)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - connect(mTreeWidget, &XTreeWidget::DDSFileSelected, this, [this](std::shared_ptr ddsFile, const QString aParentName) { - DDSViewer *ddsViewer = new DDSViewer(this); - ddsViewer->setAcceptDrops(false); - ddsViewer->SetDDSFile(ddsFile); - ddsViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QString fileStem = ddsFile->fileStem + ".dds"; - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete ddsViewer; - return; - } - } - - ui->tabWidget->addTab(ddsViewer, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_IMAGE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - connect(mTreeWidget, &XTreeWidget::IWIFileSelected, this, [this](std::shared_ptr iwiFile, const QString aParentName) { - IWIViewer *iwiViewer = new IWIViewer(this); - iwiViewer->setAcceptDrops(false); - iwiViewer->SetIWIFile(iwiFile); - iwiViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QString fileStem = iwiFile->fileStem + ".iwi"; - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete iwiViewer; - return; - } - } - - ui->tabWidget->addTab(iwiViewer, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_IMAGE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - connect(mTreeWidget, &XTreeWidget::FastFileSelected, this, [this](std::shared_ptr aFastFile, const QString aParentName) { - FastFileViewer *fastFileViewer = new FastFileViewer(this); - fastFileViewer->setAcceptDrops(false); - fastFileViewer->SetFastFile(aFastFile); - fastFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QString fileStem = aFastFile->GetStem(); - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete fastFileViewer; - return; - } - } - - ui->tabWidget->addTab(fastFileViewer, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_FAST_FILE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - connect(mTreeWidget, &XTreeWidget::ZoneFileSelected, this, [this](std::shared_ptr aZoneFile, const QString aParentName) { - ZoneFileViewer *zoneFileViewer = new ZoneFileViewer(this); - zoneFileViewer->setAcceptDrops(false); - zoneFileViewer->SetZoneFile(aZoneFile); - zoneFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QString fileStem = aZoneFile->GetBaseStem() + ".zone"; - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete zoneFileViewer; - return; - } - } - - QWidget *containerWidget = new QWidget(); - QVBoxLayout *layout = new QVBoxLayout(containerWidget); - layout->addWidget(zoneFileViewer); - containerWidget->setLayout(layout); - - // Create a scroll area and set its properties - QScrollArea *scrollArea = new QScrollArea(ui->tabWidget); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - scrollArea->setWidgetResizable(true); // Important to allow resizing - scrollArea->setWidget(containerWidget); - - ui->tabWidget->addTab(scrollArea, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_ZONE_FILE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - connect(mTreeWidget, &XTreeWidget::LocalStringSelected, this, [this](std::shared_ptr aZoneFile, const QString aParentName) { - LocalStringViewer *localStrViewer = new LocalStringViewer(this); - localStrViewer->setAcceptDrops(false); - localStrViewer->SetZoneFile(aZoneFile); - localStrViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QString fileStem = aZoneFile->GetStem() + ".str"; - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete localStrViewer; - return; - } - } - - ui->tabWidget->addTab(localStrViewer, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_LOCALIZE_ENTRY)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - connect(mTreeWidget, &XTreeWidget::TechSetSelected, this, [this](std::shared_ptr aTechSet, const QString aParentName) { - TechSetViewer *techSetViewer = new TechSetViewer(this); - techSetViewer->setAcceptDrops(false); - techSetViewer->SetTechSet(aTechSet); - techSetViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QString fileStem = aTechSet->name; - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete techSetViewer; - return; - } - } - - ui->tabWidget->addTab(techSetViewer, aTechSet->name); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_TECHNIQUE_SET)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - connect(mTreeWidget, &XTreeWidget::StrTableSelected, this, [this](std::shared_ptr aStrTable, const QString aParentName) { - StringTableViewer *strTableViewer = new StringTableViewer(this); - strTableViewer->setAcceptDrops(false); - strTableViewer->SetStringTable(aStrTable); - strTableViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - QString fileStem = aStrTable->name; - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == fileStem) { - delete strTableViewer; - return; - } - } - - ui->tabWidget->addTab(strTableViewer, fileStem); - ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_STRINGTABLE)); - ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - }); - - // connect(mTreeWidget, &XTreeWidget::SoundSelected, this, [this](std::shared_ptr aSound, const QString aParentName) { - // SoundViewer *soundViewer = new SoundViewer(this); - // soundViewer->setAcceptDrops(false); - // soundViewer->SetSound(aSound); - // soundViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); - - // QString fileStem = aSound->path.split('/').last(); - // for (int i = 0; i < ui->tabWidget->count(); i++) { - // if (ui->tabWidget->tabText(i) == fileStem) { - // delete soundViewer; - // return; - // } - // } - - // ui->tabWidget->addTab(soundViewer, fileStem); - // ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(Utils::ASSET_SOUND)); - // ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); - // }); - - connect(mTreeWidget, &XTreeWidget::ItemSelected, this, [this](const QString itemText) { - for (int i = 0; i < ui->tabWidget->count(); i++) { - if (ui->tabWidget->tabText(i) == itemText) { - ui->tabWidget->setCurrentIndex(i); - break; - } - } - }); - - connect(mTreeWidget, &XTreeWidget::ItemClosed, this, [this](const QString itemText) { - for (int i = 0; i < ui->tabWidget->count(); i++) { - const QString parentName = ui->tabWidget->widget(i)->property("PARENT_NAME").toString(); - if (parentName == itemText) { - ui->tabWidget->removeTab(i); - break; - } - } - }); - - // Connect Help > About dialog - connect(ui->actionAbout, &QAction::triggered, this, [this](bool checked) { - Q_UNUSED(checked); - - AboutDialog *aboutDialog = new AboutDialog(this); - aboutDialog->exec(); - - delete aboutDialog; - }); - - connect(ui->actionOpen_Fast_File, &QAction::triggered, this, - [this](bool checked) { - Q_UNUSED(checked); - OpenFastFile(); - }); - connect(ui->actionOpen_Zone_File, &QAction::triggered, this, - [this](bool checked) { - Q_UNUSED(checked); - OpenZoneFile(); - }); - - QDockWidget *treeDockWidget = new QDockWidget(this); - treeDockWidget->setWidget(mTreeWidget); - treeDockWidget->setWindowTitle("Tree Browser"); - addDockWidget(Qt::LeftDockWidgetArea, treeDockWidget); - - QDockWidget *logDockWidget = new QDockWidget(this); - logDockWidget->setWidget(mLogWidget); - logDockWidget->setWindowTitle("Logs"); - addDockWidget(Qt::RightDockWidgetArea, logDockWidget); - - ui->toolBar->addAction(ui->actionNew_Fast_File); - ui->toolBar->addAction(ui->actionNew_Zone_File); - ui->toolBar->addAction(ui->actionOpen_Fast_File); - ui->toolBar->addAction(ui->actionOpen_Zone_File); - ui->toolBar->addAction(ui->actionOpen_Folder); - ui->toolBar->addAction(ui->actionSave); - ui->toolBar->addSeparator(); - ui->toolBar->addAction(ui->actionCut); - ui->toolBar->addAction(ui->actionCopy); - ui->toolBar->addAction(ui->actionPaste); - ui->toolBar->addSeparator(); - - ui->toolBar->addSeparator(); - - ui->toolBar->addSeparator(); - ui->toolBar->addAction(ui->actionFind_2); - - Reset(); -} - -MainWindow::~MainWindow() { delete ui; } - -void MainWindow::Reset() { - // Clear data tree - // mTreeWidget->clear(); - - // Reset class vars - mTypeMap.clear(); - mTypeOrder.clear(); - mTagCount = 0; - mRecordCount = 0; - mRawFileMap.clear(); - mTreeMap.clear(); - mStrTableMap.clear(); -} - -/* - OpenFastFile() - - Opens a file dialog in the steam folder, - and opens the selected file. -*/ -bool MainWindow::OpenFastFile(const QString aFastFilePath) { - const QString fastFileStem = aFastFilePath.section("/", -1, -1); - if (mTreeWidget->HasFastFile(fastFileStem)) { - LogManager::instance().addError("Can't add duplicate file!"); - return false; - } - - std::shared_ptr fastFile = FastFileFactory::Create(aFastFilePath); - fastFile->SetStem(fastFileStem); - mTreeWidget->AddFastFile(fastFile); - - // Open zone file after decompressing ff and writing - return true; -} - -/* - OpenFastFile() - - Opens a file dialog in the steam folder, - and opens the selected file. -*/ -bool MainWindow::OpenFastFile() { - // Open file dialog to steam apps - const QString steamPath = "C:/Program Files (x86)/Steam/steamapps/common/Call of Duty World at War/zone/english/"; - const QString fastFilePath = QFileDialog::getOpenFileName(this, "Open Fast File", steamPath, "Fast File (*.ff);;All Files (*.*)"); - if (fastFilePath.isNull()) { - // User pressed cancel - return false; - } else if (!QFile::exists(fastFilePath)) { - QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(fastFilePath)); - return false; - } - if (!OpenFastFile(fastFilePath)) { - qDebug() << "Failed to open Fast file!"; - return false; - } - return true; -} - -/* - OpenZoneFile() - - Opens a file dialog in the steam folder, - and opens the selected file. -*/ -bool MainWindow::OpenZoneFile(const QString aZoneFilePath, bool fromFF) { - Q_UNUSED(aZoneFilePath); - Q_UNUSED(fromFF); - - //ZoneFile zoneFile; - //if (!zoneFile.Load(aZoneFilePath)) { - // qDebug() << "Error: Failed to load zone file!"; - // return false; - //} - //mTreeWidget->AddZoneFile(std::make_shared(zoneFile)); - - return true; -} - -bool MainWindow::OpenZoneFile() { - // Open file dialog to steam apps - const QString steamPath = "C:/Program Files (x86)/Steam/steamapps/common/Call of Duty World at War/zone/english/"; - const QString zoneFilePath = QFileDialog::getOpenFileName(this, "Open Zone File", steamPath, "Zone File (*.zone);;All Files (*.*)"); - if (zoneFilePath.isNull()) { - // User pressed cancel - return false; - } else if (!QFile::exists(zoneFilePath)) { - QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(zoneFilePath)); - qDebug() << "Failed to open Zone file!"; - return false; - } - return true; -} - -int MainWindow::LoadFile_D3DBSP(const QString aFilePath) { - QFile file(aFilePath); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Error: Unable to open file" << aFilePath; - return 1; // ERR_FILE_NOT_FOUND - } - - QDataStream stream(&file); - stream.setByteOrder(QDataStream::LittleEndian); - - // Read Magic Value - quint32 magicValue; - stream >> magicValue; - if (magicValue != 'PSBI') { - qDebug() << "Error: Not a valid D3DBSP file"; - return 2; // ERR_NOT_A_D3DBSP_FILE - } - - // Read BSP Version and Lump Count - stream >> mBSPVersion; - stream >> mDiskLumpCount; - - // Assign diskLumpOrderSize - mDiskLumpOrder.resize(mDiskLumpCount); - - // Read Lump Index Entries - quint32 lumpOffset = - sizeof(quint32) * 3 + sizeof(LumpIndexEntry) * mDiskLumpCount; - - for (quint32 i = 0; i < mDiskLumpCount; i++) { - LumpIndexEntry indexEntry; - stream >> indexEntry.type >> indexEntry.length; - - mDiskLumpOrder[i] = indexEntry.type; - - Lump &lump = mLumps[indexEntry.type]; - lump.size = indexEntry.length; - lump.content.resize(indexEntry.length); - lump.isEmpty = false; - - qDebug() << "Lump Type:" - << Utils::LumpTypeToString((LUMP_TYPE)indexEntry.type) - << "Lump Size:" << indexEntry.length; - - // Handle offsets and padding - qint64 currentPos = file.pos(); - file.seek(lumpOffset); - file.read(lump.content.data(), lump.size); - file.seek(currentPos); - lumpOffset += Utils::PadInt4(indexEntry.length); - } - - file.close(); - return 0; // Success -} - -struct Command { - quint32 size = 0; - bool compressed = false; -}; -quint32 DXT1 = 0x31545844; // 'DXT1' -quint32 DXT3 = 0x33545844; // 'DXT3' -quint32 DXT5 = 0x35545844; // 'DXT5' - -int MainWindow::LoadFile_IWI(const QString aFilePath) { - mTreeWidget->AddIWIFile(std::make_shared(aFilePath)); - - return 0; -} - -int MainWindow::LoadFile_DDSFiles(const QStringList aFilePaths) { - for (const QString &filePath : aFilePaths) { - if (!filePath.endsWith(".dds", Qt::CaseInsensitive)) { - qDebug() << "Error: Invalid filename " << filePath; - return -1; - } - mTreeWidget->AddDDSFile(std::make_shared(filePath)); - } - return 0; -} - -void MainWindow::HandleLogEntry(const QString &entry) { - QString logContents = mLogWidget->toPlainText() + "\n" + entry; - if (mLogWidget->toPlainText().isEmpty()) { - logContents = entry; - } - mLogWidget->setPlainText(logContents); -} - -void MainWindow::HandleStatusUpdate(const QString &message, int timeout) { - statusBar()->showMessage(message, timeout); - mProgressBar->setVisible(false); // Hide progress bar if just a message -} - -void MainWindow::HandleProgressUpdate(const QString &message, int progress, int max) { - mProgressBar->setMaximum(max); - mProgressBar->setValue(progress); - mProgressBar->setVisible(true); - - QString progressText = QString("%1 (%2/%3)").arg(message).arg(progress).arg(max); - statusBar()->showMessage(progressText); -} - -int MainWindow::LoadFile_DDS(const QString aFilePath) { - if (!aFilePath.endsWith(".dds", Qt::CaseInsensitive)) { - qDebug() << "Error: Invalid filename " << aFilePath; - return -1; - } - mTreeWidget->AddDDSFile(std::make_shared(aFilePath)); - return 0; -} - -int MainWindow::LoadFile_XSUB(const QString aFilePath) { - QFile file(aFilePath); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Error: Unable to open file" << aFilePath; - return 1; // ERR_FILE_NOT_FOUND - } - - QDataStream stream(&file); - stream.setByteOrder(QDataStream::LittleEndian); - - QByteArray magic(4, Qt::Uninitialized); - stream.readRawData(magic.data(), 4); - if (magic != "KAPI") { - qDebug() << "Wrong magic, invalid XSUB file!"; - return -1; - } - qDebug() << "Magic: " << magic; - - stream.skipRawData(5 * 4); - - quint32 fileSize; - stream >> fileSize; - qDebug() << "File Size: " << fileSize; - - return 0; // Success -} - -int MainWindow::LoadFile_IPAK(const QString aFilePath) { - QFile file(aFilePath); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Error: Unable to open file" << aFilePath; - return 1; // ERR_FILE_NOT_FOUND - } - - QDataStream stream(&file); - stream.setByteOrder(QDataStream::BigEndian); - - IPAKHeader header; - stream >> header; - - if (header.version == "50000") { - if (header.magic == "KAPI") { - stream.setByteOrder(QDataStream::LittleEndian); - } else if (header.magic == "IPAK") { - stream.setByteOrder(QDataStream::BigEndian); - } else { - qDebug() << "Invalid IPAK file!"; - return -1; - } - } else { - qDebug() << "Invalid IPAK file version!"; - return -1; - } - - qDebug() << "IPAK File " << "\n" - << "- Platform: " << header.platform << "\n" - << "- Magic: " << header.magic << "\n" - << "- Size: " << header.size << "\n" - << "- Version: " << header.version << "\n" - << "- Sections: " << header.sectionCount; - - QDir outputFolder = QDir(QDir::currentPath() + "/output"); - outputFolder.remove(QDir::currentPath() + "/output"); - outputFolder.mkdir(QDir::currentPath() + "/output"); - - QVector metas = QVector(); - - QVector entries = QVector(); - QVector sections = QVector(header.sectionCount); - for (uint i = 0; i < header.sectionCount; i++) { - IPAKSection currentSection; - stream >> currentSection; - sections << currentSection; - - qDebug() << " - IPAK Section " << i + 1 << "\n" - << " - Type: " << currentSection.type << " -> " << currentSection.typeInt << "\n" - << " - Offset: " << currentSection.offset << "\n" - << " - Item (IWI) Count: " << currentSection.itemCount << "\n" - << " - Size: " << currentSection.size; - - qint64 sectionPos = stream.device()->pos(); - stream.device()->seek(currentSection.offset); - - QString sectionType = currentSection.type; - if (sectionType == "Data") { - IPAKDataChunkHeader chunkHeader; - stream >> chunkHeader; - qDebug() << " - Chunk Header\n" - << " - Count: " << chunkHeader.count << "\n" - << " - Offset: " << chunkHeader.offset; - - for (uint j = 0; j < 31; j++) { - IPAKDataChunkCommand command; - stream >> command; - if (!command.size) { continue; } - chunkHeader.commands << command; - qDebug() << " - Command\n" - << " - Size: " << command.size << "\n" - << " - Compressed: " << command.compressed; - - } - for (uint j = 0; j < chunkHeader.count; j++) { - auto command = chunkHeader.commands[j]; - - qDebug() << "Reading from " << stream.device()->pos(); - QByteArray data = stream.device()->read(command.size); - qDebug() << " to " << stream.device()->pos(); - - QString outputFilePath = outputFolder.filePath(QString("%1.iwi").arg(j)); - if (command.compressed) { - //data = Compression::DecompressLZO(data); - } - QFile outputFile(outputFilePath); - if (!outputFile.open(QIODevice::WriteOnly)) { - qDebug() << "Failed to extract IPAK file."; - } - qDebug() << " - File Name: " << outputFile.fileName(); - outputFile.write(data); - outputFile.close(); - } - - - qDebug() << stream.device()->pos(); - stream.skipRawData(sizeof(quint32) * (31 - chunkHeader.count)); - qDebug() << stream.device()->pos(); - } else if (sectionType == "Index") { - for (uint j = 0; j < currentSection.itemCount; j++) { - IPAKIndexEntry entry; - stream >> entry; - - if (entry.size == 0) { continue; } - - entries << entry; - - quint64 entryPos = stream.device()->pos(); - qDebug() << " - Index Entry " << j + 1 << "\n" - << " - Name Hash: " << entry.nameHash << "\n" - << " - Data Hash: " << entry.dataHash << "\n" - << " - Offset: " << entry.offset << "\n" - << " - Size: " << entry.size; - - stream.device()->seek(entry.offset); - - QByteArray sectionData(entry.size, Qt::Uninitialized); - stream.readRawData(sectionData.data(), entry.size); - - const QString entryKey = QString::number(entry.nameHash); - QFile outputFile(outputFolder.filePath(QString("%1.dds").arg(entryKey))); - if (!outputFile.open(QIODevice::WriteOnly)) { - qDebug() << "Failed to extract IPAK file."; - } - qDebug() << " - File Name: " << outputFile.fileName(); - outputFile.write(sectionData); - outputFile.close(); - - stream.device()->seek(entryPos); - } - } - stream.device()->seek(sectionPos); - qDebug() << stream.device()->pos(); - } - - return 0; // Success -} - -void MainWindow::dragEnterEvent(QDragEnterEvent *event) { - const QMimeData *mimeData = event->mimeData(); - bool goodDrag = true; - if (mimeData->hasUrls()) { - foreach (const QUrl url, mimeData->urls()) { - if (!url.toString().contains(".ff") && - !url.toString().contains(".zone") && - !url.toString().contains(".ipak") && - !url.toString().contains(".d3dbsp") && - !url.toString().contains(".lzoin") && - !url.toString().contains(".xsub") && - !url.toString().contains(".iwi") && - !url.toString().contains(".dds")) { - goodDrag = false; - } - } - } else { - goodDrag = false; - } - if (goodDrag) { - event->acceptProposedAction(); - } -} - -void MainWindow::dragMoveEvent(QDragMoveEvent *event) { - Q_UNUSED(event); -} - -void MainWindow::dragLeaveEvent(QDragLeaveEvent *event) { - Q_UNUSED(event); -} - -void MainWindow::dropEvent(QDropEvent *event) { - const QMimeData *mimeData = event->mimeData(); - if (mimeData->hasUrls()) { - QStringList ddsPaths = QStringList(); - foreach (const QUrl url, mimeData->urls()) { - const QString urlStr = url.toLocalFile(); - if (urlStr.contains(".zone")) { - qDebug() << "OpenZoneFile Returned: " << OpenZoneFile(urlStr); - } else if (urlStr.contains(".ff")) { - qDebug() << "OpenFastFile Returned: " << OpenFastFile(urlStr); - } else if (urlStr.contains(".ipak")) { - qDebug() << "LoadFile_IPAK Returned: " << LoadFile_IPAK(urlStr); - } else if (urlStr.contains(".xsub")) { - qDebug() << "LoadFile_XSUB Returned: " << LoadFile_XSUB(urlStr); - } else if (urlStr.contains(".iwi")) { - qDebug() << "LoadFile_IWI Returned: " << LoadFile_IWI(urlStr); - } else if (urlStr.contains(".dds")) { - if (mimeData->urls().size() == 1) { - qDebug() << "LoadFile_DDS Returned: " << LoadFile_DDS(urlStr); - } else { - ddsPaths << urlStr; - } - } else if (urlStr.contains(".d3dbsp")) { - LoadFile_D3DBSP(urlStr); - } else if (urlStr.contains(".lzoin")) { - QFile lzoFile(urlStr); - if (!lzoFile.open(QIODevice::ReadOnly)) { - qDebug() << "LZO: Failed to read file!"; - continue; - } - QByteArray data;// = Compression::DecompressLZO(lzoFile.readAll()); - lzoFile.close(); - - if (data.isEmpty()) { - qDebug() << "LZO: Decompression gave empty result!"; - continue; - } - - QFile outputFile(url.toLocalFile().replace("lzoin", "lzoout")); - if (!outputFile.open(QIODevice::WriteOnly)) { - qDebug() << "LZO: Failed to write file!"; - continue; - } - outputFile.write(data); - outputFile.close(); - } else { - const QString ext = urlStr.split('.').last(); - ui->statusBar->showMessage( - QString("Can't display dropped file! .%1").arg(ext)); - } - } - if (ddsPaths.size() > 1) { - qDebug() << "LoadFile_DDSFiles Returned: " << LoadFile_DDSFiles(ddsPaths); - } - } else { - ui->statusBar->showMessage("Can't display dropped data!"); - } -} +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include "aboutdialog.h" +#include "fastfile.h" +#include "highlighter_gsc.h" +#include "highlighter_cfg.h" +#include "highlighter_shock.h" +#include "highlighter_rumble.h" +#include "materialviewer.h" +#include "preferenceeditor.h" +#include "reportissuedialog.h" +#include "rumblefileviewer.h" +#include "rumblegraphviewer.h" +#include "soundviewer.h" +#include "stringtableviewer.h" +#include "techsetviewer.h" +#include "fastfile_factory.h" +#include "iwifile.h" +#include "ddsfile.h" +#include "statusbarmanager.h" +#include "ddsviewer.h" +#include "fastfileviewer.h" +#include "ipak_structs.h" +#include "iwiviewer.h" +#include "localstringviewer.h" +#include "imagewidget.h" +#include "zonefileviewer.h" +#include "techsetviewer.h" +#include "logmanager.h" + +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); + + setAcceptDrops(true); + + mTypeMap = QMap(); + mTypeOrder = QStringList(); + mRawFileMap = QMap(); + //mImageMap = QMap(); + mTreeMap = QMap(); + mStrTableMap = QMap>>(); + mBSPVersion = 0; + mDiskLumpCount = 0; + mDiskLumpOrder = QVector(); + mLumps = QMap(); + mTreeWidget = new XTreeWidget(this); + mLogWidget = new QPlainTextEdit(this); + + mProgressBar = new QProgressBar(this); + mProgressBar->setMaximum(100); // Default max value + mProgressBar->setVisible(false); // Initially hidden + + connect(ui->actionRun_Tests, &QAction::triggered, this, [](bool checked) { + Q_UNUSED(checked); + }); + + connect(ui->actionReport_Issue, &QAction::triggered, this, [this](bool checked) { + Q_UNUSED(checked); + + ReportIssueDialog issueDialog("https://git.redline.llc", "njohnson", "XPlor", "4738c4d2efd123efac1506c68c59b285c646df9f", this); + if (issueDialog.exec() == QDialog::Accepted) { + + } + }); + + connect(&StatusBarManager::instance(), &StatusBarManager::statusUpdated, + this, &MainWindow::HandleStatusUpdate); + + connect(&StatusBarManager::instance(), &StatusBarManager::progressUpdated, + this, &MainWindow::HandleProgressUpdate); + + connect(&LogManager::instance(), &LogManager::entryAdded, + this, &MainWindow::HandleLogEntry); + + statusBar()->addPermanentWidget(mProgressBar); + + connect(ui->actionPreferences, &QAction::triggered, this, [this](bool checked) { + Q_UNUSED(checked); + + PreferenceEditor *prefEditor = new PreferenceEditor(this); + prefEditor->exec(); + }); + + ui->tabWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->tabWidget, &QTabWidget::customContextMenuRequested, this, [this](const QPoint &pos) { + if (pos.isNull()) + return; + + int tabIndex = ui->tabWidget->tabBar()->tabAt(pos); + QMenu *contextMenu = new QMenu(this); + + QAction *closeAction = new QAction("Close"); + contextMenu->addAction(closeAction); + connect(closeAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { + Q_UNUSED(checked); + + ui->tabWidget->removeTab(tabIndex); + }); + + QMenu *closeMultipleAction = new QMenu("Close Multiple Tabs"); + + QAction *closeAllAction = new QAction("Close All"); + closeMultipleAction->addAction(closeAllAction); + connect(closeAllAction, &QAction::triggered, this, [this](bool checked) { + Q_UNUSED(checked); + + ui->tabWidget->clear(); + }); + + QAction *closeAllButAction = new QAction("Close All BUT This"); + closeMultipleAction->addAction(closeAllButAction); + connect(closeAllButAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { + Q_UNUSED(checked); + + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (i != tabIndex) { + ui->tabWidget->removeTab(i); + } + } + }); + + QAction *closeLeftAction = new QAction("Close All to the Left"); + closeMultipleAction->addAction(closeLeftAction); + connect(closeLeftAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { + Q_UNUSED(checked); + + for (int i = 0; i < tabIndex; i++) { + ui->tabWidget->removeTab(i); + } + }); + + QAction *closeRightAction = new QAction("Close All to the Right"); + closeMultipleAction->addAction(closeRightAction); + connect(closeRightAction, &QAction::triggered, this, [this, &tabIndex](bool checked) { + Q_UNUSED(checked); + + for (int i = tabIndex + 1; i < ui->tabWidget->count(); i++) { + ui->tabWidget->removeTab(i); + } + }); + + contextMenu->addMenu(closeMultipleAction); + + QPoint pt(pos); + contextMenu->exec(ui->tabWidget->mapToGlobal(pt)); + + delete contextMenu; + }); + + connect(ui->tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) { + ui->tabWidget->removeTab(index); + }); + + connect(mTreeWidget, &XTreeWidget::Cleared, this, [this]() { + ui->tabWidget->clear(); + }); + + connect(mTreeWidget, &XTreeWidget::RawFileSelected, this, [this](const XRawFile* rawFile, const QString aParentName) { + QTabWidget *rawTabWidget = new QTabWidget(this); + rawTabWidget->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QPlainTextEdit *scriptEditor = new QPlainTextEdit(rawTabWidget); + scriptEditor->setAcceptDrops(false); + scriptEditor->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + scriptEditor->setFont(QFont("Consolas")); + + // if (rawFile->contents.isEmpty()) { + // scriptEditor->setPlainText("EMPTY"); + // } else { + // scriptEditor->setPlainText(rawFile->contents); + // } + + QString fileStem;// = rawFile->path.split('/').last(); + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete scriptEditor; + return; + } + } + + const int tabStopSpaces = 4; + QFontMetrics metrics(scriptEditor->font()); + scriptEditor->setTabStopDistance(tabStopSpaces * metrics.horizontalAdvance(' ')); + + QSyntaxHighlighter *highlighter = nullptr; + if (fileStem.contains(".gsc")) { + highlighter = new Highlighter_GSC(scriptEditor->document()); + } else if (fileStem.contains(".cfg")) { + highlighter = new Highlighter_CFG(scriptEditor->document()); + } else if (fileStem.contains(".rmb")) { + highlighter = new Highlighter_Rumble(scriptEditor->document()); + + RumbleGraphViewer *rmbGraphViewer = new RumbleGraphViewer(this); + rmbGraphViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + rmbGraphViewer->SetRumbleGraphFile(rawFile); + + rawTabWidget->addTab(rmbGraphViewer, "UI Editor"); + rawTabWidget->addTab(scriptEditor, "Text Editor"); + + ui->tabWidget->addTab(rawTabWidget, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_RUMBLE)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + return; + } else if (fileStem.contains(".shock")) { + highlighter = new Highlighter_Shock(scriptEditor->document()); + } /*else if (rawFile->contents.left(6) == "RUMBLE") { + RumbleFileViewer *rmbFileViewer = new RumbleFileViewer(this); + rmbFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + rmbFileViewer->SetRumbleFile(rawFile); + + rawTabWidget->addTab(rmbFileViewer, "UI Editor"); + rawTabWidget->addTab(scriptEditor, "Text Editor"); + + ui->tabWidget->addTab(rawTabWidget, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_RUMBLE)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + return; + }*/ else { + delete highlighter; + } + + ui->tabWidget->addTab(scriptEditor, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_RAWFILE)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + // connect(mTreeWidget, &XTreeWidget::ImageSelected, this, [this](std::shared_ptr image, const QString aParentName) { + // ImageWidget *mImageWidget = new ImageWidget(this); + // mImageWidget->setAcceptDrops(false); + // mImageWidget->SetImage(image); + // mImageWidget->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + // QString fileStem = image->materialName; + // for (int i = 0; i < ui->tabWidget->count(); i++) { + // if (ui->tabWidget->tabText(i) == fileStem) { + // delete mImageWidget; + // return; + // } + // } + + // ui->tabWidget->addTab(mImageWidget, fileStem); + // ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_IMAGE)); + // ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + // }); + + // connect(mTreeWidget, &XTreeWidget::MenuSelected, this, [](std::shared_ptr menu, const QString aParentName) { + // Q_UNUSED(menu); + // }); + + connect(mTreeWidget, &XTreeWidget::MaterialSelected, this, [this](const XMaterial* material, const QString aParentName) { + MaterialViewer *matViewer = new MaterialViewer(this); + matViewer->setAcceptDrops(false); + matViewer->SetMaterial(material); + matViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + // QString fileStem = material->name; + // for (int i = 0; i < ui->tabWidget->count(); i++) { + // if (ui->tabWidget->tabText(i) == fileStem) { + // delete matViewer; + // return; + // } + // } + + //ui->tabWidget->addTab(matViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_MATERIAL)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::DDSFileSelected, this, [this](const DDSFile* ddsFile, const QString aParentName) { + DDSViewer *ddsViewer = new DDSViewer(this); + ddsViewer->setAcceptDrops(false); + ddsViewer->SetDDSFile(ddsFile); + ddsViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QString fileStem = ddsFile->fileStem + ".dds"; + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete ddsViewer; + return; + } + } + + ui->tabWidget->addTab(ddsViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_IMAGE)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::IWIFileSelected, this, [this](const IWIFile* iwiFile, const QString aParentName) { + IWIViewer *iwiViewer = new IWIViewer(this); + iwiViewer->setAcceptDrops(false); + iwiViewer->SetIWIFile(iwiFile); + iwiViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QString fileStem = iwiFile->fileStem + ".iwi"; + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete iwiViewer; + return; + } + } + + ui->tabWidget->addTab(iwiViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_IMAGE)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::FastFileSelected, this, [this](const FastFile* aFastFile, const QString aParentName) { + FastFileViewer *fastFileViewer = new FastFileViewer(this); + fastFileViewer->setAcceptDrops(false); + fastFileViewer->SetFastFile(aFastFile); + fastFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QString fileStem = aFastFile->GetStem(); + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete fastFileViewer; + return; + } + } + + ui->tabWidget->addTab(fastFileViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon("FF")); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::ZoneFileSelected, this, [this](const ZoneFile* aZoneFile, const QString aParentName) { + ZoneFileViewer *zoneFileViewer = new ZoneFileViewer(this); + zoneFileViewer->setAcceptDrops(false); + zoneFileViewer->SetZoneFile(aZoneFile); + zoneFileViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QString fileStem = aZoneFile->GetBaseStem() + ".zone"; + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete zoneFileViewer; + return; + } + } + + QWidget *containerWidget = new QWidget(); + QVBoxLayout *layout = new QVBoxLayout(containerWidget); + layout->addWidget(zoneFileViewer); + containerWidget->setLayout(layout); + + // Create a scroll area and set its properties + QScrollArea *scrollArea = new QScrollArea(ui->tabWidget); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + scrollArea->setWidgetResizable(true); // Important to allow resizing + scrollArea->setWidget(containerWidget); + + ui->tabWidget->addTab(scrollArea, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon("ZF")); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::LocalStringSelected, this, [this](const ZoneFile* aZoneFile, const QString aParentName) { + LocalStringViewer *localStrViewer = new LocalStringViewer(this); + localStrViewer->setAcceptDrops(false); + localStrViewer->SetZoneFile(aZoneFile); + localStrViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QString fileStem = aZoneFile->GetStem() + ".str"; + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete localStrViewer; + return; + } + } + + ui->tabWidget->addTab(localStrViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_LOCALIZE_ENTRY)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::TechSetSelected, this, [this](const XMaterialTechniqueSet* aTechSet, const QString aParentName) { + TechSetViewer *techSetViewer = new TechSetViewer(this); + techSetViewer->setAcceptDrops(false); + techSetViewer->SetTechSet(aTechSet); + techSetViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QString fileStem = aTechSet->GetName(); + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete techSetViewer; + return; + } + } + + ui->tabWidget->addTab(techSetViewer, aTechSet->GetName()); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_TECHNIQUE_SET)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + connect(mTreeWidget, &XTreeWidget::StrTableSelected, this, [this](const XStringTable* aStrTable, const QString aParentName) { + StringTableViewer *strTableViewer = new StringTableViewer(this); + strTableViewer->setAcceptDrops(false); + strTableViewer->SetStringTable(aStrTable); + strTableViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + QString fileStem = aStrTable->GetName()->GetString(); + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == fileStem) { + delete strTableViewer; + return; + } + } + + ui->tabWidget->addTab(strTableViewer, fileStem); + ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_STRINGTABLE)); + ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + }); + + // connect(mTreeWidget, &XTreeWidget::SoundSelected, this, [this](std::shared_ptr aSound, const QString aParentName) { + // SoundViewer *soundViewer = new SoundViewer(this); + // soundViewer->setAcceptDrops(false); + // soundViewer->SetSound(aSound); + // soundViewer->setProperty("PARENT_NAME", QVariant::fromValue(aParentName)); + + // QString fileStem = aSound->path.split('/').last(); + // for (int i = 0; i < ui->tabWidget->count(); i++) { + // if (ui->tabWidget->tabText(i) == fileStem) { + // delete soundViewer; + // return; + // } + // } + + // ui->tabWidget->addTab(soundViewer, fileStem); + // ui->tabWidget->setTabIcon(ui->tabWidget->count() - 1, Utils::CreateAssetIcon(ASSET_TYPE_SOUND)); + // ui->tabWidget->setCurrentIndex(ui->tabWidget->count() - 1); + // }); + + connect(mTreeWidget, &XTreeWidget::ItemSelected, this, [this](const QString itemText) { + for (int i = 0; i < ui->tabWidget->count(); i++) { + if (ui->tabWidget->tabText(i) == itemText) { + ui->tabWidget->setCurrentIndex(i); + break; + } + } + }); + + connect(mTreeWidget, &XTreeWidget::ItemClosed, this, [this](const QString itemText) { + for (int i = 0; i < ui->tabWidget->count(); i++) { + const QString parentName = ui->tabWidget->widget(i)->property("PARENT_NAME").toString(); + if (parentName == itemText) { + ui->tabWidget->removeTab(i); + break; + } + } + }); + + // Connect Help > About dialog + connect(ui->actionAbout, &QAction::triggered, this, [this](bool checked) { + Q_UNUSED(checked); + + AboutDialog *aboutDialog = new AboutDialog(this); + aboutDialog->exec(); + + delete aboutDialog; + }); + + connect(ui->actionOpen_Fast_File, &QAction::triggered, this, + [this](bool checked) { + Q_UNUSED(checked); + OpenFastFile(); + }); + connect(ui->actionOpen_Zone_File, &QAction::triggered, this, + [this](bool checked) { + Q_UNUSED(checked); + OpenZoneFile(); + }); + + QDockWidget *treeDockWidget = new QDockWidget(this); + treeDockWidget->setWidget(mTreeWidget); + treeDockWidget->setWindowTitle("Tree Browser"); + addDockWidget(Qt::LeftDockWidgetArea, treeDockWidget); + + QDockWidget *logDockWidget = new QDockWidget(this); + logDockWidget->setWidget(mLogWidget); + logDockWidget->setWindowTitle("Logs"); + addDockWidget(Qt::RightDockWidgetArea, logDockWidget); + + ui->toolBar->addAction(ui->actionNew_Fast_File); + ui->toolBar->addAction(ui->actionNew_Zone_File); + ui->toolBar->addAction(ui->actionOpen_Fast_File); + ui->toolBar->addAction(ui->actionOpen_Zone_File); + ui->toolBar->addAction(ui->actionOpen_Folder); + ui->toolBar->addAction(ui->actionSave); + ui->toolBar->addSeparator(); + ui->toolBar->addAction(ui->actionCut); + ui->toolBar->addAction(ui->actionCopy); + ui->toolBar->addAction(ui->actionPaste); + ui->toolBar->addSeparator(); + + ui->toolBar->addSeparator(); + + ui->toolBar->addSeparator(); + ui->toolBar->addAction(ui->actionFind_2); + + Reset(); +} + +MainWindow::~MainWindow() { delete ui; } + +void MainWindow::Reset() { + // Clear data tree + // mTreeWidget->clear(); + + // Reset class vars + mTypeMap.clear(); + mTypeOrder.clear(); + mTagCount = 0; + mRecordCount = 0; + mRawFileMap.clear(); + mTreeMap.clear(); + mStrTableMap.clear(); +} + +/* + OpenFastFile() + + Opens a file dialog in the steam folder, + and opens the selected file. +*/ +bool MainWindow::OpenFastFile(const QString aFastFilePath) { + const QString fastFileStem = aFastFilePath.section("/", -1, -1); + if (mTreeWidget->HasFastFile(fastFileStem)) { + LogManager::instance().addError("Can't add duplicate file!"); + return false; + } + + FastFile* fastFile = FastFileFactory::Create(aFastFilePath); + fastFile->SetStem(fastFileStem); + mTreeWidget->AddFastFile(fastFile); + + // Open zone file after decompressing ff and writing + return true; +} + +/* + OpenFastFile() + + Opens a file dialog in the steam folder, + and opens the selected file. +*/ +bool MainWindow::OpenFastFile() { + // Open file dialog to steam apps + const QString steamPath = "C:/Program Files (x86)/Steam/steamapps/common/Call of Duty World at War/zone/english/"; + const QString fastFilePath = QFileDialog::getOpenFileName(this, "Open Fast File", steamPath, "Fast File (*.ff);;All Files (*.*)"); + if (fastFilePath.isNull()) { + // User pressed cancel + return false; + } else if (!QFile::exists(fastFilePath)) { + QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(fastFilePath)); + return false; + } + if (!OpenFastFile(fastFilePath)) { + qDebug() << "Failed to open Fast file!"; + return false; + } + return true; +} + +/* + OpenZoneFile() + + Opens a file dialog in the steam folder, + and opens the selected file. +*/ +bool MainWindow::OpenZoneFile(const QString aZoneFilePath, bool fromFF) { + Q_UNUSED(aZoneFilePath); + Q_UNUSED(fromFF); + + // ZoneFile* zoneFile = ZoneFile::Create(); + // if (!zoneFile.Load(aZoneFilePath)) { + // qDebug() << "Error: Failed to load zone file!"; + // return false; + // } + // mTreeWidget->AddZoneFile(std::make_shared(zoneFile)); + + return true; +} + +bool MainWindow::OpenZoneFile() { + // Open file dialog to steam apps + const QString steamPath = "C:/Program Files (x86)/Steam/steamapps/common/Call of Duty World at War/zone/english/"; + const QString zoneFilePath = QFileDialog::getOpenFileName(this, "Open Zone File", steamPath, "Zone File (*.zone);;All Files (*.*)"); + if (zoneFilePath.isNull()) { + // User pressed cancel + return false; + } else if (!QFile::exists(zoneFilePath)) { + QMessageBox::warning(this, "Warning!", QString("%1 does not exist!.").arg(zoneFilePath)); + qDebug() << "Failed to open Zone file!"; + return false; + } + return true; +} + +int MainWindow::LoadFile_D3DBSP(const QString aFilePath) { + QFile file(aFilePath); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Error: Unable to open file" << aFilePath; + return 1; // ERR_FILE_NOT_FOUND + } + + QDataStream stream(&file); + stream.setByteOrder(QDataStream::LittleEndian); + + // Read Magic Value + quint32 magicValue; + stream >> magicValue; + if (magicValue != 'PSBI') { + qDebug() << "Error: Not a valid D3DBSP file"; + return 2; // ERR_NOT_A_D3DBSP_FILE + } + + // Read BSP Version and Lump Count + stream >> mBSPVersion; + stream >> mDiskLumpCount; + + // Assign diskLumpOrderSize + mDiskLumpOrder.resize(mDiskLumpCount); + + // Read Lump Index Entries + quint32 lumpOffset = + sizeof(quint32) * 3 + sizeof(LumpIndexEntry) * mDiskLumpCount; + + for (quint32 i = 0; i < mDiskLumpCount; i++) { + LumpIndexEntry indexEntry; + stream >> indexEntry.type >> indexEntry.length; + + mDiskLumpOrder[i] = indexEntry.type; + + Lump &lump = mLumps[indexEntry.type]; + lump.size = indexEntry.length; + lump.content.resize(indexEntry.length); + lump.isEmpty = false; + + qDebug() << "Lump Type:" + << Utils::LumpTypeToString((LUMP_TYPE)indexEntry.type) + << "Lump Size:" << indexEntry.length; + + // Handle offsets and padding + qint64 currentPos = file.pos(); + file.seek(lumpOffset); + file.read(lump.content.data(), lump.size); + file.seek(currentPos); + lumpOffset += Utils::PadInt4(indexEntry.length); + } + + file.close(); + return 0; // Success +} + +struct Command { + quint32 size = 0; + bool compressed = false; +}; +quint32 DXT1 = 0x31545844; // 'DXT1' +quint32 DXT3 = 0x33545844; // 'DXT3' +quint32 DXT5 = 0x35545844; // 'DXT5' + +int MainWindow::LoadFile_IWI(const QString aFilePath) { + mTreeWidget->AddIWIFile(new IWIFile(aFilePath)); + + return 0; +} + +int MainWindow::LoadFile_DDSFiles(const QStringList aFilePaths) { + for (const QString &filePath : aFilePaths) { + if (!filePath.endsWith(".dds", Qt::CaseInsensitive)) { + qDebug() << "Error: Invalid filename " << filePath; + return -1; + } + mTreeWidget->AddDDSFile(new DDSFile(filePath)); + } + return 0; +} + +void MainWindow::HandleLogEntry(const QString &entry) { + QString logContents = mLogWidget->toPlainText() + "\n" + entry; + if (mLogWidget->toPlainText().isEmpty()) { + logContents = entry; + } + mLogWidget->setPlainText(logContents); +} + +void MainWindow::HandleStatusUpdate(const QString &message, int timeout) { + statusBar()->showMessage(message, timeout); + mProgressBar->setVisible(false); // Hide progress bar if just a message +} + +void MainWindow::HandleProgressUpdate(const QString &message, int progress, int max) { + mProgressBar->setMaximum(max); + mProgressBar->setValue(progress); + mProgressBar->setVisible(true); + + QString progressText = QString("%1 (%2/%3)").arg(message).arg(progress).arg(max); + statusBar()->showMessage(progressText); +} + +int MainWindow::LoadFile_DDS(const QString aFilePath) { + if (!aFilePath.endsWith(".dds", Qt::CaseInsensitive)) { + qDebug() << "Error: Invalid filename " << aFilePath; + return -1; + } + mTreeWidget->AddDDSFile(new DDSFile(aFilePath)); + return 0; +} + +int MainWindow::LoadFile_XSUB(const QString aFilePath) { + QFile file(aFilePath); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Error: Unable to open file" << aFilePath; + return 1; // ERR_FILE_NOT_FOUND + } + + QDataStream stream(&file); + stream.setByteOrder(QDataStream::LittleEndian); + + QByteArray magic(4, Qt::Uninitialized); + stream.readRawData(magic.data(), 4); + if (magic != "KAPI") { + qDebug() << "Wrong magic, invalid XSUB file!"; + return -1; + } + qDebug() << "Magic: " << magic; + + stream.skipRawData(5 * 4); + + quint32 fileSize; + stream >> fileSize; + qDebug() << "File Size: " << fileSize; + + return 0; // Success +} + +int MainWindow::LoadFile_IPAK(const QString aFilePath) { + QFile file(aFilePath); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Error: Unable to open file" << aFilePath; + return 1; // ERR_FILE_NOT_FOUND + } + + QDataStream stream(&file); + stream.setByteOrder(QDataStream::BigEndian); + + IPAKHeader header; + stream >> header; + + if (header.version == "50000") { + if (header.magic == "KAPI") { + stream.setByteOrder(QDataStream::LittleEndian); + } else if (header.magic == "IPAK") { + stream.setByteOrder(QDataStream::BigEndian); + } else { + qDebug() << "Invalid IPAK file!"; + return -1; + } + } else { + qDebug() << "Invalid IPAK file version!"; + return -1; + } + + qDebug() << "IPAK File " << "\n" + << "- Platform: " << header.platform << "\n" + << "- Magic: " << header.magic << "\n" + << "- Size: " << header.size << "\n" + << "- Version: " << header.version << "\n" + << "- Sections: " << header.sectionCount; + + QDir outputFolder = QDir(QDir::currentPath() + "/output"); + outputFolder.remove(QDir::currentPath() + "/output"); + outputFolder.mkdir(QDir::currentPath() + "/output"); + + QVector metas = QVector(); + + QVector entries = QVector(); + QVector sections = QVector(header.sectionCount); + for (uint i = 0; i < header.sectionCount; i++) { + IPAKSection currentSection; + stream >> currentSection; + sections << currentSection; + + qDebug() << " - IPAK Section " << i + 1 << "\n" + << " - Type: " << currentSection.type << " -> " << currentSection.typeInt << "\n" + << " - Offset: " << currentSection.offset << "\n" + << " - Item (IWI) Count: " << currentSection.itemCount << "\n" + << " - Size: " << currentSection.size; + + qint64 sectionPos = stream.device()->pos(); + stream.device()->seek(currentSection.offset); + + QString sectionType = currentSection.type; + if (sectionType == "Data") { + IPAKDataChunkHeader chunkHeader; + stream >> chunkHeader; + qDebug() << " - Chunk Header\n" + << " - Count: " << chunkHeader.count << "\n" + << " - Offset: " << chunkHeader.offset; + + for (uint j = 0; j < 31; j++) { + IPAKDataChunkCommand command; + stream >> command; + if (!command.size) { continue; } + chunkHeader.commands << command; + qDebug() << " - Command\n" + << " - Size: " << command.size << "\n" + << " - Compressed: " << command.compressed; + + } + for (uint j = 0; j < chunkHeader.count; j++) { + auto command = chunkHeader.commands[j]; + + qDebug() << "Reading from " << stream.device()->pos(); + QByteArray data = stream.device()->read(command.size); + qDebug() << " to " << stream.device()->pos(); + + QString outputFilePath = outputFolder.filePath(QString("%1.iwi").arg(j)); + if (command.compressed) { + //data = Compression::DecompressLZO(data); + } + QFile outputFile(outputFilePath); + if (!outputFile.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to extract IPAK file."; + } + qDebug() << " - File Name: " << outputFile.fileName(); + outputFile.write(data); + outputFile.close(); + } + + + qDebug() << stream.device()->pos(); + stream.skipRawData(sizeof(quint32) * (31 - chunkHeader.count)); + qDebug() << stream.device()->pos(); + } else if (sectionType == "Index") { + for (uint j = 0; j < currentSection.itemCount; j++) { + IPAKIndexEntry entry; + stream >> entry; + + if (entry.size == 0) { continue; } + + entries << entry; + + quint64 entryPos = stream.device()->pos(); + qDebug() << " - Index Entry " << j + 1 << "\n" + << " - Name Hash: " << entry.nameHash << "\n" + << " - Data Hash: " << entry.dataHash << "\n" + << " - Offset: " << entry.offset << "\n" + << " - Size: " << entry.size; + + stream.device()->seek(entry.offset); + + QByteArray sectionData(entry.size, Qt::Uninitialized); + stream.readRawData(sectionData.data(), entry.size); + + const QString entryKey = QString::number(entry.nameHash); + QFile outputFile(outputFolder.filePath(QString("%1.dds").arg(entryKey))); + if (!outputFile.open(QIODevice::WriteOnly)) { + qDebug() << "Failed to extract IPAK file."; + } + qDebug() << " - File Name: " << outputFile.fileName(); + outputFile.write(sectionData); + outputFile.close(); + + stream.device()->seek(entryPos); + } + } + stream.device()->seek(sectionPos); + qDebug() << stream.device()->pos(); + } + + return 0; // Success +} + +void MainWindow::dragEnterEvent(QDragEnterEvent *event) { + const QMimeData *mimeData = event->mimeData(); + bool goodDrag = true; + if (mimeData->hasUrls()) { + foreach (const QUrl url, mimeData->urls()) { + if (!url.toString().contains(".ff") && + !url.toString().contains(".zone") && + !url.toString().contains(".ipak") && + !url.toString().contains(".d3dbsp") && + !url.toString().contains(".lzoin") && + !url.toString().contains(".xsub") && + !url.toString().contains(".iwi") && + !url.toString().contains(".dds")) { + goodDrag = false; + } + } + } else { + goodDrag = false; + } + if (goodDrag) { + event->acceptProposedAction(); + } +} + +void MainWindow::dragMoveEvent(QDragMoveEvent *event) { + Q_UNUSED(event); +} + +void MainWindow::dragLeaveEvent(QDragLeaveEvent *event) { + Q_UNUSED(event); +} + +void MainWindow::dropEvent(QDropEvent *event) { + const QMimeData *mimeData = event->mimeData(); + if (mimeData->hasUrls()) { + QStringList ddsPaths = QStringList(); + foreach (const QUrl url, mimeData->urls()) { + const QString urlStr = url.toLocalFile(); + if (urlStr.contains(".zone")) { + qDebug() << "OpenZoneFile Returned: " << OpenZoneFile(urlStr); + } else if (urlStr.contains(".ff")) { + qDebug() << "OpenFastFile Returned: " << OpenFastFile(urlStr); + } else if (urlStr.contains(".ipak")) { + qDebug() << "LoadFile_IPAK Returned: " << LoadFile_IPAK(urlStr); + } else if (urlStr.contains(".xsub")) { + qDebug() << "LoadFile_XSUB Returned: " << LoadFile_XSUB(urlStr); + } else if (urlStr.contains(".iwi")) { + qDebug() << "LoadFile_IWI Returned: " << LoadFile_IWI(urlStr); + } else if (urlStr.contains(".dds")) { + if (mimeData->urls().size() == 1) { + qDebug() << "LoadFile_DDS Returned: " << LoadFile_DDS(urlStr); + } else { + ddsPaths << urlStr; + } + } else if (urlStr.contains(".d3dbsp")) { + LoadFile_D3DBSP(urlStr); + } else if (urlStr.contains(".lzoin")) { + QFile lzoFile(urlStr); + if (!lzoFile.open(QIODevice::ReadOnly)) { + qDebug() << "LZO: Failed to read file!"; + continue; + } + QByteArray data;// = Compression::DecompressLZO(lzoFile.readAll()); + lzoFile.close(); + + if (data.isEmpty()) { + qDebug() << "LZO: Decompression gave empty result!"; + continue; + } + + QFile outputFile(url.toLocalFile().replace("lzoin", "lzoout")); + if (!outputFile.open(QIODevice::WriteOnly)) { + qDebug() << "LZO: Failed to write file!"; + continue; + } + outputFile.write(data); + outputFile.close(); + } else { + const QString ext = urlStr.split('.').last(); + ui->statusBar->showMessage( + QString("Can't display dropped file! .%1").arg(ext)); + } + } + if (ddsPaths.size() > 1) { + qDebug() << "LoadFile_DDSFiles Returned: " << LoadFile_DDSFiles(ddsPaths); + } + } else { + ui->statusBar->showMessage("Can't display dropped data!"); + } +}