forked from Mirrors/openclonk
367 lines
12 KiB
C++
367 lines
12 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
|
|
* Copyright (c) 2013, The OpenClonk Team and contributors
|
|
*
|
|
* Distributed under the terms of the ISC license; see accompanying file
|
|
* "COPYING" for details.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
|
|
* See accompanying file "TRADEMARK" for details.
|
|
*
|
|
* To redistribute this file separately, substitute the full license texts
|
|
* for the above references.
|
|
*/
|
|
|
|
/* "New scenario" editor dialogue */
|
|
|
|
#include "C4Include.h"
|
|
#include "script/C4Value.h"
|
|
#include "config/C4Config.h"
|
|
#include "editor/C4ConsoleQtNewScenario.h"
|
|
|
|
|
|
/* Definition file list model for new scenario definition selection */
|
|
|
|
C4ConsoleQtDefinitionFileListModel::DefFileInfo::DefFileInfo(C4ConsoleQtDefinitionFileListModel::DefFileInfo *parent, const char *filename, const char *root_path)
|
|
: parent(parent), filename(filename), root_path(root_path), was_opened(false), is_root(false), selected(parent->IsSelected()), disabled(parent->IsSelected() /*not a bug*/)
|
|
{
|
|
// Delay opening of groups until information is actually requested
|
|
// Full names into child groups in C4S always delimeted with backslashes
|
|
if (parent->full_filename.getLength())
|
|
full_filename = parent->full_filename + "\\" + filename;
|
|
else
|
|
full_filename = filename;
|
|
}
|
|
|
|
C4ConsoleQtDefinitionFileListModel::DefFileInfo::DefFileInfo()
|
|
: parent(nullptr), was_opened(true), is_root(true), selected(false), disabled(false)
|
|
{
|
|
// Init as root: List definitions in root paths
|
|
// Objects.ocd is always there (even if not actually found) and always first
|
|
DefFileInfo *main_objects_def = new DefFileInfo(this, C4CFN_Objects, "");
|
|
children.emplace_back(main_objects_def);
|
|
main_objects_def->SetSelected(true);
|
|
main_objects_def->SetDisabled(true);
|
|
bool has_default_objects_found = false;
|
|
for (auto & root_iter : ::Reloc)
|
|
{
|
|
const char *root = root_iter.strBuf.getData();
|
|
for (DirectoryIterator def_file_iter(root); *def_file_iter; ++def_file_iter)
|
|
{
|
|
const char *def_file = ::GetFilename(*def_file_iter);
|
|
if (WildcardMatch(C4CFN_DefFiles, def_file))
|
|
{
|
|
// Set path of main objects if found
|
|
if (!has_default_objects_found && !strcmp(C4CFN_Objects, def_file))
|
|
{
|
|
main_objects_def->root_path.Copy(root);
|
|
continue;
|
|
}
|
|
// Avoid duplicates on top level
|
|
bool dup = false;
|
|
for (auto & child : children)
|
|
if (!strcmp(child->GetName(), def_file))
|
|
{
|
|
dup = true; break;
|
|
}
|
|
if (dup) continue;
|
|
children.emplace_back(new DefFileInfo(this, def_file, root));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4ConsoleQtDefinitionFileListModel::DefFileInfo::SetSelected(bool to_val)
|
|
{
|
|
selected = to_val;
|
|
// Selection propagates to children; children of selected are disabled because they cannot be un-selected
|
|
for (auto & child : children)
|
|
{
|
|
child->SetSelected(selected);
|
|
child->SetDisabled(selected);
|
|
}
|
|
}
|
|
|
|
bool C4ConsoleQtDefinitionFileListModel::DefFileInfo::OpenGroup()
|
|
{
|
|
children.clear();
|
|
was_opened = true; // mark as opened even if fails to prevent permanent re-loading of broken groups
|
|
if (parent->IsRoot())
|
|
{
|
|
if (!grp.Open((root_path + DirectorySeparator + filename).getData())) return false;
|
|
}
|
|
else
|
|
{
|
|
if (!grp.OpenAsChild(&parent->grp, filename.getData())) return false;
|
|
}
|
|
// Init child array (without loading full groups)
|
|
StdStrBuf child_filename;
|
|
children.reserve(grp.EntryCount(C4CFN_DefFiles));
|
|
grp.ResetSearch();
|
|
while (grp.FindNextEntry(C4CFN_DefFiles, &child_filename))
|
|
children.emplace_back(new DefFileInfo(this, child_filename.getData(), nullptr));
|
|
return true;
|
|
}
|
|
|
|
int32_t C4ConsoleQtDefinitionFileListModel::DefFileInfo::GetChildCount()
|
|
{
|
|
if (!was_opened) OpenGroup();
|
|
return children.size();
|
|
}
|
|
|
|
C4ConsoleQtDefinitionFileListModel::DefFileInfo *C4ConsoleQtDefinitionFileListModel::DefFileInfo::GetChild(int32_t index)
|
|
{
|
|
if (!was_opened) OpenGroup();
|
|
if (index >= children.size()) return nullptr;
|
|
return children[index].get();
|
|
}
|
|
|
|
int32_t C4ConsoleQtDefinitionFileListModel::DefFileInfo::GetChildIndex(const DefFileInfo *child)
|
|
{
|
|
auto iter = std::find_if(children.begin(), children.end(),
|
|
[child](std::unique_ptr<DefFileInfo> & item)->bool { return item.get() == child; });
|
|
if (iter == children.end()) return -1; // not found
|
|
return int32_t(iter - children.begin());
|
|
}
|
|
|
|
void C4ConsoleQtDefinitionFileListModel::DefFileInfo::AddSelectedDefinitions(std::list<const char *> *result) const
|
|
{
|
|
// Add parent-most selected
|
|
if (IsSelected())
|
|
result->push_back(full_filename.getData());
|
|
else
|
|
for (auto &iter : children) iter->AddSelectedDefinitions(result);
|
|
}
|
|
|
|
C4ConsoleQtDefinitionFileListModel::C4ConsoleQtDefinitionFileListModel()
|
|
{
|
|
}
|
|
|
|
C4ConsoleQtDefinitionFileListModel::~C4ConsoleQtDefinitionFileListModel()
|
|
{
|
|
}
|
|
|
|
std::list<const char *> C4ConsoleQtDefinitionFileListModel::GetSelectedDefinitions() const
|
|
{
|
|
std::list<const char *> result;
|
|
root.AddSelectedDefinitions(&result);
|
|
return result;
|
|
}
|
|
|
|
int C4ConsoleQtDefinitionFileListModel::rowCount(const QModelIndex & parent) const
|
|
{
|
|
if (!parent.isValid()) return root.GetChildCount();
|
|
DefFileInfo *parent_def = static_cast<DefFileInfo *>(parent.internalPointer());
|
|
if (!parent_def) return 0;
|
|
return parent_def->GetChildCount();
|
|
}
|
|
|
|
int C4ConsoleQtDefinitionFileListModel::columnCount(const QModelIndex & parent) const
|
|
{
|
|
return 1; // Name
|
|
}
|
|
|
|
QVariant C4ConsoleQtDefinitionFileListModel::data(const QModelIndex & index, int role) const
|
|
{
|
|
DefFileInfo *def = static_cast<DefFileInfo *>(index.internalPointer());
|
|
if (!def) return QVariant();
|
|
// Query latest data from prop list
|
|
if (role == Qt::DisplayRole)
|
|
{
|
|
return QString(def->GetName());
|
|
}
|
|
else if (role == Qt::CheckStateRole)
|
|
{
|
|
return def->IsSelected() ? Qt::Checked : Qt::Unchecked;
|
|
}
|
|
// Nothing to show
|
|
return QVariant();
|
|
}
|
|
|
|
QModelIndex C4ConsoleQtDefinitionFileListModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if (column) return QModelIndex();
|
|
DefFileInfo *parent_def = &root;
|
|
if (parent.isValid()) parent_def = static_cast<DefFileInfo *>(parent.internalPointer());
|
|
if (!parent_def) return QModelIndex();
|
|
return createIndex(row, column, parent_def->GetChild(row));
|
|
}
|
|
|
|
QModelIndex C4ConsoleQtDefinitionFileListModel::parent(const QModelIndex &index) const
|
|
{
|
|
DefFileInfo *def = static_cast<DefFileInfo *>(index.internalPointer());
|
|
if (!def) return QModelIndex();
|
|
DefFileInfo *parent_def = def->GetParent();
|
|
if (!parent_def) return QModelIndex();
|
|
int32_t def_index = parent_def->GetChildIndex(def);
|
|
if (def_index < 0) return QModelIndex(); // can't happen
|
|
return createIndex(def_index, 0, parent_def);
|
|
}
|
|
|
|
Qt::ItemFlags C4ConsoleQtDefinitionFileListModel::flags(const QModelIndex &index) const
|
|
{
|
|
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
|
|
DefFileInfo *def = static_cast<DefFileInfo *>(index.internalPointer());
|
|
if (def && !def->IsDisabled()) flags |= Qt::ItemIsEnabled;
|
|
return flags;
|
|
}
|
|
|
|
bool C4ConsoleQtDefinitionFileListModel::setData(const QModelIndex& index, const QVariant& value, int role)
|
|
{
|
|
// Adjust check-state
|
|
if (role == Qt::CheckStateRole)
|
|
{
|
|
DefFileInfo *def = static_cast<DefFileInfo *>(index.internalPointer());
|
|
if (def && !def->IsDisabled())
|
|
{
|
|
def->SetSelected(value.toBool());
|
|
// Update changed index and all children
|
|
int32_t child_count = def->GetChildCount();
|
|
QModelIndex end_changed = index;
|
|
if (child_count) end_changed = createIndex(child_count - 1, 0, def->GetChild(child_count - 1));
|
|
emit dataChanged(index, end_changed);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/* New scenario dialogue */
|
|
|
|
C4ConsoleQtNewScenarioDlg::C4ConsoleQtNewScenarioDlg(class QMainWindow *parent_window)
|
|
: QDialog(parent_window, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint)
|
|
, has_custom_filename(false)
|
|
{
|
|
ui.setupUi(this);
|
|
adjustSize();
|
|
setMinimumSize(size());
|
|
ui.filenameEdit->setText(::Config.General.UserDataPath);
|
|
QItemSelectionModel *m = ui.definitionTreeView->selectionModel();
|
|
ui.definitionTreeView->setModel(&def_file_model);
|
|
delete m;
|
|
}
|
|
|
|
bool C4ConsoleQtNewScenarioDlg::SaveScenario(C4Group &grp)
|
|
{
|
|
// Save c4s
|
|
if (!c4s.Save(grp)) return false;
|
|
// Save default Objects.c with player start object
|
|
int32_t mid_x = c4s.Landscape.MapWdt.Std * c4s.Landscape.MapZoom.Std / 2;
|
|
int32_t mid_y = c4s.Landscape.MapHgt.Std * c4s.Landscape.MapZoom.Std / 2 - 8;
|
|
StdStrBuf objects_file;
|
|
objects_file.AppendFormat("public func InitializeObjects() {\n\tCreateObjectAbove(PlayerStart, %d, %d);\n}\n", (int)mid_x, (int)mid_y);
|
|
// grp.Add... does not work for unpacked groups
|
|
StdStrBuf objects_filename = grp.GetFullName();
|
|
objects_filename.AppendBackslash();
|
|
objects_filename.Append(C4CFN_ScenarioObjectsScript);
|
|
objects_file.SaveToFile(objects_filename.getData());
|
|
//return grp.Save(false); -- not needed because group is unpacked
|
|
return true;
|
|
}
|
|
|
|
void C4ConsoleQtNewScenarioDlg::CreatePressed()
|
|
{
|
|
// Take over settings
|
|
c4s.Landscape.MapWdt.SetConstant(ui.mapWidthSpinBox->value());
|
|
c4s.Landscape.MapHgt.SetConstant(ui.mapHeightSpinBox->value());
|
|
c4s.Landscape.MapZoom.SetConstant(ui.mapZoomSpinBox->value());
|
|
SCopy(ui.titleEdit->text().toUtf8(), c4s.Head.Title, C4MaxTitle);
|
|
c4s.Game.Mode.Copy(ui.gameModeComboBox->currentText().toUtf8());
|
|
if (c4s.Game.Mode == "Undefined") c4s.Game.Mode.Clear();
|
|
filename.Copy(ui.filenameEdit->text().toUtf8());
|
|
std::list<const char *> definitions = def_file_model.GetSelectedDefinitions();
|
|
if (definitions.size() > C4S_MaxDefinitions)
|
|
{
|
|
DoError(FormatString(::LoadResStr("IDS_ERR_TOOMANYDEFINITIONS"), (int)definitions.size(), (int)C4S_MaxDefinitions).getData());
|
|
ui.definitionTreeView->setFocus();
|
|
return;
|
|
}
|
|
std::ostringstream definitions_join("");
|
|
if (definitions.size())
|
|
{
|
|
// definitions_join = definitions.join(";")
|
|
auto iter_end = definitions.end();
|
|
std::copy(definitions.begin(), --iter_end, std::ostream_iterator<std::string>(definitions_join, ";"));
|
|
definitions_join << *iter_end;
|
|
}
|
|
c4s.Definitions.SetModules(definitions_join.str().c_str());
|
|
// Check validity of settings
|
|
if (!*c4s.Head.Title)
|
|
{
|
|
DoError(::LoadResStr("IDS_ERR_ENTERTITLE"));
|
|
ui.titleEdit->setFocus();
|
|
return;
|
|
}
|
|
if (ItemExists(filename.getData()))
|
|
{
|
|
DoError(::LoadResStr("IDS_ERR_NEWSCENARIOFILEEXISTS"));
|
|
ui.titleEdit->setFocus();
|
|
return;
|
|
}
|
|
// Try to create scenario
|
|
if (!CreatePath(filename.getData()))
|
|
{
|
|
DoError(::LoadResStr("IDS_ERR_CREATESCENARIO"));
|
|
ui.titleEdit->setFocus();
|
|
return;
|
|
}
|
|
C4Group grp;
|
|
if (!grp.Open(filename.getData(), false) || !SaveScenario(grp))
|
|
{
|
|
grp.Close();
|
|
EraseDirectory(filename.getData());
|
|
DoError(::LoadResStr("IDS_ERR_CREATESCENARIO"));
|
|
ui.titleEdit->setFocus();
|
|
return;
|
|
}
|
|
// Close dialogue with OK
|
|
accept();
|
|
}
|
|
|
|
// Filter for allowed characters in filename
|
|
// (Also replace space, because spaces in filenames suk)
|
|
static char ReplaceSpecialFilenameChars(char c)
|
|
{
|
|
const char *special_chars = "\\/:<>|$?\" ";
|
|
return strchr(special_chars, c) ? '_' : c;
|
|
}
|
|
|
|
void C4ConsoleQtNewScenarioDlg::TitleChanged(const QString &new_title)
|
|
{
|
|
if (!has_custom_filename)
|
|
{
|
|
// Default filename by title
|
|
std::string filename = new_title.toStdString();
|
|
std::transform(filename.begin(), filename.end(), filename.begin(), ReplaceSpecialFilenameChars);
|
|
filename += (C4CFN_ScenarioFiles+1);
|
|
ui.filenameEdit->setText(Config.AtUserDataPath(filename.c_str()));
|
|
}
|
|
}
|
|
|
|
void C4ConsoleQtNewScenarioDlg::DoError(const char *msg)
|
|
{
|
|
QMessageBox::critical(this, ::LoadResStr("IDS_ERR_TITLE"), QString(msg));
|
|
}
|
|
|
|
void C4ConsoleQtNewScenarioDlg::BrowsePressed()
|
|
{
|
|
// Browse for new filename to be used instead of the filename generated from the title
|
|
QString new_file;
|
|
for (;;)
|
|
{
|
|
new_file = QFileDialog::getSaveFileName(this, LoadResStr("IDS_CNS_NEWSCENARIO"), Config.General.UserDataPath, QString("%1 (%2)").arg(LoadResStr("IDS_CNS_SCENARIOFILE")).arg(C4CFN_ScenarioFiles), nullptr, QFileDialog::DontConfirmOverwrite);
|
|
if (!new_file.size()) return;
|
|
// Extension must be .ocs
|
|
if (!new_file.endsWith(C4CFN_ScenarioFiles + 1)) new_file += (C4CFN_ScenarioFiles + 1);
|
|
if (!ItemExists(new_file.toUtf8())) break;
|
|
// Overwriting of existing scenarios not supported
|
|
QMessageBox::critical(this, ::LoadResStr("IDS_ERR_TITLE"), ::LoadResStr("IDS_ERR_NEWSCENARIOFILEEXISTS"));
|
|
}
|
|
filename.Copy(new_file.toUtf8());
|
|
ui.filenameEdit->setText(filename.getData()); // set from converted filename just in case weird stuff happened in toUtf8
|
|
// After setting a new filename, it no longer changes when changing the title
|
|
has_custom_filename = true;
|
|
}
|