openclonk/src/editor/C4ConsoleQtNewScenario.cpp

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;
}