diff options
21 files changed, 402 insertions, 51 deletions
diff --git a/source/blender/blenkernel/BKE_asset_catalog.hh b/source/blender/blenkernel/BKE_asset_catalog.hh index d5c8acff960..f7896e0f51e 100644 --- a/source/blender/blenkernel/BKE_asset_catalog.hh +++ b/source/blender/blenkernel/BKE_asset_catalog.hh @@ -63,13 +63,21 @@ class AssetCatalogService { AssetCatalogService(); explicit AssetCatalogService(const CatalogFilePath &asset_library_root); + /** + * Set global tag indicating that some catalog modifications are unsaved that could get lost + * on exit. This tag is not set by internal catalog code, the catalog service user is responsible + * for it. It is cleared by #write_to_disk(). + */ + void tag_has_unsaved_changes(); + bool has_unsaved_changes() const; + /** Load asset catalog definitions from the files found in the asset library. */ void load_from_disk(); /** Load asset catalog definitions from the given file or directory. */ void load_from_disk(const CatalogFilePath &file_or_directory_path); /** - * Write the catalog definitions to disk in response to the blend file being saved. + * Write the catalog definitions to disk. * * The location where the catalogs are saved is variable, and depends on the location of the * blend file. The first matching rule wins: @@ -85,7 +93,7 @@ class AssetCatalogService { * * Return true on success, which either means there were no in-memory categories to save, * or the save was successful. */ - bool write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path); + bool write_to_disk(const CatalogFilePath &blend_file_path); /** * Merge on-disk changes into the in-memory asset catalogs. @@ -166,10 +174,15 @@ class AssetCatalogService { Vector<std::unique_ptr<AssetCatalogCollection>> undo_snapshots_; Vector<std::unique_ptr<AssetCatalogCollection>> redo_snapshots_; + bool has_unsaved_changes_ = false; void load_directory_recursive(const CatalogFilePath &directory_path); void load_single_file(const CatalogFilePath &catalog_definition_file_path); + /** Implementation of #write_to_disk() that doesn't clear the "has unsaved changes" tag. */ + bool write_to_disk_ex(const CatalogFilePath &blend_file_path); + void untag_has_unsaved_changes(); + std::unique_ptr<AssetCatalogDefinitionFile> parse_catalog_file( const CatalogFilePath &catalog_definition_file_path); diff --git a/source/blender/blenkernel/BKE_asset_library.h b/source/blender/blenkernel/BKE_asset_library.h index b4674a8d932..ca12fd6f4fb 100644 --- a/source/blender/blenkernel/BKE_asset_library.h +++ b/source/blender/blenkernel/BKE_asset_library.h @@ -77,6 +77,9 @@ bool BKE_asset_library_find_suitable_root_path_from_main( void BKE_asset_library_refresh_catalog_simplename(struct AssetLibrary *asset_library, struct AssetMetaData *asset_data); +/** Return whether any loaded AssetLibrary has unsaved changes to its catalogs. */ +bool BKE_asset_library_has_any_unsaved_catalogs(void); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenkernel/BKE_asset_library.hh b/source/blender/blenkernel/BKE_asset_library.hh index 6d5c9836b26..64c8afecaef 100644 --- a/source/blender/blenkernel/BKE_asset_library.hh +++ b/source/blender/blenkernel/BKE_asset_library.hh @@ -38,6 +38,10 @@ namespace blender::bke { * For now this is only for catalogs, later this can be expanded to indexes/caches/more. */ struct AssetLibrary { + /* Controlled by #ED_asset_catalogs_set_save_catalogs_when_file_is_saved, + * for managing the "Save Catalog Changes" in the quit-confirmation dialog box. */ + static bool save_catalogs_when_file_is_saved; + std::unique_ptr<AssetCatalogService> catalog_service; AssetLibrary(); @@ -53,10 +57,10 @@ struct AssetLibrary { * meant to help recover from. */ void refresh_catalog_simplename(struct AssetMetaData *asset_data); - void on_save_handler_register(); - void on_save_handler_unregister(); + void on_blend_save_handler_register(); + void on_blend_save_handler_unregister(); - void on_save_post(struct Main *, struct PointerRNA **pointers, const int num_pointers); + void on_blend_save_post(struct Main *, struct PointerRNA **pointers, const int num_pointers); private: bCallbackFuncStore on_save_callback_store_{}; diff --git a/source/blender/blenkernel/intern/asset_catalog.cc b/source/blender/blenkernel/intern/asset_catalog.cc index f3a4f88ef38..aa8f12d0e23 100644 --- a/source/blender/blenkernel/intern/asset_catalog.cc +++ b/source/blender/blenkernel/intern/asset_catalog.cc @@ -66,6 +66,21 @@ AssetCatalogService::AssetCatalogService(const CatalogFilePath &asset_library_ro { } +void AssetCatalogService::tag_has_unsaved_changes() +{ + has_unsaved_changes_ = true; +} + +void AssetCatalogService::untag_has_unsaved_changes() +{ + has_unsaved_changes_ = false; +} + +bool AssetCatalogService::has_unsaved_changes() const +{ + return has_unsaved_changes_; +} + bool AssetCatalogService::is_empty() const { return catalog_collection_->catalogs_.is_empty(); @@ -344,7 +359,17 @@ void AssetCatalogService::merge_from_disk_before_writing() cdf->parse_catalog_file(cdf->file_path, catalog_parsed_callback); } -bool AssetCatalogService::write_to_disk_on_blendfile_save(const CatalogFilePath &blend_file_path) +bool AssetCatalogService::write_to_disk(const CatalogFilePath &blend_file_path) +{ + if (!write_to_disk_ex(blend_file_path)) { + return false; + } + + untag_has_unsaved_changes(); + return true; +} + +bool AssetCatalogService::write_to_disk_ex(const CatalogFilePath &blend_file_path) { /* TODO(Sybren): expand to support multiple CDFs. */ diff --git a/source/blender/blenkernel/intern/asset_catalog_test.cc b/source/blender/blenkernel/intern/asset_catalog_test.cc index d1413d521a1..d743d250c1d 100644 --- a/source/blender/blenkernel/intern/asset_catalog_test.cc +++ b/source/blender/blenkernel/intern/asset_catalog_test.cc @@ -222,7 +222,7 @@ class AssetCatalogTest : public testing::Test { const AssetCatalog *cat = service.create_catalog("some/catalog/path"); /* Mock that the blend file is written to the directory already containing a CDF. */ - ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + ASSERT_TRUE(service.write_to_disk(blendfilename)); /* Test that the CDF still exists in the expected location. */ EXPECT_TRUE(BLI_exists(cdf_toplevel.c_str())); @@ -489,7 +489,7 @@ TEST_F(AssetCatalogTest, no_writing_empty_files) { const CatalogFilePath temp_lib_root = create_temp_path(); AssetCatalogService service(temp_lib_root); - service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + service.write_to_disk(temp_lib_root + "phony.blend"); const CatalogFilePath default_cdf_path = temp_lib_root + AssetCatalogService::DEFAULT_CATALOG_FILENAME; @@ -515,7 +515,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__with_existing_cdf) const AssetCatalog *cat = service.create_catalog("some/catalog/path"); const CatalogFilePath blendfilename = top_level_dir + "subdir/some_file.blend"; - ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + ASSERT_TRUE(service.write_to_disk(blendfilename)); EXPECT_EQ(cdf_filename, service.get_catalog_definition_file()->file_path); /* Test that the CDF was created in the expected location. */ @@ -542,7 +542,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_empty_directory) const AssetCatalog *cat = service.create_catalog("some/catalog/path"); const CatalogFilePath blendfilename = target_dir + "some_file.blend"; - ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + ASSERT_TRUE(service.write_to_disk(blendfilename)); /* Test that the CDF was created in the expected location. */ const CatalogFilePath expected_cdf_path = target_dir + @@ -575,7 +575,7 @@ TEST_F(AssetCatalogTest, on_blendfile_save__from_memory_into_existing_cdf_and_me /* Mock that the blend file is written to a subdirectory of the asset library. */ const CatalogFilePath blendfilename = target_dir + "some_file.blend"; - ASSERT_TRUE(service.write_to_disk_on_blendfile_save(blendfilename)); + ASSERT_TRUE(service.write_to_disk(blendfilename)); /* Test that the CDF still exists in the expected location. */ const CatalogFilePath backup_filename = writable_cdf_file + "~"; @@ -630,7 +630,7 @@ TEST_F(AssetCatalogTest, create_first_catalog_from_scratch) EXPECT_FALSE(BLI_exists(temp_lib_root.c_str())); /* Writing to disk should create the directory + the default file. */ - service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + service.write_to_disk(temp_lib_root + "phony.blend"); EXPECT_TRUE(BLI_is_dir(temp_lib_root.c_str())); const CatalogFilePath definition_file_path = temp_lib_root + "/" + @@ -681,7 +681,7 @@ TEST_F(AssetCatalogTest, create_catalog_after_loading_file) << "expecting newly added catalog to not yet be saved to " << temp_lib_root; /* Write and reload the catalog file. */ - service.write_to_disk_on_blendfile_save(temp_lib_root + "phony.blend"); + service.write_to_disk(temp_lib_root + "phony.blend"); AssetCatalogService reloaded_service(temp_lib_root); reloaded_service.load_from_disk(); EXPECT_NE(nullptr, reloaded_service.find_catalog(UUID_POSES_ELLIE)) @@ -867,7 +867,7 @@ TEST_F(AssetCatalogTest, merge_catalog_files) ASSERT_EQ(0, BLI_copy(modified_cdf_file.c_str(), temp_cdf_file.c_str())); /* Overwrite the modified file. This should merge the on-disk file with our catalogs. */ - service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + service.write_to_disk(cdf_dir + "phony.blend"); AssetCatalogService loaded_service(cdf_dir); loaded_service.load_from_disk(); @@ -897,7 +897,7 @@ TEST_F(AssetCatalogTest, backups) AssetCatalogService service(cdf_dir); service.load_from_disk(); service.delete_catalog_by_id(UUID_POSES_ELLIE); - service.write_to_disk_on_blendfile_save(cdf_dir + "phony.blend"); + service.write_to_disk(cdf_dir + "phony.blend"); const CatalogFilePath backup_path = writable_cdf_file + "~"; ASSERT_TRUE(BLI_is_file(backup_path.c_str())); diff --git a/source/blender/blenkernel/intern/asset_library.cc b/source/blender/blenkernel/intern/asset_library.cc index 32e7aab235d..6c4660ae75d 100644 --- a/source/blender/blenkernel/intern/asset_library.cc +++ b/source/blender/blenkernel/intern/asset_library.cc @@ -35,6 +35,8 @@ #include <memory> +bool blender::bke::AssetLibrary::save_catalogs_when_file_is_saved = true; + /** * Loading an asset library at this point only means loading the catalogs. Later on this should * invoke reading of asset representations too. @@ -52,6 +54,12 @@ struct AssetLibrary *BKE_asset_library_load(const char *library_path) return reinterpret_cast<struct AssetLibrary *>(lib); } +bool BKE_asset_library_has_any_unsaved_catalogs() +{ + blender::bke::AssetLibraryService *service = blender::bke::AssetLibraryService::get(); + return service->has_any_unsaved_catalogs(); +} + bool BKE_asset_library_find_suitable_root_path_from_path(const char *input_path, char *r_library_path) { @@ -109,7 +117,7 @@ AssetLibrary::AssetLibrary() : catalog_service(std::make_unique<AssetCatalogServ AssetLibrary::~AssetLibrary() { if (on_save_callback_store_.func) { - on_save_handler_unregister(); + on_blend_save_handler_unregister(); } } @@ -127,11 +135,12 @@ void asset_library_on_save_post(struct Main *main, void *arg) { AssetLibrary *asset_lib = static_cast<AssetLibrary *>(arg); - asset_lib->on_save_post(main, pointers, num_pointers); + asset_lib->on_blend_save_post(main, pointers, num_pointers); } + } // namespace -void AssetLibrary::on_save_handler_register() +void AssetLibrary::on_blend_save_handler_register() { /* The callback system doesn't own `on_save_callback_store_`. */ on_save_callback_store_.alloc = false; @@ -142,22 +151,24 @@ void AssetLibrary::on_save_handler_register() BKE_callback_add(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); } -void AssetLibrary::on_save_handler_unregister() +void AssetLibrary::on_blend_save_handler_unregister() { BKE_callback_remove(&on_save_callback_store_, BKE_CB_EVT_SAVE_POST); on_save_callback_store_.func = nullptr; on_save_callback_store_.arg = nullptr; } -void AssetLibrary::on_save_post(struct Main *main, - struct PointerRNA ** /*pointers*/, - const int /*num_pointers*/) +void AssetLibrary::on_blend_save_post(struct Main *main, + struct PointerRNA ** /*pointers*/, + const int /*num_pointers*/) { if (this->catalog_service == nullptr) { return; } - this->catalog_service->write_to_disk_on_blendfile_save(main->name); + if (save_catalogs_when_file_is_saved) { + this->catalog_service->write_to_disk(main->name); + } } void AssetLibrary::refresh_catalog_simplename(struct AssetMetaData *asset_data) @@ -166,15 +177,12 @@ void AssetLibrary::refresh_catalog_simplename(struct AssetMetaData *asset_data) asset_data->catalog_simple_name[0] = '\0'; return; } - const AssetCatalog *catalog = this->catalog_service->find_catalog(asset_data->catalog_id); if (catalog == nullptr) { /* No-op if the catalog cannot be found. This could be the kind of "the catalog definition file * is corrupt/lost" scenario that the simple name is meant to help recover from. */ return; } - STRNCPY(asset_data->catalog_simple_name, catalog->simple_name.c_str()); } - } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_library_service.cc b/source/blender/blenkernel/intern/asset_library_service.cc index c5447de645b..aeded0bc128 100644 --- a/source/blender/blenkernel/intern/asset_library_service.cc +++ b/source/blender/blenkernel/intern/asset_library_service.cc @@ -83,7 +83,7 @@ AssetLibrary *AssetLibraryService::get_asset_library_on_disk(StringRefNull top_l AssetLibraryPtr lib_uptr = std::make_unique<AssetLibrary>(); AssetLibrary *lib = lib_uptr.get(); - lib->on_save_handler_register(); + lib->on_blend_save_handler_register(); lib->load(top_dir_trailing_slash); on_disk_libraries_.add_new(top_dir_trailing_slash, std::move(lib_uptr)); @@ -99,7 +99,7 @@ AssetLibrary *AssetLibraryService::get_asset_library_current_file() else { CLOG_INFO(&LOG, 2, "get current file lib (loaded)"); current_file_library_ = std::make_unique<AssetLibrary>(); - current_file_library_->on_save_handler_register(); + current_file_library_->on_blend_save_handler_register(); } AssetLibrary *lib = current_file_library_.get(); @@ -148,4 +148,19 @@ void AssetLibraryService::app_handler_unregister() on_load_callback_store_.arg = nullptr; } +bool AssetLibraryService::has_any_unsaved_catalogs() const +{ + if (current_file_library_ && current_file_library_->catalog_service->has_unsaved_changes()) { + return true; + } + + for (const auto &asset_lib_uptr : on_disk_libraries_.values()) { + if (asset_lib_uptr->catalog_service->has_unsaved_changes()) { + return true; + } + } + + return false; +} + } // namespace blender::bke diff --git a/source/blender/blenkernel/intern/asset_library_service.hh b/source/blender/blenkernel/intern/asset_library_service.hh index 63ffe56ab74..03df706bc42 100644 --- a/source/blender/blenkernel/intern/asset_library_service.hh +++ b/source/blender/blenkernel/intern/asset_library_service.hh @@ -66,6 +66,9 @@ class AssetLibraryService { /** Get the "Current File" asset library. */ AssetLibrary *get_asset_library_current_file(); + /** Returns whether there are any known asset libraries with unsaved catalog edits. */ + bool has_any_unsaved_catalogs() const; + protected: static std::unique_ptr<AssetLibraryService> instance_; diff --git a/source/blender/blenkernel/intern/asset_library_service_test.cc b/source/blender/blenkernel/intern/asset_library_service_test.cc index 36baa877454..ed132d7a8d8 100644 --- a/source/blender/blenkernel/intern/asset_library_service_test.cc +++ b/source/blender/blenkernel/intern/asset_library_service_test.cc @@ -22,6 +22,8 @@ #include "BLI_fileops.h" #include "BLI_path_util.h" +#include "BKE_appdir.h" + #include "CLG_log.h" #include "testing/testing.h" @@ -31,6 +33,7 @@ namespace blender::bke::tests { class AssetLibraryServiceTest : public testing::Test { public: CatalogFilePath asset_library_root_; + CatalogFilePath temp_library_path_; static void SetUpTestSuite() { @@ -48,11 +51,34 @@ class AssetLibraryServiceTest : public testing::Test { FAIL(); } asset_library_root_ = test_files_dir + "/" + "asset_library"; + temp_library_path_ = ""; } void TearDown() override { AssetLibraryService::destroy(); + + if (!temp_library_path_.empty()) { + BLI_delete(temp_library_path_.c_str(), true, true); + temp_library_path_ = ""; + } + } + + /* Register a temporary path, which will be removed at the end of the test. + * The returned path ends in a slash. */ + CatalogFilePath use_temp_path() + { + BKE_tempdir_init(""); + const CatalogFilePath tempdir = BKE_tempdir_session(); + temp_library_path_ = tempdir + "test-temporary-path/"; + return temp_library_path_; + } + + CatalogFilePath create_temp_path() + { + CatalogFilePath path = use_temp_path(); + BLI_dir_create_recursive(path.c_str()); + return path; } }; @@ -122,4 +148,52 @@ TEST_F(AssetLibraryServiceTest, catalogs_loaded) << "Catalogs should be loaded after getting an asset library from disk."; } +TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs) +{ + AssetLibraryService *const service = AssetLibraryService::get(); + EXPECT_FALSE(service->has_any_unsaved_catalogs()) + << "Empty AssetLibraryService should have no unsaved catalogs"; + + AssetLibrary *const lib = service->get_asset_library_on_disk(asset_library_root_); + AssetCatalogService *const cat_service = lib->catalog_service.get(); + EXPECT_FALSE(service->has_any_unsaved_catalogs()) + << "Unchanged AssetLibrary should have no unsaved catalogs"; + + const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78"); + cat_service->prune_catalogs_by_id(UUID_POSES_ELLIE); + EXPECT_FALSE(service->has_any_unsaved_catalogs()) + << "Deletion of catalogs via AssetCatalogService should not tag as 'unsaved changes'."; + + cat_service->tag_has_unsaved_changes(); + EXPECT_TRUE(service->has_any_unsaved_catalogs()) + << "Tagging as having unsaved changes of a single catalog service should result in unsaved " + "changes being reported."; +} + +TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs_after_write) +{ + const CatalogFilePath writable_dir = create_temp_path(); /* Has trailing slash. */ + const CatalogFilePath original_cdf_file = asset_library_root_ + "/blender_assets.cats.txt"; + CatalogFilePath writable_cdf_file = writable_dir + AssetCatalogService::DEFAULT_CATALOG_FILENAME; + BLI_path_slash_native(writable_cdf_file.data()); + ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str())); + + AssetLibraryService *const service = AssetLibraryService::get(); + AssetLibrary *const lib = service->get_asset_library_on_disk(writable_dir); + + EXPECT_FALSE(service->has_any_unsaved_catalogs()) + << "Unchanged AssetLibrary should have no unsaved catalogs"; + + AssetCatalogService *const cat_service = lib->catalog_service.get(); + cat_service->tag_has_unsaved_changes(); + + EXPECT_TRUE(service->has_any_unsaved_catalogs()) + << "Tagging as having unsaved changes of a single catalog service should result in unsaved " + "changes being reported."; + + cat_service->write_to_disk(writable_dir + "dummy_path.blend"); + EXPECT_FALSE(service->has_any_unsaved_catalogs()) + << "Written AssetCatalogService should have no unsaved catalogs"; +} + } // namespace blender::bke::tests diff --git a/source/blender/blenkernel/intern/asset_library_test.cc b/source/blender/blenkernel/intern/asset_library_test.cc index 7c3587c6dfa..c6c949a7ec4 100644 --- a/source/blender/blenkernel/intern/asset_library_test.cc +++ b/source/blender/blenkernel/intern/asset_library_test.cc @@ -29,7 +29,7 @@ namespace blender::bke::tests { -class AssetLibraryServiceTest : public testing::Test { +class AssetLibraryTest : public testing::Test { public: static void SetUpTestSuite() { @@ -46,7 +46,7 @@ class AssetLibraryServiceTest : public testing::Test { } }; -TEST_F(AssetLibraryServiceTest, bke_asset_library_load) +TEST_F(AssetLibraryTest, bke_asset_library_load) { const std::string test_files_dir = blender::tests::flags_test_asset_dir(); if (test_files_dir.empty()) { @@ -73,7 +73,7 @@ TEST_F(AssetLibraryServiceTest, bke_asset_library_load) EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str()); } -TEST_F(AssetLibraryServiceTest, load_nonexistent_directory) +TEST_F(AssetLibraryTest, load_nonexistent_directory) { const std::string test_files_dir = blender::tests::flags_test_asset_dir(); if (test_files_dir.empty()) { diff --git a/source/blender/editors/asset/CMakeLists.txt b/source/blender/editors/asset/CMakeLists.txt index b6657bfca63..9017f136319 100644 --- a/source/blender/editors/asset/CMakeLists.txt +++ b/source/blender/editors/asset/CMakeLists.txt @@ -41,6 +41,7 @@ set(SRC intern/asset_ops.cc intern/asset_temp_id_consumer.cc + ED_asset_catalog.h ED_asset_catalog.hh ED_asset_filter.h ED_asset_handle.h diff --git a/source/blender/editors/asset/ED_asset_catalog.h b/source/blender/editors/asset/ED_asset_catalog.h new file mode 100644 index 00000000000..451ec0d5984 --- /dev/null +++ b/source/blender/editors/asset/ED_asset_catalog.h @@ -0,0 +1,39 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup edasset + */ + +#pragma once + +#include "BLI_utildefines.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct Main; +struct AssetLibrary; + +void ED_asset_catalogs_save_from_main_path(struct AssetLibrary *library, const struct Main *bmain); + +void ED_asset_catalogs_set_save_catalogs_when_file_is_saved(bool should_save); +bool ED_asset_catalogs_get_save_catalogs_when_file_is_saved(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/editors/asset/ED_asset_catalog.hh b/source/blender/editors/asset/ED_asset_catalog.hh index cffd7728a60..8b8fc4d3574 100644 --- a/source/blender/editors/asset/ED_asset_catalog.hh +++ b/source/blender/editors/asset/ED_asset_catalog.hh @@ -33,3 +33,7 @@ blender::bke::AssetCatalog *ED_asset_catalog_add(AssetLibrary *library, blender::StringRefNull name, blender::StringRef parent_path = nullptr); void ED_asset_catalog_remove(AssetLibrary *library, const blender::bke::CatalogID &catalog_id); + +void ED_asset_catalog_rename(AssetLibrary *library, + blender::bke::CatalogID catalog_id, + blender::StringRefNull new_name); diff --git a/source/blender/editors/asset/intern/asset_catalog.cc b/source/blender/editors/asset/intern/asset_catalog.cc index 056fda63bd7..f8b33e76447 100644 --- a/source/blender/editors/asset/intern/asset_catalog.cc +++ b/source/blender/editors/asset/intern/asset_catalog.cc @@ -21,11 +21,15 @@ #include "BKE_asset_catalog.hh" #include "BKE_asset_catalog_path.hh" #include "BKE_asset_library.hh" +#include "BKE_main.h" #include "BLI_string_utils.h" +#include "ED_asset_catalog.h" #include "ED_asset_catalog.hh" +#include "WM_api.h" + using namespace blender; using namespace blender::bke; @@ -67,7 +71,13 @@ AssetCatalog *ED_asset_catalog_add(::AssetLibrary *library, AssetCatalogPath fullpath = AssetCatalogPath(parent_path) / unique_name; catalog_service->undo_push(); - return catalog_service->create_catalog(fullpath); + catalog_service->tag_has_unsaved_changes(); + bke::AssetCatalog *new_catalog = catalog_service->create_catalog(fullpath); + if (!new_catalog) { + return nullptr; + } + + return new_catalog; } void ED_asset_catalog_remove(::AssetLibrary *library, const CatalogID &catalog_id) @@ -79,5 +89,47 @@ void ED_asset_catalog_remove(::AssetLibrary *library, const CatalogID &catalog_i } catalog_service->undo_push(); + catalog_service->tag_has_unsaved_changes(); catalog_service->prune_catalogs_by_id(catalog_id); } + +void ED_asset_catalog_rename(::AssetLibrary *library, + const CatalogID catalog_id, + const StringRefNull new_name) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + BLI_assert_unreachable(); + return; + } + + const AssetCatalog *catalog = catalog_service->find_catalog(catalog_id); + + AssetCatalogPath new_path = catalog->path.parent(); + new_path = new_path / StringRef(new_name); + + catalog_service->undo_push(); + catalog_service->tag_has_unsaved_changes(); + catalog_service->update_catalog_path(catalog_id, new_path); +} + +void ED_asset_catalogs_save_from_main_path(::AssetLibrary *library, const Main *bmain) +{ + bke::AssetCatalogService *catalog_service = BKE_asset_library_get_catalog_service(library); + if (!catalog_service) { + BLI_assert_unreachable(); + return; + } + + catalog_service->write_to_disk(bmain->name); +} + +void ED_asset_catalogs_set_save_catalogs_when_file_is_saved(const bool should_save) +{ + bke::AssetLibrary::save_catalogs_when_file_is_saved = should_save; +} + +bool ED_asset_catalogs_get_save_catalogs_when_file_is_saved() +{ + return bke::AssetLibrary::save_catalogs_when_file_is_saved; +} diff --git a/source/blender/editors/asset/intern/asset_ops.cc b/source/blender/editors/asset/intern/asset_ops.cc index 33a22775280..834874abc19 100644 --- a/source/blender/editors/asset/intern/asset_ops.cc +++ b/source/blender/editors/asset/intern/asset_ops.cc @@ -22,6 +22,7 @@ #include "BKE_asset_library.hh" #include "BKE_context.h" #include "BKE_lib_id.h" +#include "BKE_main.h" #include "BKE_report.h" #include "BLI_string_ref.hh" @@ -398,7 +399,8 @@ static int asset_catalog_new_exec(bContext *C, wmOperator *op) MEM_freeN(parent_path); - WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + WM_event_add_notifier_ex( + CTX_wm_manager(C), CTX_wm_window(C), NC_ASSET | ND_ASSET_CATALOGS, nullptr); return OPERATOR_FINISHED; } @@ -436,7 +438,8 @@ static int asset_catalog_delete_exec(bContext *C, wmOperator *op) MEM_freeN(catalog_id_str); - WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr); + WM_event_add_notifier_ex( + CTX_wm_manager(C), CTX_wm_window(C), NC_ASSET | ND_ASSET_CATALOGS, nullptr); return OPERATOR_FINISHED; } @@ -561,6 +564,55 @@ static void ASSET_OT_catalog_undo_push(struct wmOperatorType *ot) /* -------------------------------------------------------------------- */ +static bool asset_catalogs_save_poll(bContext *C) +{ + if (!asset_catalog_operator_poll(C)) { + return false; + } + + const Main *bmain = CTX_data_main(C); + if (!bmain->name[0]) { + CTX_wm_operator_poll_msg_set(C, "Cannot save asset catalogs before the Blender file is saved"); + return false; + } + + if (!BKE_asset_library_has_any_unsaved_catalogs()) { + CTX_wm_operator_poll_msg_set(C, "No changes to be saved"); + return false; + } + + return true; +} + +static int asset_catalogs_save_exec(bContext *C, wmOperator * /*op*/) +{ + const SpaceFile *sfile = CTX_wm_space_file(C); + ::AssetLibrary *asset_library = ED_fileselect_active_asset_library_get(sfile); + + ED_asset_catalogs_save_from_main_path(asset_library, CTX_data_main(C)); + + WM_event_add_notifier_ex( + CTX_wm_manager(C), CTX_wm_window(C), NC_ASSET | ND_ASSET_CATALOGS, nullptr); + + return OPERATOR_FINISHED; +} + +static void ASSET_OT_catalogs_save(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Save Asset Catalogs"; + ot->description = + "Make any edits to any catalogs permanent by writing the current set up to the asset " + "library"; + ot->idname = "ASSET_OT_catalogs_save"; + + /* api callbacks */ + ot->exec = asset_catalogs_save_exec; + ot->poll = asset_catalogs_save_poll; +} + +/* -------------------------------------------------------------------- */ + void ED_operatortypes_asset(void) { WM_operatortype_append(ASSET_OT_mark); @@ -568,6 +620,7 @@ void ED_operatortypes_asset(void) WM_operatortype_append(ASSET_OT_catalog_new); WM_operatortype_append(ASSET_OT_catalog_delete); + WM_operatortype_append(ASSET_OT_catalogs_save); WM_operatortype_append(ASSET_OT_catalog_undo); WM_operatortype_append(ASSET_OT_catalog_redo); WM_operatortype_append(ASSET_OT_catalog_undo_push); diff --git a/source/blender/editors/include/ED_asset.h b/source/blender/editors/include/ED_asset.h index 8f19c97e671..6b8d33fa713 100644 --- a/source/blender/editors/include/ED_asset.h +++ b/source/blender/editors/include/ED_asset.h @@ -36,6 +36,7 @@ void ED_operatortypes_asset(void); } #endif +#include "../asset/ED_asset_catalog.h" #include "../asset/ED_asset_filter.h" #include "../asset/ED_asset_handle.h" #include "../asset/ED_asset_library.h" @@ -45,5 +46,6 @@ void ED_operatortypes_asset(void); /* C++ only headers. */ #ifdef __cplusplus +# include "../asset/ED_asset_catalog.hh" # include "../asset/ED_asset_list.hh" #endif diff --git a/source/blender/editors/space_file/asset_catalog_tree_view.cc b/source/blender/editors/space_file/asset_catalog_tree_view.cc index 8906cf34288..0eade57934e 100644 --- a/source/blender/editors/space_file/asset_catalog_tree_view.cc +++ b/source/blender/editors/space_file/asset_catalog_tree_view.cc @@ -21,8 +21,6 @@ * \ingroup spfile */ -#include "ED_fileselect.h" - #include "DNA_space_types.h" #include "BKE_asset.h" @@ -33,6 +31,9 @@ #include "BLT_translation.h" +#include "ED_asset.h" +#include "ED_fileselect.h" + #include "RNA_access.h" #include "UI_interface.h" @@ -52,7 +53,7 @@ using namespace blender::bke; namespace blender::ed::asset_browser { class AssetCatalogTreeView : public ui::AbstractTreeView { - bke::AssetCatalogService *catalog_service_; + ::AssetLibrary *asset_library_; /** The asset catalog tree this tree-view represents. */ bke::AssetCatalogTree *catalog_tree_; FileAssetSelectParams *params_; @@ -129,7 +130,7 @@ class AssetCatalogTreeViewUnassignedItem : public ui::BasicTreeViewItem { AssetCatalogTreeView::AssetCatalogTreeView(::AssetLibrary *library, FileAssetSelectParams *params, SpaceFile &space_file) - : catalog_service_(BKE_asset_library_get_catalog_service(library)), + : asset_library_(library), catalog_tree_(BKE_asset_library_get_catalog_tree(library)), params_(params), space_file_(space_file) @@ -353,12 +354,7 @@ bool AssetCatalogTreeViewItem::rename(StringRefNull new_name) const AssetCatalogTreeView &tree_view = static_cast<const AssetCatalogTreeView &>( get_tree_view()); - - AssetCatalogPath new_path = catalog_item_.catalog_path().parent(); - new_path = new_path / StringRef(new_name); - - tree_view.catalog_service_->undo_push(); - tree_view.catalog_service_->update_catalog_path(catalog_item_.get_catalog_id(), new_path); + ED_asset_catalog_rename(tree_view.asset_library_, catalog_item_.get_catalog_id(), new_name); return true; } @@ -369,6 +365,10 @@ void AssetCatalogTreeViewAllItem::build_row(uiLayout &row) ui::BasicTreeViewItem::build_row(row); PointerRNA *props; + + UI_but_extra_operator_icon_add( + (uiBut *)tree_row_button(), "ASSET_OT_catalogs_save", WM_OP_INVOKE_DEFAULT, ICON_FILE_TICK); + props = UI_but_extra_operator_icon_add( (uiBut *)tree_row_button(), "ASSET_OT_catalog_new", WM_OP_INVOKE_DEFAULT, ICON_ADD); /* No parent path to use the root level. */ diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h index b5f6caf4cb7..081e76e3d5b 100644 --- a/source/blender/windowmanager/WM_types.h +++ b/source/blender/windowmanager/WM_types.h @@ -462,6 +462,10 @@ typedef struct wmNotifier { #define ND_ASSET_LIST (1 << 16) #define ND_ASSET_LIST_PREVIEW (2 << 16) #define ND_ASSET_LIST_READING (3 << 16) +/* Catalog data changed, requiring a redraw of catalog UIs. Note that this doesn't denote a + * reloading of asset libraries & their catalogs should happen. That only happens on explicit user + * action. */ +#define ND_ASSET_CATALOGS (4 << 16) /* subtype, 256 entries too */ #define NOTE_SUBTYPE 0x0000FF00 diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index 9d3fd9b2ec9..e203281297b 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -75,6 +75,7 @@ #include "BKE_addon.h" #include "BKE_appdir.h" +#include "BKE_asset_library.h" #include "BKE_autoexec.h" #include "BKE_blender.h" #include "BKE_blendfile.h" @@ -105,6 +106,7 @@ #include "IMB_imbuf_types.h" #include "IMB_thumbs.h" +#include "ED_asset.h" #include "ED_datafiles.h" #include "ED_fileselect.h" #include "ED_image.h" @@ -168,9 +170,13 @@ void WM_file_tag_modified(void) } } -bool wm_file_or_image_is_modified(const Main *bmain, const wmWindowManager *wm) +/** + * Check if there is data that would be lost when closing the current file without saving. + */ +bool wm_file_or_session_data_has_unsaved_changes(const Main *bmain, const wmWindowManager *wm) { - return !wm->file_saved || ED_image_should_save_modified(bmain); + return !wm->file_saved || ED_image_should_save_modified(bmain) || + BKE_asset_library_has_any_unsaved_catalogs(); } /** \} */ @@ -3598,6 +3604,14 @@ static void wm_block_file_close_save_button(uiBlock *block, wmGenericCallback *p static const char *close_file_dialog_name = "file_close_popup"; +static void save_catalogs_when_file_is_closed_set_fn(bContext *UNUSED(C), + void *arg1, + void *UNUSED(arg2)) +{ + char *save_catalogs_when_file_is_closed = arg1; + ED_asset_catalogs_set_save_catalogs_when_file_is_saved(*save_catalogs_when_file_is_closed != 0); +} + static uiBlock *block_create__close_file_dialog(struct bContext *C, struct ARegion *region, void *arg1) @@ -3654,11 +3668,17 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C, MEM_freeN(message); } + /* Used to determine if extra separators are needed. */ + bool has_extra_checkboxes = false; + /* Modified Images Checkbox. */ if (modified_images_count > 0) { char message[64]; BLI_snprintf(message, sizeof(message), "Save %u modified image(s)", modified_images_count); - uiItemS(layout); + /* Only the first checkbox should get extra separation. */ + if (!has_extra_checkboxes) { + uiItemS(layout); + } uiDefButBitC(block, UI_BTYPE_CHECKBOX, 1, @@ -3674,11 +3694,41 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C, 0, 0, ""); + has_extra_checkboxes = true; + } + + if (BKE_asset_library_has_any_unsaved_catalogs()) { + static char save_catalogs_when_file_is_closed; + + save_catalogs_when_file_is_closed = ED_asset_catalogs_get_save_catalogs_when_file_is_saved(); + + /* Only the first checkbox should get extra separation. */ + if (!has_extra_checkboxes) { + uiItemS(layout); + } + uiBut *but = uiDefButBitC(block, + UI_BTYPE_CHECKBOX, + 1, + 0, + "Save modified asset catalogs", + 0, + 0, + 0, + UI_UNIT_Y, + &save_catalogs_when_file_is_closed, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set( + but, save_catalogs_when_file_is_closed_set_fn, &save_catalogs_when_file_is_closed, NULL); + has_extra_checkboxes = true; } BKE_reports_clear(&reports); - uiItemS_ex(layout, modified_images_count > 0 ? 2.0f : 4.0f); + uiItemS_ex(layout, has_extra_checkboxes ? 2.0f : 4.0f); /* Buttons. */ #ifdef _WIN32 @@ -3759,7 +3809,7 @@ bool wm_operator_close_file_dialog_if_needed(bContext *C, wmGenericCallbackFn post_action_fn) { if (U.uiflag & USER_SAVE_PROMPT && - wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C))) { + wm_file_or_session_data_has_unsaved_changes(CTX_data_main(C), CTX_wm_manager(C))) { wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); callback->exec = post_action_fn; callback->user_data = IDP_CopyProperty(op->properties); diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 7113beef56e..2c92d4ac6f8 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -368,7 +368,8 @@ void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win) CTX_wm_window_set(C, win); if (U.uiflag & USER_SAVE_PROMPT) { - if (wm_file_or_image_is_modified(CTX_data_main(C), CTX_wm_manager(C)) && !G.background) { + if (wm_file_or_session_data_has_unsaved_changes(CTX_data_main(C), CTX_wm_manager(C)) && + !G.background) { wm_window_raise(win); wm_confirm_quit(C); } diff --git a/source/blender/windowmanager/wm_files.h b/source/blender/windowmanager/wm_files.h index 2fa5a68829e..135f31cf8ac 100644 --- a/source/blender/windowmanager/wm_files.h +++ b/source/blender/windowmanager/wm_files.h @@ -79,7 +79,7 @@ void wm_close_file_dialog(bContext *C, struct wmGenericCallback *post_action); bool wm_operator_close_file_dialog_if_needed(bContext *C, wmOperator *op, wmGenericCallbackFn exec_fn); -bool wm_file_or_image_is_modified(const Main *bmain, const wmWindowManager *wm); +bool wm_file_or_session_data_has_unsaved_changes(const Main *bmain, const wmWindowManager *wm); void WM_OT_save_homefile(struct wmOperatorType *ot); void WM_OT_save_userpref(struct wmOperatorType *ot); |