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:
authorSybren A. Stüvel <sybren@blender.org>2021-10-19 19:07:22 +0300
committerSybren A. Stüvel <sybren@blender.org>2021-10-19 19:07:22 +0300
commit823996b0342b7352fc5b2e24eceb6204612438cd (patch)
tree08dca1ce5386c2b06ab01fad1f2f1ac5c236047a
parentb6c3b41d413813d8059476f2c0357b7a4e51ad22 (diff)
Asset Browser: Improved workflow for asset catalog saving
No longer save asset catalogs on blendfile save. Instead: - extend the confirmation prompt for unsaved changes to show unsaved catalogs. - In the confirmation prompt, make catalog saving explicit & optional, just like we do it for external images. {F10881736} - In the Asset Browser catalog tree, show an operator icon to save the catalogs to disk. It's grayed out if there are no changes to save, or if the .blend wasn't saved yet (required to know where to save the catalog definitions to). {F10881743} Much of the work was done by @Severin and reviewed by me, then we swapped roles. Reviewed By: Severin Differential Revision: https://developer.blender.org/D12796
-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);