diff --git a/src/customprops/directory_prop.cpp b/src/customprops/directory_prop.cpp index 76ce7b248..80acf784e 100644 --- a/src/customprops/directory_prop.cpp +++ b/src/customprops/directory_prop.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////// // Purpose: Derived wxStringProperty class for choosing a directory // Author: Ralph Walden -// Copyright: Copyright (c) 2021-2023 KeyWorks Software (Ralph Walden) +// Copyright: Copyright (c) 2021-2025 KeyWorks Software (Ralph Walden) // License: Apache License -- see ../../LICENSE ///////////////////////////////////////////////////////////////////////////// @@ -34,24 +34,43 @@ bool DirectoryDialogAdapter::DoShowDialog(wxPropertyGrid* propGrid, wxPGProperty dlg_pos = propGrid->GetGoodEditorDialogPosition(property, dlg_sz); } - tt_string path = Project.getProjectPath(); + wxFileName path; auto node = m_prop->getNode(); if (node->isGen(gen_wxFilePickerCtrl)) { - path = m_prop->as_string().size() ? m_prop->as_string() : Project.getProjectPath(); + if (m_prop->as_string().size()) + { + path.AssignDir(m_prop->as_string()); + } + else + { + path = *Project.get_wxFileName(); + path.SetFullName(wxEmptyString); // clear the project filename + } } else { - path.append_filename(m_prop->as_string()); - path.make_absolute(); + path = *Project.get_wxFileName(); + path.SetFullName(wxEmptyString); // clear the project filename + if (m_prop->as_string().size()) + { + wxFileName prop_path; + prop_path.AssignDir(m_prop->as_string()); + for (auto& iter: prop_path.GetDirs()) + { + path.AppendDir(iter); + } + path.MakeAbsolute(); + } } // If the directory doesn't exist, then we need to reset it. Otherwise on Windows, the // dialog will be for the computer, requiring the user to drill down to where the project // file is. - if (!node->isGen(gen_wxFilePickerCtrl) && !path.dir_exists()) + if (!node->isGen(gen_wxFilePickerCtrl) && !path.DirExists()) { - path = Project.getProjectPath(); + path = *Project.get_wxFileName(); + path.SetFullName(wxEmptyString); // clear the project filename } auto style = wxDD_DEFAULT_STYLE | wxDD_CHANGE_DIR; @@ -60,7 +79,7 @@ bool DirectoryDialogAdapter::DoShowDialog(wxPropertyGrid* propGrid, wxPGProperty style |= wxDD_DIR_MUST_EXIST; } - wxDirDialog dlg(propGrid, wxDirSelectorPromptStr, path.make_wxString(), style, dlg_pos, dlg_sz); + wxDirDialog dlg(propGrid, wxDirSelectorPromptStr, path.GetPath(), style, dlg_pos, dlg_sz); if (dlg.ShowModal() == wxID_OK) { SetValue(dlg.GetPath()); diff --git a/src/customprops/img_string_prop.cpp b/src/customprops/img_string_prop.cpp index fa1ba864d..54e44efd4 100644 --- a/src/customprops/img_string_prop.cpp +++ b/src/customprops/img_string_prop.cpp @@ -1,7 +1,7 @@ ///////////////////////////////////////////////////////////////////////////// // Purpose: Derived wxStringProperty class for handling wxImage files or art // Author: Ralph Walden -// Copyright: Copyright (c) 2021-2023 KeyWorks Software (Ralph Walden) +// Copyright: Copyright (c) 2021-2025 KeyWorks Software (Ralph Walden) // License: Apache License -- see ../../LICENSE ///////////////////////////////////////////////////////////////////////////// @@ -68,9 +68,9 @@ bool ImageDialogAdapter::DoShowDialog(wxPropertyGrid* propGrid, wxPGProperty* WX tt_cwd cwd(true); if (Project.hasValue(prop_art_directory)) { - if (auto dir = Project.ArtDirectory(); dir.dir_exists()) + if (auto dir = Project.getArtPath(); dir->DirExists()) { - wxFileName::SetCwd(dir.make_wxString()); + dir->SetCwd(); } } diff --git a/src/customprops/tt_file_property.cpp b/src/customprops/tt_file_property.cpp index 21ed8eaf7..b0fe8159b 100644 --- a/src/customprops/tt_file_property.cpp +++ b/src/customprops/tt_file_property.cpp @@ -1,11 +1,12 @@ ///////////////////////////////////////////////////////////////////////////// // Purpose: Version of wxFileProperty specific to wxUiEditor // Author: Ralph Walden -// Copyright: Copyright (c) 2024 KeyWorks Software (Ralph Walden) +// Copyright: Copyright (c) 2024-2025 KeyWorks Software (Ralph Walden) // License: Apache License -- see ../../LICENSE ///////////////////////////////////////////////////////////////////////////// #include +#include // wxFileName - encapsulates a file path #include "node_prop.h" // for wxNodeProperty #include "project_handler.h" // ProjectHandler class @@ -29,7 +30,7 @@ ttFileProperty::ttFileProperty(const wxString& label, const wxString& name, cons bool ttFileProperty::DisplayEditorDialog(wxPropertyGrid* pg, wxVariant& value) { - tt_string root_path; + wxFileName root_path; wxString wildcard; wxString title; auto* form = m_prop->getNode()->getForm(); @@ -39,48 +40,45 @@ bool ttFileProperty::DisplayEditorDialog(wxPropertyGrid* pg, wxVariant& value) { case prop_base_file: if (folder && folder->hasValue(prop_folder_base_directory)) - root_path = folder->as_string(prop_folder_base_directory); + root_path.AssignDir(folder->as_string(prop_folder_base_directory)); else if (Project.getProjectNode()->hasValue(prop_base_directory)) - root_path = Project.getProjectNode()->as_string(prop_base_directory); + root_path.AssignDir(Project.getProjectNode()->as_string(prop_base_directory)); else - root_path = Project.getProjectPath(); + root_path.AssignDir(Project.get_wxFileName()->GetPath()); title = "Base class filename"; wildcard = "C++ Files|*.cpp;*.cc;*.cxx"; break; case prop_derived_file: - if (folder && folder->hasValue(prop_folder_derived_directory)) - root_path = folder->as_string(prop_folder_derived_directory); + root_path.AssignDir(folder->as_string(prop_folder_derived_directory)); else if (Project.getProjectNode()->hasValue(prop_derived_directory)) - root_path = Project.getProjectNode()->as_string(prop_derived_directory); + root_path.AssignDir(Project.getProjectNode()->as_string(prop_derived_directory)); else - root_path = Project.getProjectPath(); + root_path.AssignDir(Project.get_wxFileName()->GetPath()); title = "Derived class filename"; wildcard = "C++ Files|*.cpp;*.cc;*.cxx"; break; - case prop_xrc_file: - case prop_combined_xrc_file: - case prop_folder_combined_xrc_file: - if (folder && folder->hasValue(prop_folder_xrc_directory)) - root_path = folder->as_string(prop_folder_xrc_directory); - else if (Project.getProjectNode()->hasValue(prop_xrc_directory)) - root_path = Project.getProjectNode()->as_string(prop_xrc_directory); + case prop_perl_file: + if (folder && folder->hasValue(prop_folder_perl_output_folder)) + root_path.AssignDir(folder->as_string(prop_folder_perl_output_folder)); + else if (Project.getProjectNode()->hasValue(prop_perl_output_folder)) + root_path.AssignDir(Project.getProjectNode()->as_string(prop_perl_output_folder)); else - root_path = Project.getProjectPath(); - title = "XRC filename"; - wildcard = "XRC Files|*.xrc"; + root_path.AssignDir(Project.get_wxFileName()->GetPath()); + title = "Perl filename"; + wildcard = "Perl Files|*.pl;*.pm"; break; case prop_python_file: case prop_python_combined_file: if (folder && folder->hasValue(prop_folder_python_output_folder)) - root_path = folder->as_string(prop_folder_python_output_folder); + root_path.AssignDir(folder->as_string(prop_folder_python_output_folder)); else if (Project.getProjectNode()->hasValue(prop_python_output_folder)) - root_path = Project.getProjectNode()->as_string(prop_python_output_folder); + root_path.AssignDir(Project.getProjectNode()->as_string(prop_python_output_folder)); else - root_path = Project.getProjectPath(); + root_path.AssignDir(Project.get_wxFileName()->GetPath()); title = "Python filename"; wildcard = "Python Files|*.py"; break; @@ -88,69 +86,73 @@ bool ttFileProperty::DisplayEditorDialog(wxPropertyGrid* pg, wxVariant& value) case prop_ruby_file: case prop_ruby_combined_file: if (folder && folder->hasValue(prop_folder_ruby_output_folder)) - root_path = folder->as_string(prop_folder_ruby_output_folder); + root_path.AssignDir(folder->as_string(prop_folder_ruby_output_folder)); else if (Project.getProjectNode()->hasValue(prop_ruby_output_folder)) - root_path = Project.getProjectNode()->as_string(prop_ruby_output_folder); + root_path.AssignDir(Project.getProjectNode()->as_string(prop_ruby_output_folder)); else - root_path = Project.getProjectPath(); + root_path.AssignDir(Project.get_wxFileName()->GetPath()); title = "Ruby filename"; wildcard = "Ruby Files|*.rb;*.rbw"; break; + case prop_rust_file: + if (folder && folder->hasValue(prop_folder_rust_output_folder)) + root_path.AssignDir(folder->as_string(prop_folder_rust_output_folder)); + else if (Project.getProjectNode()->hasValue(prop_rust_output_folder)) + root_path.AssignDir(Project.getProjectNode()->as_string(prop_rust_output_folder)); + else + root_path.AssignDir(Project.get_wxFileName()->GetPath()); + title = "Rust filename"; + wildcard = "Rust Files|*.rust"; + break; + + case prop_xrc_file: + case prop_combined_xrc_file: + case prop_folder_combined_xrc_file: + if (folder && folder->hasValue(prop_folder_xrc_directory)) + root_path.AssignDir(folder->as_string(prop_folder_xrc_directory)); + else if (Project.getProjectNode()->hasValue(prop_xrc_directory)) + root_path.AssignDir(Project.getProjectNode()->as_string(prop_xrc_directory)); + else + root_path.AssignDir(Project.get_wxFileName()->GetPath()); + title = "XRC filename"; + wildcard = "XRC Files|*.xrc"; + break; + +#if GENERATE_NEW_LANG_CODE case prop_fortran_file: if (folder && folder->hasValue(prop_folder_fortran_output_folder)) - root_path = folder->as_string(prop_folder_fortran_output_folder); + root_path.AssignDir(folder->as_string(prop_folder_fortran_output_folder)); else if (Project.getProjectNode()->hasValue(prop_fortran_output_folder)) - root_path = Project.getProjectNode()->as_string(prop_fortran_output_folder); + root_path.AssignDir(Project.getProjectNode()->as_string(prop_fortran_output_folder)); else - root_path = Project.getProjectPath(); + root_path.AssignDir(Project.get_wxFileName()->GetPath()); title = "Fortran filename"; wildcard = "Fortran Files|*.f90;*.f95;*.f03;*.f08"; break; case prop_haskell_file: if (folder && folder->hasValue(prop_folder_haskell_output_folder)) - root_path = folder->as_string(prop_folder_haskell_output_folder); + root_path.AssignDir(folder->as_string(prop_folder_haskell_output_folder)); else if (Project.getProjectNode()->hasValue(prop_haskell_output_folder)) - root_path = Project.getProjectNode()->as_string(prop_haskell_output_folder); + root_path.AssignDir(Project.getProjectNode()->as_string(prop_haskell_output_folder)); else - root_path = Project.getProjectPath(); + root_path.AssignDir(Project.get_wxFileName()->GetPath()); title = "Haskell filename"; wildcard = "Haskell Files|*.hs;*.lhs"; break; case prop_lua_file: if (folder && folder->hasValue(prop_folder_lua_output_folder)) - root_path = folder->as_string(prop_folder_lua_output_folder); + root_path.AssignDir(folder->as_string(prop_folder_lua_output_folder)); else if (Project.getProjectNode()->hasValue(prop_lua_output_folder)) - root_path = Project.getProjectNode()->as_string(prop_lua_output_folder); + root_path.AssignDir(Project.getProjectNode()->as_string(prop_lua_output_folder)); else - root_path = Project.getProjectPath(); + root_path.AssignDir(Project.get_wxFileName()->GetPath()); title = "Lua filename"; wildcard = "Lua Files|*.lua"; break; - - case prop_perl_file: - if (folder && folder->hasValue(prop_folder_perl_output_folder)) - root_path = folder->as_string(prop_folder_perl_output_folder); - else if (Project.getProjectNode()->hasValue(prop_perl_output_folder)) - root_path = Project.getProjectNode()->as_string(prop_perl_output_folder); - else - root_path = Project.getProjectPath(); - title = "Perl filename"; - wildcard = "Perl Files|*.pl;*.pm"; - break; - - case prop_rust_file: - if (folder && folder->hasValue(prop_folder_rust_output_folder)) - root_path = folder->as_string(prop_folder_rust_output_folder); - else if (Project.getProjectNode()->hasValue(prop_rust_output_folder)) - root_path = Project.getProjectNode()->as_string(prop_rust_output_folder); - else - root_path = Project.getProjectPath(); - title = "Rust filename"; - wildcard = "Rust Files|*.rust"; - break; +#endif // GENERATE_NEW_LANG_CODE case prop_cmake_file: case prop_folder_cmake_file: @@ -175,15 +177,15 @@ bool ttFileProperty::DisplayEditorDialog(wxPropertyGrid* pg, wxVariant& value) case prop_data_file: if (m_prop->as_string().size()) { - root_path = m_prop->as_string(); - root_path.remove_filename(); + root_path.Assign(m_prop->as_string()); } else { auto result = Project.GetOutputPath(m_prop->getNode()->getForm(), GEN_LANG_CPLUSPLUS); - root_path = result.first; - if (result.second) // true if the the base filename was returned - root_path.remove_filename(); + if (!result.second) + root_path.AssignDir(result.first); + else + root_path.Assign(result.first); } if (m_prop->getNode()->isGen(gen_data_xml)) { @@ -203,30 +205,24 @@ bool ttFileProperty::DisplayEditorDialog(wxPropertyGrid* pg, wxVariant& value) } // switch (m_prop->get_name()) - tt_string full_path = root_path; - auto cur_path = value.GetString().utf8_string(); + wxFileName full_path; + full_path.AssignDir(root_path.GetPath()); + auto cur_path = value.GetString(); if (cur_path.starts_with("./")) { - full_path = cur_path; - } - else - { - full_path.append_filename(cur_path); + full_path.Assign(cur_path); } - full_path.make_absolute(); - wxString filename = full_path.filename().make_wxString(); - full_path.remove_filename(); - wxFileDialog dlg(pg->GetPanel(), title, full_path.make_wxString(), filename, wildcard, wxFD_SAVE); + full_path.MakeAbsolute(); + wxFileDialog dlg(pg->GetPanel(), title, full_path.GetPath(), full_path.GetFullName(), wildcard, wxFD_SAVE); if (dlg.ShowModal() == wxID_OK) { - full_path = dlg.GetPath().utf8_string(); - // Note that no matter whether the filename is in one of the base or output directories, we - // always make it relative to the project path. - full_path.make_relative(Project.getProjectPath()); - full_path.backslashestoforward(); - if (!full_path.contains("/")) - full_path = "./" + full_path; - value = full_path.make_wxString(); + full_path.Assign(dlg.GetPath()); + full_path.MakeRelativeTo(Project.get_wxFileName()->GetPath()); + tt_string final_path = full_path.GetFullPath().utf8_string(); + final_path.backslashestoforward(); + if (!final_path.contains("/")) + final_path = "./" + full_path.GetFullPath().utf8_string(); + value = final_path.make_wxString(); return true; } return false; diff --git a/src/mainframe.cpp b/src/mainframe.cpp index b9e4d0440..dde5012eb 100644 --- a/src/mainframe.cpp +++ b/src/mainframe.cpp @@ -609,54 +609,55 @@ void MainFrame::OnSaveProject(wxCommandEvent& event) void MainFrame::OnSaveAsProject(wxCommandEvent&) { - tt_string filename = Project.getProjectFile().filename(); - if (filename.is_sameas(txtEmptyProject)) + wxFileName filename(*Project.get_wxFileName()); + if (!filename.IsOk()) { - filename = "MyProject"; + filename.Assign("MyProject"); } - tt_string path = Project.getProjectPath(); + // The ".wxue" extension is only used for testing -- all normal projects should have a .wxui extension - wxFileDialog dialog(this, "Save Project As", path.make_wxString(), filename.make_wxString(), + wxFileDialog dialog(this, "Save Project As", wxFileName::GetCwd(), filename.GetFullName(), "wxUiEditor Project File (*.wxui)|*.wxui;*.wxue", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); if (dialog.ShowModal() == wxID_OK) { - filename = dialog.GetPath().utf8_string(); - if (filename.extension().empty()) + filename = dialog.GetPath(); + // Note that under Windows, any extension the user added will be followed with a .wxui extension + auto ext = filename.GetExt(); + if (ext.empty()) { - filename.replace_extension(".wxui"); + filename.SetExt("wxui"); } // Don't allow the user to walk over existing project file types that are probably associated with another // designer tool - else if (filename.extension().is_sameas(".fbp", tt::CASE::either)) + else if (ext.CmpNoCase("fbp") == 0) { wxMessageBox("You cannot save the project as a wxFormBuilder project file", "Save Project As"); return; } - else if (filename.extension().is_sameas(".fjd", tt::CASE::either)) + else if (ext.CmpNoCase("fjd") == 0) { wxMessageBox("You cannot save the project as a DialogBlocks project file", "Save Project As"); return; } - else if (filename.extension().is_sameas(".wxg", tt::CASE::either)) + else if (ext.CmpNoCase("wxg") == 0) { wxMessageBox("You cannot save the project as a wxGlade file", "Save Project As"); return; } - else if (filename.extension().is_sameas(".wxs", tt::CASE::either)) + else if (ext.CmpNoCase("wxs") == 0) { wxMessageBox("You cannot save the project as a wxSmith file", "Save Project As"); return; } - else if (filename.extension().is_sameas(".xrc", tt::CASE::either)) + else if (ext.CmpNoCase("xrc") == 0) { wxMessageBox("You cannot save the project as a XRC file", "Save Project As"); return; } - else if (filename.extension().is_sameas(".rc", tt::CASE::either) || - filename.extension().is_sameas(".dlg", tt::CASE::either)) + else if (ext.CmpNoCase("rc") == 0 || ext.CmpNoCase("dlg") == 0) { wxMessageBox("You cannot save the project as a Windows Resource file", "Save Project As"); return; @@ -664,18 +665,18 @@ void MainFrame::OnSaveAsProject(wxCommandEvent&) pugi::xml_document doc; Project.getProjectNode()->createDoc(doc); - if (doc.save_file(filename, " ", pugi::format_indent_attributes)) + if (doc.save_file(filename.GetFullPath().utf8_string(), " ", pugi::format_indent_attributes)) { m_isProject_modified = false; m_isImported = false; - m_FileHistory.AddFileToHistory(filename); - Project.setProjectFile(filename); + m_FileHistory.AddFileToHistory(filename.GetFullPath()); + Project.setProjectPath(&filename); ProjectSaved(); FireProjectLoadedEvent(); } else { - wxMessageBox(wxString("Unable to save the project: ") << filename, "Save Project As"); + wxMessageBox(wxString("Unable to save the project: ") << filename.GetFullPath(), "Save Project As"); } }; } diff --git a/src/paths.cpp b/src/paths.cpp index 79ee3be01..43d7d787e 100644 --- a/src/paths.cpp +++ b/src/paths.cpp @@ -1,12 +1,14 @@ ///////////////////////////////////////////////////////////////////////////// // Purpose: Functions for directory and file properties // Author: Ralph Walden -// Copyright: Copyright (c) 2022-2024 KeyWorks Software (Ralph Walden) +// Copyright: Copyright (c) 2022-2025 KeyWorks Software (Ralph Walden) // License: Apache License -- see ../LICENSE ///////////////////////////////////////////////////////////////////////////// // This module handles changes to art_directory, base_directory, and derived_directory +#include // wxFileName - encapsulates a file path + #include "paths.h" #include "mainframe.h" // MainFrame -- Main window frame @@ -16,25 +18,25 @@ void AllowDirectoryChange(wxPropertyGridEvent& event, NodeProperty* /* prop */, Node* /* node */) { - tt_string newValue = event.GetPropertyValue().GetString().utf8_string(); - if (newValue.empty()) + wxFileName newValue; + newValue.AssignDir(event.GetPropertyValue().GetString()); + if (!newValue.IsOk()) return; - newValue.make_absolute(); - newValue.make_relative(Project.getProjectPath()); - newValue.backslashestoforward(); + newValue.MakeAbsolute(); + newValue.MakeRelativeTo(Project.get_wxFileName()->GetPath()); tt_cwd cwd(true); Project.ChangeDir(); - if (!newValue.dir_exists()) + if (!newValue.DirExists()) { // Displaying the message box can cause a focus change event which will call validation again in the OnIdle() // processing. Preserve the focus to avoid validating twice. auto focus = wxWindow::FindFocus(); - auto result = wxMessageBox(tt_string() << "The directory \"" << newValue - << "\" does not exist. Do you want to use this name anyway?", + auto result = wxMessageBox(wxString() << "The directory \"" << newValue.GetFullPath() + << "\" does not exist. Do you want to use this name anyway?", "Directory doesn't exist", wxYES_NO | wxICON_WARNING, wxGetMainFrame()); if (focus) { @@ -53,7 +55,7 @@ void AllowDirectoryChange(wxPropertyGridEvent& event, NodeProperty* /* prop */, // If the event was previously veto'd, and the user corrected the file, then we have to set it here, // otherwise it will revert back to the original name before the Veto. - event.GetProperty()->SetValueFromString(newValue); + event.GetProperty()->SetValueFromString(newValue.GetFullPath()); } // Unlike the AllowDirectoryChange() above, this will *not* allow a duplicate prop_base_file filename since the generated @@ -65,15 +67,16 @@ void AllowFileChange(wxPropertyGridEvent& event, NodeProperty* prop, Node* node) if (prop->isProp(prop_base_file) || prop->isProp(prop_python_file) || prop->isProp(prop_ruby_file) || prop->isProp(prop_xrc_file)) { - tt_string newValue = event.GetPropertyValue().GetString().utf8_string(); - if (newValue.empty()) + wxFileName newValue; + newValue.Assign(event.GetPropertyValue().GetString()); + if (!newValue.IsOk()) return; - newValue.make_absolute(); - newValue.make_relative(Project.getProjectPath()); - newValue.backslashestoforward(); + newValue.MakeAbsolute(); + newValue.MakeRelativeTo(Project.get_wxFileName()->GetPath()); - auto filename = newValue; + tt_string filename = newValue.GetFullPath().utf8_string(); + filename.backslashestoforward(); std::vector forms; Project.CollectForms(forms); @@ -144,7 +147,28 @@ void AllowFileChange(wxPropertyGridEvent& event, NodeProperty* prop, Node* node) return; } } - else + else if (prop->isProp(prop_perl_file)) + { + if (child->as_string(prop_perl_file).filename() == filename) + { + auto focus = wxWindow::FindFocus(); + + wxMessageBox(wxString() << "The perl filename \"" << filename.make_wxString() + << "\" is already in use by " << child->as_string(prop_class_name) + << "\n\nEither change the name, or press ESC to restore the original name.", + "Duplicate perl filename", wxICON_STOP); + if (focus) + { + focus->SetFocus(); + } + + event.Veto(); + event.SetValidationFailureBehavior(wxPGVFBFlags::MarkCell | wxPGVFBFlags::StayInProperty); + wxGetFrame().setStatusField("Either change the name, or press ESC to restore the original value."); + return; + } + } + else if (prop->isProp(prop_xrc_file)) { // Currently, XRC files don't have a directory property, so the full path // relative to the project file is what we check. It *is* valid to have the @@ -174,7 +198,7 @@ void AllowFileChange(wxPropertyGridEvent& event, NodeProperty* prop, Node* node) // If the event was previously veto'd, and the user corrected the file, then we have to set it here, // otherwise it will revert back to the original name before the Veto. - event.GetProperty()->SetValueFromString(newValue); + event.GetProperty()->SetValueFromString(newValue.GetFullPath()); } } @@ -183,19 +207,26 @@ void OnPathChanged(wxPropertyGridEvent& event, NodeProperty* prop, Node* node) // If the user clicked the path button, the current directory may have changed. Project.ChangeDir(); - tt_string newValue = event.GetPropertyValue().GetString().utf8_string(); + wxFileName newValue; + newValue.AssignDir(event.GetPropertyValue().GetString()); + if (!newValue.IsOk()) + return; + if (!node->isGen(gen_wxFilePickerCtrl)) { - newValue.make_absolute(); - newValue.make_relative(Project.getProjectPath()); - newValue.backslashestoforward(); + newValue.MakeAbsolute(); + newValue.MakeRelativeTo(Project.get_wxFileName()->GetPath()); } + + tt_string dir = newValue.GetFullPath().utf8_string(); + dir.backslashestoforward(); + // Note that on Windows, even though we changed the property to a forward slash, it will still be displayed // with a backslash. However, modifyProperty() will save our forward slash version, so even thought the // display isn't correct, it will be stored in the project file correctly. - event.GetProperty()->SetValueFromString(newValue); - tt_string value(newValue); + event.GetProperty()->SetValueFromString(dir.make_wxString()); + tt_string value(dir); if (value != prop->as_string()) { if (prop->isProp(prop_derived_directory)) diff --git a/src/project/loadproject.cpp b/src/project/loadproject.cpp index 0d6ad4c58..ede47459f 100644 --- a/src/project/loadproject.cpp +++ b/src/project/loadproject.cpp @@ -1,12 +1,13 @@ ///////////////////////////////////////////////////////////////////////////// // Purpose: Load wxUiEditor project // Author: Ralph Walden -// Copyright: Copyright (c) 2020-2024 KeyWorks Software (Ralph Walden) +// Copyright: Copyright (c) 2020-2025 KeyWorks Software (Ralph Walden) // License: Apache License -- see ../../LICENSE ///////////////////////////////////////////////////////////////////////////// -#include // A wxWidgets implementation of Scintilla. -#include // Miscellaneous utilities +#include // wxFileName - encapsulates a file path +#include // A wxWidgets implementation of Scintilla. +#include // Miscellaneous utilities #include "base_generator.h" // BaseGenerator -- Base widget generator class #include "dlg_msgs.h" // wxMessageDialog dialogs @@ -1124,17 +1125,12 @@ bool ProjectHandler::NewProject(bool create_empty, bool allow_ui) m_project_node->set_value(prop_src_preamble, preamble); } - // Set the current working directory to the first file imported. - tt_string path(file_list[0]); - if (path.size()) + wxFileName path(file_list[0]); + if (path.IsOk()) { - path.replace_extension(".wxui"); - path.make_absolute(); - path.backslashestoforward(); - m_projectFile = path; - m_projectPath = m_projectFile; - m_projectPath.make_absolute(); - m_projectPath.remove_filename(); + path.SetExt("wxui"); + path.MakeAbsolute(); + setProjectPath(&path); } wxGetFrame().setImportedFlag(); } diff --git a/src/project/project_handler.cpp b/src/project/project_handler.cpp index 0b617b8f4..ce2afff48 100644 --- a/src/project/project_handler.cpp +++ b/src/project/project_handler.cpp @@ -1,12 +1,13 @@ ///////////////////////////////////////////////////////////////////////////// // Purpose: ProjectHandler class // Author: Ralph Walden -// Copyright: Copyright (c) 2020-2024 KeyWorks Software (Ralph Walden) +// Copyright: Copyright (c) 2020-2025 KeyWorks Software (Ralph Walden) // License: Apache License -- see ../LICENSE ///////////////////////////////////////////////////////////////////////////// #include #include +#include #include #include // wxAnimation and wxAnimationCtrl @@ -28,6 +29,19 @@ ProjectHandler& Project = ProjectHandler::getInstance(); +ProjectHandler::ProjectHandler() +{ + m_project_path = std::make_unique(); + m_art_path = std::make_unique(); +} + +ProjectHandler::~ProjectHandler() +{ + // m_project_path will be automatically deleted. We need the destructor to be defined in the + // source module so that wx/filename.h doesn't need to be included in the header file (and + // therefore in every file that needs to include the ProjectHandler header file). +} + void ProjectHandler::Initialize(NodeSharedPtr project, bool allow_ui) { m_project_node = project; @@ -40,12 +54,37 @@ void ProjectHandler::Initialize(NodeSharedPtr project, bool allow_ui) ProjectData.Clear(); } +void ProjectHandler::setProjectPath(const wxFileName* path) +{ + m_project_path->Assign(*path); + + // If the Project File is being set, then assume the art directory will need to be changed + m_art_path->Clear(); +} + void ProjectHandler::setProjectFile(const tt_string& file) { - m_projectFile = file; - m_projectPath = m_projectFile; - m_projectPath.make_absolute(); - m_projectPath.remove_filename(); + ASSERT(m_project_path); + m_project_path->Assign(file); + m_project_path->MakeAbsolute(); + + // If the Project File is being set, then assume the art directory will need to be changed + m_art_path->Clear(); +} + +tt_string ProjectHandler::getProjectFile() const +{ + return m_project_path->GetFullPath().utf8_string(); +} + +tt_string ProjectHandler::getProjectPath() const +{ + return m_project_path->GetPath().utf8_string(); +} + +bool ProjectHandler::ChangeDir() const +{ + return m_project_path->SetCwd(); } void ProjectHandler::CollectForms(std::vector& forms, Node* node_start) @@ -77,9 +116,11 @@ void ProjectHandler::FixupDuplicatedNode(Node* new_node) std::set derived_classnames; std::set base_filenames; std::set derived_filenames; - std::set xrc_filenames; + std::set perl_filenames; std::set python_filenames; std::set ruby_filenames; + std::set rust_filenames; + std::set xrc_filenames; // Collect all of the class and filenames in use by each form so we can make sure the new // form doesn't use any of them. @@ -95,12 +136,16 @@ void ProjectHandler::FixupDuplicatedNode(Node* new_node) base_filenames.insert(iter->as_string(prop_base_file)); if (iter->hasValue(prop_derived_file)) derived_filenames.insert(iter->as_string(prop_derived_file)); - if (iter->hasValue(prop_xrc_file)) - xrc_filenames.insert(iter->as_string(prop_xrc_file)); + if (iter->hasValue(prop_perl_file)) + python_filenames.insert(iter->as_string(prop_perl_file)); if (iter->hasValue(prop_python_file)) python_filenames.insert(iter->as_string(prop_python_file)); if (iter->hasValue(prop_ruby_file)) ruby_filenames.insert(iter->as_string(prop_ruby_file)); + if (iter->hasValue(prop_rust_file)) + ruby_filenames.insert(iter->as_string(prop_rust_file)); + if (iter->hasValue(prop_xrc_file)) + xrc_filenames.insert(iter->as_string(prop_xrc_file)); } auto lambda = [&](std::set& set_names, PropName prop) @@ -143,30 +188,70 @@ void ProjectHandler::FixupDuplicatedNode(Node* new_node) lambda(derived_classnames, prop_derived_class_name); lambda(base_filenames, prop_base_file); lambda(derived_filenames, prop_derived_file); - lambda(xrc_filenames, prop_xrc_file); + lambda(perl_filenames, prop_perl_file); lambda(python_filenames, prop_python_file); lambda(ruby_filenames, prop_ruby_file); + lambda(rust_filenames, prop_rust_file); + lambda(xrc_filenames, prop_xrc_file); } -tt_string ProjectHandler::ArtDirectory() const +const wxFileName* ProjectHandler::get_wxFileName() const { - tt_string result; - - if (m_project_node->hasValue(prop_art_directory)) - result = m_project_node->as_string(prop_art_directory); - if (result.empty()) - result = m_projectPath; + if (m_project_path->IsOk()) + { + return m_project_path.get(); + } + else + { + if (m_project_node->hasValue(prop_art_directory)) + { + m_project_path->Assign(m_project_node->as_string(prop_art_directory), wxEmptyString, wxEmptyString, + wxPATH_NATIVE); + m_project_path->MakeRelativeTo(m_project_path->GetPath()); + m_project_path->MakeAbsolute(); + return m_project_path.get(); + } + else + { + m_project_path->Assign(m_project_path->GetFullPath()); + return m_project_path.get(); + } + } +} - result.make_absolute(); +const wxFileName* ProjectHandler::getArtPath() +{ + if (m_art_path->IsOk()) + { + return m_art_path.get(); + } + else + { + if (m_project_node->hasValue(prop_art_directory)) + { + m_art_path->Assign(m_project_node->as_string(prop_art_directory), wxEmptyString, wxEmptyString, wxPATH_NATIVE); + m_art_path->MakeRelativeTo(m_project_path->GetPath()); + m_art_path->MakeAbsolute(); + return m_art_path.get(); + } + else + { + m_art_path->Assign(m_project_path->GetFullPath()); + return m_art_path.get(); + } + } +} - return result; +tt_string ProjectHandler::ArtDirectory() +{ + return getArtPath()->GetFullPath(); } tt_string ProjectHandler::getBaseDirectory(Node* node, GenLang language) const { if (!node || node == m_project_node.get()) { - return m_projectPath; + return getProjectPath(); } if (!node->isForm() && !node->isFolder()) @@ -174,7 +259,7 @@ tt_string ProjectHandler::getBaseDirectory(Node* node, GenLang language) const node = node->getForm(); if (!node) { - return m_projectPath; + return getProjectPath(); } } @@ -259,7 +344,7 @@ std::pair ProjectHandler::GetOutputPath(Node* form, GenLang lan } if (result.empty()) - result = m_projectPath; + result = getProjectPath(); tt_string base_file; switch (language) @@ -335,8 +420,8 @@ std::pair ProjectHandler::GetOutputPath(Node* form, GenLang lan return std::make_pair(result, true); } -// Note that this will return a directory for GEN_LANG_PYTHON and GEN_LANG_XRC even though we currently -// don't generate derived files for those languages. +// Note that this will return a directory for all languages even though we currently don't generate +// derived files for any language except C++. tt_string ProjectHandler::getDerivedDirectory(Node* node, GenLang language) const { tt_string result; @@ -346,10 +431,14 @@ tt_string ProjectHandler::getDerivedDirectory(Node* node, GenLang language) cons { if (language == GEN_LANG_CPLUSPLUS && folder->hasValue(prop_folder_derived_directory)) result = folder->as_string(prop_folder_base_directory); + else if (language == GEN_LANG_PERL && folder->hasValue(prop_folder_perl_output_folder)) + result = folder->as_string(prop_folder_perl_output_folder); else if (language == GEN_LANG_PYTHON && folder->hasValue(prop_folder_python_output_folder)) result = folder->as_string(prop_folder_python_output_folder); else if (language == GEN_LANG_RUBY && folder->hasValue(prop_folder_ruby_output_folder)) - result = folder->as_string(prop_folder_python_output_folder); + result = folder->as_string(prop_folder_ruby_output_folder); + else if (language == GEN_LANG_RUST && folder->hasValue(prop_folder_rust_output_folder)) + result = folder->as_string(prop_folder_rust_output_folder); else if (language == GEN_LANG_XRC && folder->hasValue(prop_folder_xrc_directory)) result = folder->as_string(prop_folder_xrc_directory); } @@ -360,16 +449,20 @@ tt_string ProjectHandler::getDerivedDirectory(Node* node, GenLang language) cons { if (language == GEN_LANG_CPLUSPLUS && m_project_node->hasValue(prop_derived_directory)) result = m_project_node->as_string(prop_base_directory); + else if (language == GEN_LANG_PERL && m_project_node->hasValue(prop_perl_output_folder)) + result = m_project_node->as_string(prop_perl_output_folder); else if (language == GEN_LANG_PYTHON && m_project_node->hasValue(prop_python_output_folder)) result = m_project_node->as_string(prop_python_output_folder); else if (language == GEN_LANG_RUBY && m_project_node->hasValue(prop_ruby_output_folder)) result = m_project_node->as_string(prop_ruby_output_folder); + else if (language == GEN_LANG_RUST && m_project_node->hasValue(prop_rust_output_folder)) + result = m_project_node->as_string(prop_rust_output_folder); else if (language == GEN_LANG_XRC && m_project_node->hasValue(prop_xrc_directory)) result = m_project_node->as_string(prop_xrc_directory); } if (result.empty()) - result = m_projectPath; + result = getProjectPath(); result.make_absolute(); @@ -507,105 +600,106 @@ size_t ProjectHandler::getOutputType(int flags) const } } } - - if (child->hasValue(prop_python_file)) + if (child->hasValue(prop_perl_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - if (child->as_string(prop_python_file) == child->getPropDefaultValue(prop_python_file) && - getCodePreference(form) != GEN_LANG_PYTHON) + if (child->as_string(prop_perl_file) == child->getPropDefaultValue(prop_perl_file) && + getCodePreference(form) != GEN_LANG_PERL) { continue; } } - result |= OUTPUT_PYTHON; + result |= OUTPUT_PERL; } - if (child->hasValue(prop_ruby_file)) + if (child->hasValue(prop_python_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - if (child->as_string(prop_ruby_file) == child->getPropDefaultValue(prop_ruby_file) && - getCodePreference(form) != GEN_LANG_RUBY) + if (child->as_string(prop_python_file) == child->getPropDefaultValue(prop_python_file) && + getCodePreference(form) != GEN_LANG_PYTHON) { continue; } } - result |= OUTPUT_RUBY; + result |= OUTPUT_PYTHON; } - if (child->hasValue(prop_fortran_file)) + if (child->hasValue(prop_ruby_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - if (child->as_string(prop_fortran_file) == child->getPropDefaultValue(prop_fortran_file) && - getCodePreference(form) != GEN_LANG_FORTRAN) + if (child->as_string(prop_ruby_file) == child->getPropDefaultValue(prop_ruby_file) && + getCodePreference(form) != GEN_LANG_RUBY) { continue; } } - result |= OUTPUT_FORTRAN; + result |= OUTPUT_RUBY; } - if (child->hasValue(prop_haskell_file)) + if (child->hasValue(prop_rust_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - if (child->as_string(prop_haskell_file) == child->getPropDefaultValue(prop_haskell_file) && - getCodePreference(form) != GEN_LANG_HASKELL) + if (child->as_string(prop_rust_file) == child->getPropDefaultValue(prop_rust_file) && + getCodePreference(form) != GEN_LANG_RUST) { continue; } } - result |= OUTPUT_HASKELL; + result |= OUTPUT_RUST; } - if (child->hasValue(prop_lua_file)) + if (child->hasValue(prop_xrc_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - if (child->as_string(prop_lua_file) == child->getPropDefaultValue(prop_lua_file) && - getCodePreference(form) != GEN_LANG_LUA) + // Note that we do *not* ignore this if getCodePreference(form) != + // GEN_LANG_XRC. If the language is using XRC for the UI, then the XRC must + // be generated as well. + if (child->as_string(prop_xrc_file) == child->getPropDefaultValue(prop_xrc_file)) { continue; } } - result |= OUTPUT_LUA; + result |= OUTPUT_XRC; } - if (child->hasValue(prop_perl_file)) +#if GENERATE_NEW_LANG_CODE + if (child->hasValue(prop_fortran_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - if (child->as_string(prop_perl_file) == child->getPropDefaultValue(prop_perl_file) && - getCodePreference(form) != GEN_LANG_PERL) + if (child->as_string(prop_fortran_file) == child->getPropDefaultValue(prop_fortran_file) && + getCodePreference(form) != GEN_LANG_FORTRAN) { continue; } } - result |= OUTPUT_PERL; + result |= OUTPUT_FORTRAN; } - if (child->hasValue(prop_rust_file)) + if (child->hasValue(prop_haskell_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - if (child->as_string(prop_rust_file) == child->getPropDefaultValue(prop_rust_file) && - getCodePreference(form) != GEN_LANG_RUST) + if (child->as_string(prop_haskell_file) == child->getPropDefaultValue(prop_haskell_file) && + getCodePreference(form) != GEN_LANG_HASKELL) { continue; } } - result |= OUTPUT_RUST; + result |= OUTPUT_HASKELL; } - if (child->hasValue(prop_xrc_file)) + if (child->hasValue(prop_lua_file)) { if (child->isGen(gen_Images) || child->isGen(gen_Data)) { - // Note that we do *not* ignore this if getCodePreference(form) != - // GEN_LANG_XRC. If the language is using XRC for the UI, then the XRC must - // be generated as well. - if (child->as_string(prop_xrc_file) == child->getPropDefaultValue(prop_xrc_file)) + if (child->as_string(prop_lua_file) == child->getPropDefaultValue(prop_lua_file) && + getCodePreference(form) != GEN_LANG_LUA) { continue; } } - result |= OUTPUT_XRC; + result |= OUTPUT_LUA; } +#endif // GENERATE_NEW_LANG_CODE } } }; diff --git a/src/project/project_handler.h b/src/project/project_handler.h index 7a7a360a5..e0f79afdb 100644 --- a/src/project/project_handler.h +++ b/src/project/project_handler.h @@ -1,14 +1,12 @@ ///////////////////////////////////////////////////////////////////////////// // Purpose: ProjectHandler class // Author: Ralph Walden -// Copyright: Copyright (c) 2020-2024 KeyWorks Software (Ralph Walden) +// Copyright: Copyright (c) 2020-2025 KeyWorks Software (Ralph Walden) // License: Apache License -- see ../LICENSE ///////////////////////////////////////////////////////////////////////////// #pragma once // NOLINT(#pragma once in main file) -#include // wxFileName - #include // for pair<> #include "gen_enums.h" // Enumerations for generators @@ -42,10 +40,14 @@ enum OUT_FLAG_IGNORE_DERIVED = 1 << 0, // Ignore derived output files }; +class wxFileName; // forward declaration + class ProjectHandler { private: - ProjectHandler() {} + // ProjectHandler() {} + ProjectHandler(); + ~ProjectHandler(); public: ProjectHandler(ProjectHandler const&) = delete; @@ -63,12 +65,18 @@ class ProjectHandler // This will convert the project path into a full path void setProjectFile(const tt_string& file); + void setProjectPath(const wxFileName* path); // Returns the full path to the directory the project file is in - tt_string getProjectPath() const { return m_projectPath; } + tt_string getProjectPath() const; + + // Returns the full path to the directory the project file is in + tt_string getwxProjectPath() const; // Returns the full path to the project filename - tt_string getProjectFile() const { return m_projectFile; } + tt_string getProjectFile() const; + + const wxFileName* get_wxFileName() const; // Get a bit flag indicating which output types are enabled. // @@ -76,9 +84,10 @@ class ProjectHandler size_t getOutputType(int flags = OUT_FLAG_NONE) const; // Change to the project's directory - bool ChangeDir() const { return m_projectPath.ChangeDir(); } + bool ChangeDir() const; - tt_string ArtDirectory() const; + tt_string ArtDirectory(); + const wxFileName* getArtPath(); // If the node is within a folder, and the folder specifies a directory, then that // directory is returned. Otherwise the project base directory is returned. @@ -228,10 +237,11 @@ class ProjectHandler Node* m_ImagesForm { nullptr }; Node* m_DataForm { nullptr }; - tt_string m_projectFile; - tt_string m_projectPath; + // Creating the wxFileName class this way means callers don't need to include + // wx/filename.h and all the files it includes. - wxFileName m_project_wx_filename; + std::unique_ptr m_project_path; + std::unique_ptr m_art_path; int m_ProjectVersion; int m_OriginalProjectVersion;