Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/blenkernel/BKE_asset_catalog.hh17
-rw-r--r--source/blender/blenkernel/BKE_asset_library.h3
-rw-r--r--source/blender/blenkernel/BKE_asset_library.hh10
-rw-r--r--source/blender/blenkernel/intern/asset_catalog.cc27
-rw-r--r--source/blender/blenkernel/intern/asset_catalog_test.cc18
-rw-r--r--source/blender/blenkernel/intern/asset_library.cc30
-rw-r--r--source/blender/blenkernel/intern/asset_library_service.cc19
-rw-r--r--source/blender/blenkernel/intern/asset_library_service.hh3
-rw-r--r--source/blender/blenkernel/intern/asset_library_service_test.cc74
-rw-r--r--source/blender/blenkernel/intern/asset_library_test.cc6
-rw-r--r--source/blender/editors/asset/CMakeLists.txt1
-rw-r--r--source/blender/editors/asset/ED_asset_catalog.h39
-rw-r--r--source/blender/editors/asset/ED_asset_catalog.hh4
-rw-r--r--source/blender/editors/asset/intern/asset_catalog.cc54
-rw-r--r--source/blender/editors/asset/intern/asset_ops.cc57
-rw-r--r--source/blender/editors/include/ED_asset.h2
-rw-r--r--source/blender/editors/space_file/asset_catalog_tree_view.cc20
-rw-r--r--source/blender/windowmanager/WM_types.h4
-rw-r--r--source/blender/windowmanager/intern/wm_files.c60
-rw-r--r--source/blender/windowmanager/intern/wm_window.c3
-rw-r--r--source/blender/windowmanager/wm_files.h2
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);