1
1
forked from 0ad/0ad
0ad/source/tools/atlas/AtlasUI/ArchiveViewer/ArchiveViewer.cpp
Ykkrosh 4d8133f785 # Fixes for MSVC
Use _WIN32/__WXMSW__ instead of OS_WIN.

This was SVN commit r4668.
2006-12-01 20:34:28 +00:00

636 lines
17 KiB
C++

#include "stdafx.h"
#include "ArchiveViewer.h"
#include "Misc/Version.h"
#include <set>
#include <algorithm>
#include "DatafileIO/BAR/BAR.h"
#include "DatafileIO/Stream/Stream.h"
#include "DatafileIO/Stream/wx.h"
using namespace DatafileIO;
#include "wx/dnd.h"
#include "wx/confbase.h"
#include "wx/wfstream.h"
#include "wx/tooltip.h"
#include "wx/progdlg.h"
#include "wx/regex.h"
#ifdef __WXMSW__
# include "wx/msw/registry.h"
#endif
// Possible TODO: Remove duplication of AtlasWindow code
enum {
ID_ExtractSelected,
ID_ExtractAll,
ID_EnablePreview,
ID_About
};
BEGIN_EVENT_TABLE(ArchiveViewer, wxFrame)
EVT_MENU(wxID_OPEN, ArchiveViewer::OnOpen)
EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, ArchiveViewer::OnMRUFile)
EVT_MENU(wxID_EXIT, ArchiveViewer::OnQuit)
EVT_MENU(ID_ExtractSelected, ArchiveViewer::OnExtractSelected)
EVT_MENU(ID_ExtractAll, ArchiveViewer::OnExtractAll)
EVT_MENU(ID_EnablePreview, ArchiveViewer::OnEnablePreview)
EVT_MENU(ID_About, ArchiveViewer::OnAbout)
EVT_CLOSE(ArchiveViewer::OnClose)
END_EVENT_TABLE()
//////////////////////////////////////////////////////////////////////////
class FilterTextCtrl : public wxTextCtrl
{
public:
FilterTextCtrl(wxWindow* parent, ArchiveViewer* archViewWin, const wxSize& size)
: wxTextCtrl(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, size),
m_archViewWin(archViewWin) {}
private:
void OnChange(wxCommandEvent& WXUNUSED(event))
{
m_archViewWin->SetFilter(GetValue());
}
ArchiveViewer* m_archViewWin;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(FilterTextCtrl, wxTextCtrl)
EVT_TEXT(wxID_ANY, FilterTextCtrl::OnChange)
END_EVENT_TABLE()
class FileListCtrl : public wxListCtrl
{
public:
FileListCtrl(wxWindow* parent, ArchiveViewer* archViewWin)
: wxListCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT),
m_archViewWin(archViewWin) {}
void OnSelect(wxListEvent& event)
{
m_archViewWin->UpdatePreview(event.GetIndex());
}
private:
ArchiveViewer* m_archViewWin;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(FileListCtrl, wxListCtrl)
EVT_LIST_ITEM_SELECTED(wxID_ANY, FileListCtrl::OnSelect)
END_EVENT_TABLE()
//////////////////////////////////////////////////////////////////////////
// Allow drag-and-drop of files onto the window, as a convenient way of opening them
//class DropTarget : public wxFileDropTarget
//{
//public:
// DropTarget(ColourTesterImageCtrl* imgctrl)
// : m_ImageCtrl(imgctrl)
// {}
//
// bool OnDropFiles(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y), const wxArrayString& filenames)
// {
// if (filenames.GetCount() >= 1)
// m_ImageCtrl->SetImageFile(filenames[0]);
// return true;
// }
//private:
// ColourTesterImageCtrl* m_ImageCtrl;
//};
ArchiveViewer::ArchiveViewer(wxWindow* parent)
: wxFrame(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(600, 371))
, m_FileHistory(_T("Archive Viewer"))
, m_WindowTitle(wxString::Format(_("%s - Archive Viewer"), g_ProgramNameVersion.c_str()))
, m_BARStream(NULL), m_BARReader(NULL), m_PreviewEnabled(false)
{
SetIcon(wxIcon(_T("ICON_ArchiveViewer")));
wxToolTip::Enable(true);
wxMenuBar* menuBar = new wxMenuBar;
SetMenuBar(menuBar);
wxMenu *menuFile = new wxMenu;
menuBar->Append(menuFile, _("&File"));
{
menuFile->Append(wxID_OPEN, _("&Open..."));
menuFile->AppendSeparator();//-----------
menuFile->Append(wxID_EXIT, _("E&xit"));
m_FileHistory.UseMenu(menuFile);//-------
m_FileHistory.AddFilesToMenu();
}
wxMenu *menuView = new wxMenu;
menuBar->Append(menuView, _("&View"));
{
menuView->AppendCheckItem(ID_EnablePreview, _("&Preview"));
}
wxMenu *menuExtract = new wxMenu;
menuBar->Append(menuExtract, _("&Extract"));
{
menuExtract->Append(ID_ExtractAll, _("&All files..."));
menuExtract->Append(ID_ExtractSelected, _("&Selected..."));
}
wxMenu *menuHelp = new wxMenu;
menuBar->Append(menuHelp, _("&Help"));
{
menuHelp->Append(ID_About, _("&About"));
}
m_FileHistory.Load(*wxConfigBase::Get());
m_Splitter = new SnapSplitterWindow(this, wxSP_NOBORDER | wxSP_3DSASH);
wxPanel* leftPanel = new wxPanel(m_Splitter);
//////////////////////////////////////////////////////////////////////////
// Set up sizers:
wxBoxSizer* vertSizer = new wxBoxSizer(wxVERTICAL);
leftPanel->SetSizer(vertSizer);
wxBoxSizer* filterSizer = new wxBoxSizer(wxHORIZONTAL);
//////////////////////////////////////////////////////////////////////////
// Add things to sizers:
m_FileListCtrl = new FileListCtrl(leftPanel, this);
m_FileListCtrl->InsertColumn(0, _("Filename"), wxLIST_FORMAT_LEFT, 300);
m_FileListCtrl->InsertColumn(1, _("File size (bytes)"), wxLIST_FORMAT_RIGHT, 100);
m_FileListCtrl->InsertColumn(2, _("Last modified date"), wxLIST_FORMAT_LEFT, 150);
vertSizer->Add(m_FileListCtrl, wxSizerFlags().Expand().Proportion(1).Border(wxALL, 5));
vertSizer->Add(filterSizer,
wxSizerFlags().Expand().Border(wxLEFT|wxRIGHT|wxBOTTOM, 5));
m_PreviewWindow = new FilePreviewer(m_Splitter);
m_PreviewWindow->Hide();
m_Splitter->Initialize(leftPanel);
filterSizer->Add(new wxStaticText(leftPanel, wxID_ANY, _("&Search for file:")),
wxSizerFlags().Centre().Border(wxALL, 5));
FilterTextCtrl* filterTextCtrl = new FilterTextCtrl(leftPanel, this, wxSize(150, -1));
filterTextCtrl->SetToolTip(_("You may use * to match any sequence characters, and ? to match a single character"));
filterSizer->Add(filterTextCtrl, wxSizerFlags().Border(wxALL, 5));
//////////////////////////////////////////////////////////////////////////
SetCurrentFilename();
}
void ArchiveViewer::OnAbout(wxCommandEvent& WXUNUSED(event))
{
wxMessageBox(wxString::Format(_("%s - created by Philip Taylor (philip@wildfiregames.com / philip@zaynar.demon.co.uk)"), g_ProgramNameVersion.c_str()),
_("About"), wxOK|wxCENTRE|wxICON_INFORMATION);
}
ArchiveViewer::~ArchiveViewer()
{
delete m_BARStream;
delete m_BARReader;
}
void ArchiveViewer::OnClose(wxCloseEvent& event)
{
event.Skip();
m_FileHistory.Save(*wxConfigBase::Get());
}
//struct compare_bar {
// bool operator()(const BAREntry& x, const BAREntry& y) { return (x.filename < y.filename); }
//};
void ArchiveViewer::RedrawFileList(bool keepSelection)
{
if (!m_BARReader || !m_BARStream)
// No BAR file loaded
return;
std::vector<bool> filesSelected;
filesSelected.resize(m_CachedFileData.size(), false);
// If desired, remember a list of selected and focussed items
int focussed = -1;
if (keepSelection)
{
// Iterate through selected items
long item = m_FileListCtrl->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
while (item != -1)
{
filesSelected[ m_FileListCtrl->GetItemData(item) ] = true;
item = m_FileListCtrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
}
// Remember single focussed item
item = m_FileListCtrl->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
if (item != -1)
focussed = m_FileListCtrl->GetItemData(item);
}
wxString filter;
enum { NONE, SUBSTRING, WILDCARD } filterType = NONE; // attempt to use most efficient matching system
if (m_Filter.Len())
{
if (m_Filter.Contains(_T("*")) || m_Filter.Contains(_T("?")))
{
filterType = WILDCARD;
filter = _T("*") + m_Filter.Lower() + _T("*"); // match any substring, and case insensitive
}
else
{
filterType = SUBSTRING;
filter = m_Filter.Lower();
}
}
m_FileListCtrl->Freeze();
m_FileListCtrl->DeleteAllItems();
for (size_t i = 0; i < m_CachedFileData.size(); ++i)
{
if ( (filterType == SUBSTRING && !m_CachedFileData[i].NameLower.Contains(filter))
|| (filterType == WILDCARD && !m_CachedFileData[i].NameLower.Matches(filter)))
continue;
wxListItem info;
info.SetText(m_CachedFileData[i].Name);
info.SetData((long)i);
info.SetId((long)i);
if (keepSelection)
{
int state = (filesSelected[i] ? wxLIST_STATE_SELECTED : 0) | (focussed == (int)i ? wxLIST_STATE_FOCUSED : 0);
if (state)
{
info.SetState(state);
info.SetStateMask(wxLIST_STATE_SELECTED|wxLIST_STATE_FOCUSED);
}
}
long idx = m_FileListCtrl->InsertItem(info);
wxASSERT(idx != -1);
m_FileListCtrl->SetItem(idx, 1, m_CachedFileData[i].Size);
m_FileListCtrl->SetItem(idx, 2, m_CachedFileData[i].Date);
}
m_FileListCtrl->Thaw();
}
void ArchiveViewer::UpdateFileList()
{
if (!m_BARReader || !m_BARStream)
// No BAR file loaded
return;
wxRegEx commas (_T("(\\d+)(\\d{3})"), wxRE_ADVANCED); // for comma-ising filesizes
// Get list of all files
// std::vector<BAREntry> files = m_BARReader->GetFileList(); // non-constref copy, so we can sort it
// std::sort(files.begin(), files.end(), compare_bar());
const std::vector<BAREntry>& files = m_BARReader->GetFileList();
m_CachedFileData.clear();
m_CachedFileData.resize(files.size());
for (size_t i = 0; i < files.size(); ++i)
{
m_CachedFileData[i].Name = wxString( utf16tow(files[i].filename).c_str() );
m_CachedFileData[i].NameLower = m_CachedFileData[i].Name.Lower();
m_CachedFileData[i].Size = wxString::Format(_T("%d"), files[i].filesize);
while (commas.Replace(&m_CachedFileData[i].Size, _T("\\1,\\2"))) ;
if (files[i].modified.year == 0)
m_CachedFileData[i].Date = _("Unknown");
else
{
wxDateTime date (files[i].modified.day, (wxDateTime::Month)(wxDateTime::Jan + files[i].modified.month-1), files[i].modified.year,
files[i].modified.hour, files[i].modified.minute, files[i].modified.second, files[i].modified.msecond);
m_CachedFileData[i].Date = wxString::Format(
_T("%s %s.%03d"),
date.FormatISODate().c_str(),
date.FormatISOTime().c_str(),
date.GetMillisecond());
}
}
}
void ArchiveViewer::OpenFile(const wxString& filename)
{
wxFFileInputStream* fileStream = new wxFFileInputStream(filename);
if (! fileStream->Ok())
{
wxLogError(_("Failed to open file"));
delete fileStream;
return;
}
SeekableInputStream* stream = new SeekableInputStreamFromWx(fileStream);
BARReader* reader = new BARReader(*stream);
if (! reader->Initialise())
{
wxLogError(_("Failed to read BAR file"));
delete reader;
delete stream;
return;
}
// Delete old data, replace with new data
delete m_BARStream;
delete m_BARReader;
m_BARStream = stream;
m_BARReader = reader;
UpdateFileList();
RedrawFileList(false);
m_FileHistory.AddFileToHistory(filename);
SetCurrentFilename(filename);
}
void ArchiveViewer::OnExtractSelected(wxCommandEvent& WXUNUSED(event))
{
ExtractFiles(true);
}
void ArchiveViewer::OnExtractAll(wxCommandEvent& WXUNUSED(event))
{
ExtractFiles(false);
}
void ArchiveViewer::ExtractFiles(bool onlySelected)
{
long item = -1;
if (onlySelected)
{
// Find the first selected item, and make sure there is actually a selection
item = m_FileListCtrl->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if (item == -1)
{
wxMessageBox(_("Please select one or more files to extract."), _("No files selected"), wxICON_INFORMATION);
return;
}
}
// Work out where to extract the files to:
// Assume same as last place, else default (from registry), else cwd
wxString dir = m_LastExtractDir;
if (! dir.Len())
{
dir = GetDefaultOpenDirectory();
if (! dir.Len())
dir = wxFileName::GetCwd();
}
// Let user choose final location
wxDirDialog dlg(this, wxDirSelectorPromptStr, dir);
if (dlg.ShowModal() != wxID_OK)
return;
// Remember the selection
dir = dlg.GetPath();
dir += wxFileName::GetPathSeparator();
m_LastExtractDir = dir;
wxFileName rootDir (dir);
// Construct a list of items to extract:
const std::vector<BAREntry>& files = m_BARReader->GetFileList();
std::vector<int> selection;
if (onlySelected)
{
while (item != -1)
{
selection.push_back(m_FileListCtrl->GetItemData(item)); // item was set at top of function
item = m_FileListCtrl->GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
}
}
else
{
// Extract all files
for (int i = 0; i < (int)files.size(); ++i)
selection.push_back(i);
}
bool aborted = false;
{
// Create the required directory structure:
// Extract the list of all directories:
std::set<std::wstring> dirs;
for (size_t sel = 0; sel < selection.size(); ++sel)
{
const BAREntry& file = files[selection[sel]];
wxString filename = wxString( utf16tow(file.filename).c_str() );
int lastSlash = filename.Find(_T('\\'), true);
if (lastSlash != -1)
dirs.insert(filename.Mid(0, lastSlash+1).c_str());
}
// Construct the directory tree:
for (std::set<std::wstring>::iterator it = dirs.begin(); it != dirs.end(); ++it)
{
// Append name-in-archive to target root directory, one dir
// at a time, calling mkdir at each step if necessary
wxFileName fullFilePath (rootDir);
wxFileName filePath (it->c_str(), wxPATH_WIN);
const wxArrayString& filePathDirs = filePath.GetDirs();
for (size_t i = 0; i < filePathDirs.Count(); ++i)
{
fullFilePath.AppendDir(filePathDirs[i]);
if (! wxDirExists(fullFilePath.GetPath()))
wxMkdir(fullFilePath.GetPath());
}
}
// Create progress display
wxProgressDialog progress (_("Extracting files"), _("Please wait"), (int)selection.size(), this,
wxPD_APP_MODAL | wxPD_CAN_ABORT | wxPD_ELAPSED_TIME | wxPD_REMAINING_TIME | wxPD_SMOOTH);
// Repeat for each selection file
for (size_t sel = 0; sel < selection.size(); ++sel)
{
const BAREntry& file = files[selection[sel]];
if (! progress.Update((int)sel, wxString::Format(_("Extracting %s"), file.filename.c_str())))
{
aborted = true;
break;
}
// Append name-in-archive to target root directory, one dir
// at a time, calling mkdir at each step if necessary
wxFileName filePath (wxString( utf16tow(file.filename).c_str() ), wxPATH_WIN);
filePath.MakeAbsolute(rootDir.GetPath());
// Output to disk
wxFFileOutputStream outStream(filePath.GetFullPath());
if (! outStream.Ok())
wxLogError(wxString::Format(_("Error opening output file %s"), filePath.GetFullPath().c_str()));
else
{
SeekableOutputStreamFromWx str (outStream);
m_BARReader->TransferFile(file, str);
outStream.Close();
// Set the file's timestamps to match the one specified in the archive
if (file.modified.year != 0)
{
wxDateTime date (file.modified.day, (wxDateTime::Month)(wxDateTime::Jan + file.modified.month-1), file.modified.year,
file.modified.hour, file.modified.minute, file.modified.second, file.modified.msecond);
filePath.SetTimes(NULL, &date, &date);
}
}
}
}
if (aborted)
wxMessageBox(_("Extraction aborted."), _("Stopped"));
else
wxMessageBox(_("Extraction complete."), _("Finished"));
}
void ArchiveViewer::OnEnablePreview(wxCommandEvent& event)
{
m_PreviewEnabled = event.IsChecked();
if (m_PreviewEnabled)
{
// Make sure the window's not far too small
if (GetSize().GetWidth() < 700)
SetSize(-1, -1, 900, -1); // nobody is still using 800x600, are they?
m_Splitter->SetDefaultSashPosition(600);
m_Splitter->SplitVertically(m_Splitter->GetWindow1(), m_PreviewWindow);
}
else
m_Splitter->Unsplit();
UpdatePreview();
}
void ArchiveViewer::UpdatePreview(long item)
{
if (! m_PreviewEnabled)
return;
// If the user opens a large archive file, enables the preview, selects the
// bottom file, and presses shift+home, it attempts to redraw the preview
// for every single file in sequence. That takes forever; so provide a way
// to abort the preview update.
if (wxGetKeyState(WXK_ESCAPE))
return;
// If none is specified, preview the first selected item
if (item == -1)
item = m_FileListCtrl->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if (item == -1) // none selected
return;
const BAREntry& file = m_BARReader->GetFileList()[ m_FileListCtrl->GetItemData(item) ];
SeekableInputStream* str = m_BARReader->GetFile(file);
m_PreviewWindow->PreviewFile(wxString(utf16tow(file.filename).c_str()), *str);
delete str;
}
void ArchiveViewer::OnOpen(wxCommandEvent& WXUNUSED(event))
{
wxString path, name;
if (GetCurrentFilename().IsOk())
{
path = GetCurrentFilename().GetPath();
name = GetCurrentFilename().GetFullName();
}
else
{
path = GetDefaultOpenDirectory();
}
wxFileDialog dlg (this, _("Select archive file to open"), path, name, _("BAR files (*.bar)|*.bar|All files (*.*)|*.*"), wxOPEN);
if (dlg.ShowModal() != wxID_OK)
return;
OpenFile(dlg.GetPath());
}
void ArchiveViewer::OnMRUFile(wxCommandEvent& event)
{
wxString file (m_FileHistory.GetHistoryFile(event.GetId() - wxID_FILE1));
if (file.Len())
OpenFile(file);
}
void ArchiveViewer::OnQuit(wxCommandEvent& WXUNUSED(event))
{
Close();
}
void ArchiveViewer::SetCurrentFilename(wxFileName filename)
{
m_CurrentFilename = filename;
if (filename.IsOk())
SetTitle(m_WindowTitle + _T(" - ") + filename.GetFullName());
else
SetTitle(m_WindowTitle);
}
wxString ArchiveViewer::GetDefaultOpenDirectory()
{
// TODO
/*
wxRegKey key (_T("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft Games\\Age of Empires 3\\1.0"));
if (key.Exists())
{
wxString dir;
// The path might be the default value under the AppPath key, or it
// might be the AppPath value; so check both.
wxRegKey subkey (key, _T("AppPath"));
if (subkey.Exists())
{
if (subkey.QueryValue(_T(""), dir)) // default value
return dir;
}
else
{
if (key.HasValue(_T("AppPath")) && key.QueryValue(_T("AppPath"), dir))
return dir;
}
}
*/
return _T("");
}
void ArchiveViewer::SetFilter(const wxString& filter)
{
m_Filter = filter;
RedrawFileList(true);
}