Core: Implement new blendfile compatibility handling.

This implements the main aspects of changes to blendfile compatibility
as designed in #109151:
* Blender files which file minversion is newer than current Blender
  executable won't be loaded at all.
* Blender files which file version is newer than current Blender will
  triger systematic warning to user:
  * In the status info bar (lower right corner in default UI).
  * When attempting to save (overwrite) them.

This means that the file minversion becomes a hard limit, and not a
soft, warning-only as it used to be. Further more, forward compatibility
warning is now systematic (instead of depending on file minversion),
and more visible for users.

See also https://wiki.blender.org/wiki/Process/Compatibility_Handling
for details over the new policy.

Technically:
* Opening any file with a minversion newer than current Blender file one
  now triggers an early abort, with an error message reported to the user.
  This is handled by a new utils called from `blo_decode_and_check`.
* Any file newer than current Blender version sets a new
  `has_forward_compatibility_issues` flag in Main struct at read time.
* Status bar info area is turned into a template, which uses this flag
  to display special warning UI and tooltip when set.
* A new confirmation popup appears when user tries to save (overwrite)
  such a 'newer' blendfile, stating potential loos of data, and
  proposing by default to 'save as' instead.
* The 'quit unsaved' popup has also been updated to 'save as' instead of
  'save' when the edited file is has potential forward compitibility
  issues.

NOTE: For 3.3LTS, an additional hot-fix is applied on read file
minversion, to ensure that all files from 3.6LTS releases (and
versions in-between 3.3 and 3.6) remain readable in Blender 3.3LTS
after this commit.

Part of #109151 (PR !110109).
This commit is contained in:
Bastien Montagne 2023-07-24 14:18:22 +02:00 • committed by Philipp Oeser
parent d4ebd222a5
commit 1637896367
10 changed files with 481 additions and 30 deletions

View file

@ -25,7 +25,7 @@ class STATUSBAR_HT_header(Header):
row.alignment = 'RIGHT'
# Stats & Info
row.label(text=context.screen.statusbar_info(), translate=False)
layout.template_status_info()
classes = (

View file

@ -124,8 +124,16 @@ typedef struct Main {
char filepath[1024]; /* 1024 = FILE_MAX */
short versionfile, subversionfile; /* see BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION */
short minversionfile, minsubversionfile;
/** The currently opened .blend file was written from a newer version of Blender, and has forward
* compatibility issues (data loss).
*
* \note: In practice currently this is only based on the version numbers, in the future it
* could try to use more refined detection on load. */
bool has_forward_compatibility_issues;
uint64_t build_commit_timestamp; /* commit's timestamp from buildinfo */
char build_hash[16]; /* hash from buildinfo */
/** Indicate the #Main.filepath (file) is the recovered one. */
char recovered;
/** All current ID's exist in the last memfile undo step. */
@ -424,6 +432,10 @@ int set_listbasepointers(struct Main *main, struct ListBase *lb[]);
((main)->versionfile < (ver) || \
((main)->versionfile == (ver) && (main)->subversionfile < (subver)))
#define MAIN_VERSION_FILE_OLDER_OR_EQUAL(main, ver, subver) \
((main)->versionfile < (ver) || \
((main)->versionfile == (ver) && (main)->subversionfile <= (subver)))
/**
* The size of thumbnails (optionally) stored in the `.blend` files header.
*

View file

@ -62,6 +62,7 @@
#include "BKE_anim_data.h"
#include "BKE_animsys.h"
#include "BKE_asset.h"
#include "BKE_blender_version.h"
#include "BKE_collection.h"
#include "BKE_global.h" /* for G */
#include "BKE_idprop.h"
@ -517,6 +518,8 @@ void blo_split_main(ListBase *mainlist, Main *main)
libmain->curlib = lib;
libmain->versionfile = lib->versionfile;
libmain->subversionfile = lib->subversionfile;
libmain->has_forward_compatibility_issues = !MAIN_VERSION_FILE_OLDER_OR_EQUAL(
libmain, BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
BLI_addtail(mainlist, libmain);
lib->temp_index = i;
lib_main_array[i] = libmain;
@ -536,6 +539,21 @@ void blo_split_main(ListBase *mainlist, Main *main)
MEM_freeN(lib_main_array);
}
/* Hot-fix file minversion for a specific range of versions.
*
* This is needed for newer LTS releases of 3.6 to be able to properly open files from early
* development versions of 4.0. */
static void file_global_minversion_hotfix(FileGlobal *fg)
{
if ((fg->minversion > BLENDER_FILE_VERSION) ||
(fg->minversion == BLENDER_FILE_VERSION && fg->minsubversion > BLENDER_FILE_SUBVERSION)) {
if (fg->minversion < 306 || (fg->minversion == 306 && fg->minsubversion <= 11)) {
fg->minversion = BLENDER_FILE_VERSION;
fg->minsubversion = BLENDER_FILE_SUBVERSION;
}
}
}
static void read_file_version(FileData *fd, Main *main)
{
BHead *bhead;
@ -544,6 +562,8 @@ static void read_file_version(FileData *fd, Main *main)
if (bhead->code == GLOB) {
FileGlobal *fg = read_struct(fd, bhead, "Global");
if (fg) {
file_global_minversion_hotfix(fg);
main->subversionfile = fg->subversion;
main->minversionfile = fg->minversion;
main->minsubversionfile = fg->minsubversion;
@ -1178,6 +1198,54 @@ static FileData *filedata_new(BlendFileReadReport *reports)
return fd;
}
/** Check if minversion of the file is older than current Blender, return false if it is not.
* Should only be called after #read_file_dna was successfuly executed. */
static bool is_minversion_older_than_blender(FileData *fd, ReportList *reports)
{
BLI_assert(fd->filesdna != NULL);
for (BHead *bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
if (bhead->code != GLOB) {
continue;
}
FileGlobal *fg = read_struct(fd, bhead, "Global");
file_global_minversion_hotfix(fg);
if ((fg->minversion > BLENDER_FILE_VERSION) ||
(fg->minversion == BLENDER_FILE_VERSION && fg->minsubversion > BLENDER_FILE_SUBVERSION)) {
char writer_ver_str[16];
char min_reader_ver_str[16];
if (fd->fileversion == fg->minversion) {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), (short)(fd->fileversion), fg->subversion);
BKE_blender_version_blendfile_string_from_values(
min_reader_ver_str, sizeof(min_reader_ver_str), fg->minversion, fg->minsubversion);
}
else {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), (short)(fd->fileversion), -1);
BKE_blender_version_blendfile_string_from_values(
min_reader_ver_str, sizeof(min_reader_ver_str), fg->minversion, -1);
}
BKE_reportf(reports,
RPT_ERROR,
TIP_("The file was saved by a newer version, open it with Blender %s or later"),
min_reader_ver_str);
CLOG_WARN(&LOG,
"%s: File saved by a newer version of Blender (%s), Blender %s or later is "
"needed to open it.",
fd->relabase,
writer_ver_str,
min_reader_ver_str);
MEM_freeN(fg);
return true;
}
MEM_freeN(fg);
return false;
}
return false;
}
static FileData *blo_decode_and_check(FileData *fd, ReportList *reports)
{
decode_blender_header(fd);
@ -1190,6 +1258,10 @@ static FileData *blo_decode_and_check(FileData *fd, ReportList *reports)
blo_filedata_free(fd);
fd = NULL;
}
if (is_minversion_older_than_blender(fd, reports)) {
blo_filedata_free(fd);
fd = NULL;
}
}
else {
BKE_reportf(
@ -3510,10 +3582,15 @@ static BHead *read_global(BlendFileData *bfd, FileData *fd, BHead *bhead)
{
FileGlobal *fg = read_struct(fd, bhead, "Global");
/* copy to bfd handle */
/* NOTE: `bfd->main->versionfile` is supposed to have already been set from `fd->fileversion`
* beforehand by calling code. */
bfd->main->subversionfile = fg->subversion;
bfd->main->has_forward_compatibility_issues = !MAIN_VERSION_FILE_OLDER_OR_EQUAL(
bfd->main, BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
bfd->main->minversionfile = fg->minversion;
bfd->main->minsubversionfile = fg->minsubversion;
bfd->main->build_commit_timestamp = fg->build_commit_timestamp;
BLI_strncpy(bfd->main->build_hash, fg->build_hash, sizeof(bfd->main->build_hash));

View file

@ -17,6 +17,10 @@ struct wmWindowManager;
/* info_stats.c */
void ED_info_stats_clear(struct wmWindowManager *wm, struct ViewLayer *view_layer);
const char *ED_info_statusbar_string_ex(struct Main *bmain,
struct Scene *scene,
struct ViewLayer *view_layer,
const char statusbar_flag);
const char *ED_info_statusbar_string(struct Main *bmain,
struct Scene *scene,
struct ViewLayer *view_layer);

View file

@ -2409,6 +2409,7 @@ void uiTemplateHeader3D_mode(uiLayout *layout, struct bContext *C);
void uiTemplateEditModeSelection(uiLayout *layout, struct bContext *C);
void uiTemplateReportsBanner(uiLayout *layout, struct bContext *C);
void uiTemplateInputStatus(uiLayout *layout, struct bContext *C);
void uiTemplateStatusInfo(uiLayout *layout, struct bContext *C);
void uiTemplateKeymapItemProperties(uiLayout *layout, struct PointerRNA *ptr);
bool uiTemplateEventFromKeymapItem(struct uiLayout *layout,

View file

@ -39,6 +39,8 @@
#include "BLT_translation.h"
#include "BKE_action.h"
#include "BKE_blender_version.h"
#include "BKE_blendfile.h"
#include "BKE_cachefile.h"
#include "BKE_colorband.h"
#include "BKE_colortools.h"
@ -68,6 +70,7 @@
#include "DEG_depsgraph_query.h"
#include "ED_fileselect.h"
#include "ED_info.h"
#include "ED_object.h"
#include "ED_render.h"
#include "ED_screen.h"
@ -6397,6 +6400,126 @@ void uiTemplateInputStatus(uiLayout *layout, struct bContext *C)
}
}
void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
{
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
if (!bmain->has_forward_compatibility_issues) {
const char *status_info_txt = ED_info_statusbar_string(bmain, scene, view_layer);
uiItemL(layout, status_info_txt, ICON_NONE);
return;
}
/* Blender version part is shown as warning area when there are forward compatibility issues with
* currently loaded .blend file. */
const char *status_info_txt = ED_info_statusbar_string_ex(
bmain, scene, view_layer, (U.statusbar_flag & ~STATUSBAR_SHOW_VERSION));
uiItemL(layout, status_info_txt, ICON_NONE);
status_info_txt = ED_info_statusbar_string_ex(bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
uiBut *but;
const uiStyle *style = UI_style_get();
uiLayout *ui_abs = uiLayoutAbsolute(layout, false);
uiBlock *block = uiLayoutGetBlock(ui_abs);
eUIEmbossType previous_emboss = UI_block_emboss_get(block);
UI_fontstyle_set(&style->widgetlabel);
int width = (int)BLF_width(
style->widgetlabel.uifont_id, status_info_txt, strlen(status_info_txt));
width = max_ii(width, (int)(10 * UI_DPI_FAC));
UI_block_align_begin(block);
/* Background for icon. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_DPI_FAC),
UI_UNIT_Y,
NULL,
0.0f,
0.0f,
0,
0,
"");
/* UI_BTYPE_ROUNDBOX's bg color is set in but->col. */
UI_GetThemeColorType4ubv(TH_INFO_WARNING, SPACE_INFO, but->col);
/* Background for the rest of the message. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
UI_UNIT_X + (6 * UI_DPI_FAC),
0,
UI_UNIT_X + width,
UI_UNIT_Y,
NULL,
0.0f,
0.0f,
0,
0,
"");
/* Use icon background at low opacity to highlight, but still contrasting with area TH_TEXT. */
UI_GetThemeColorType4ubv(TH_INFO_WARNING, SPACE_INFO, but->col);
but->col[3] = 64;
UI_block_align_end(block);
UI_block_emboss_set(block, UI_EMBOSS_NONE);
/* The report icon itself. */
static char compat_error_msg[256];
char writer_ver_str[12];
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
SNPRINTF(compat_error_msg,
TIP_("File saved by newer Blender\n(%s), expect loss of data"),
writer_ver_str);
but = uiDefIconBut(block,
UI_BTYPE_BUT,
0,
ICON_ERROR,
(int)(3 * UI_DPI_FAC),
0,
UI_UNIT_X,
UI_UNIT_Y,
NULL,
0.0f,
0.0f,
0.0f,
0.0f,
compat_error_msg);
UI_GetThemeColorType4ubv(TH_INFO_WARNING_TEXT, SPACE_INFO, but->col);
but->col[3] = 255; /* This theme color is RBG only, so have to set alpha here. */
/* The report message. */
but = uiDefBut(block,
UI_BTYPE_BUT,
0,
status_info_txt,
UI_UNIT_X,
0,
(short)(width + UI_UNIT_X),
UI_UNIT_Y,
NULL,
0.0f,
0.0f,
0.0f,
0.0f,
compat_error_msg);
UI_block_emboss_set(block, previous_emboss);
}
/** \} */
/* -------------------------------------------------------------------- */
@ -6694,7 +6817,8 @@ void uiTemplateCacheFileProcedural(uiLayout *layout, const bContext *C, PointerR
const bool engine_supports_procedural = RE_engine_supports_alembic_procedural(engine_type,
scene);
CacheFile *cache_file = (CacheFile *)fileptr->data;
CacheFile *cache_file_eval = (CacheFile *)DEG_get_evaluated_id(CTX_data_depsgraph_pointer(C), &cache_file->id);
CacheFile *cache_file_eval = (CacheFile *)DEG_get_evaluated_id(CTX_data_depsgraph_pointer(C),
&cache_file->id);
bool is_alembic = cache_file_eval->type == CACHEFILE_TYPE_ALEMBIC;
if (!is_alembic) {

View file

@ -626,10 +626,10 @@ static void get_stats_string(
info + *ofs, len - *ofs, TIP_(" | Objects:%s/%s"), stats_fmt->totobjsel, stats_fmt->totobj);
}
static const char *info_statusbar_string(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
char statusbar_flag)
const char *ED_info_statusbar_string_ex(Main *bmain,
Scene *scene,
ViewLayer *view_layer,
const char statusbar_flag)
{
char formatted_mem[15];
size_t ofs = 0;
@ -691,7 +691,7 @@ static const char *info_statusbar_string(Main *bmain,
const char *ED_info_statusbar_string(Main *bmain, Scene *scene, ViewLayer *view_layer)
{
return info_statusbar_string(bmain, scene, view_layer, U.statusbar_flag);
return ED_info_statusbar_string_ex(bmain, scene, view_layer, U.statusbar_flag);
}
const char *ED_info_statistics_string(Main *bmain, Scene *scene, ViewLayer *view_layer)
@ -700,7 +700,7 @@ const char *ED_info_statistics_string(Main *bmain, Scene *scene, ViewLayer *view
STATUSBAR_SHOW_MEMORY |
STATUSBAR_SHOW_VERSION;
return info_statusbar_string(bmain, scene, view_layer, statistics_status_bar_flag);
return ED_info_statusbar_string_ex(bmain, scene, view_layer, statistics_status_bar_flag);
}
static void stats_row(int col1,

View file

@ -1767,6 +1767,9 @@ void RNA_api_ui_layout(StructRNA *srna)
func = RNA_def_function(srna, "template_input_status", "uiTemplateInputStatus");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
func = RNA_def_function(srna, "template_status_info", "uiTemplateStatusInfo");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
func = RNA_def_function(srna, "template_node_link", "uiTemplateNodeLink");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
parm = RNA_def_pointer(func, "ntree", "NodeTree", "", "");

View file

@ -62,6 +62,7 @@
#include "BKE_asset_library.h"
#include "BKE_autoexec.h"
#include "BKE_blender.h"
#include "BKE_blender_version.h"
#include "BKE_blendfile.h"
#include "BKE_callbacks.h"
#include "BKE_context.h"
@ -3129,6 +3130,12 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
if (!is_save_as) {
/* If saved as current file, there are technically no more compatibility issues, the file on
* disk now matches the currently opened data version-wise. */
bmain->has_forward_compatibility_issues = false;
}
WM_event_add_notifier(C, NC_WM | ND_FILESAVE, NULL);
if (!is_save_as && RNA_boolean_get(op->ptr, "exit")) {
@ -3233,10 +3240,16 @@ static int wm_save_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *U
}
if (blendfile_path[0] != '\0') {
char path[FILE_MAX];
if (CTX_data_main(C)->has_forward_compatibility_issues) {
wm_save_file_forwardcompat_dialog(C, op);
ret = OPERATOR_INTERFACE;
}
else {
char path[FILE_MAX];
RNA_string_get(op->ptr, "filepath", path);
ret = wm_save_as_mainfile_exec(C, op);
RNA_string_get(op->ptr, "filepath", path);
ret = wm_save_as_mainfile_exec(C, op);
}
}
else {
WM_event_add_fileselect(C, op);
@ -3520,6 +3533,192 @@ void wm_test_autorun_warning(bContext *C)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Save File Forward Compatibility Dialog
* \{ */
static void free_post_file_close_action(void *arg)
{
wmGenericCallback *action = (wmGenericCallback *)arg;
WM_generic_callback_free(action);
}
static void wm_free_operator_properties_callback(void *user_data)
{
IDProperty *properties = (IDProperty *)user_data;
IDP_FreeProperty(properties);
}
static const char *save_file_forwardcompat_dialog_name = "save_file_forwardcompat_popup";
static void file_forwardcompat_detailed_info_show(uiLayout *parent_layout, Main *bmain)
{
uiLayout *layout = uiLayoutColumn(parent_layout, true);
/* Trick to make both lines of text below close enough to look like they are part of a same
* block. */
uiLayoutSetScaleY(layout, 0.70f);
char writer_ver_str[16];
char current_ver_str[16];
if (bmain->versionfile == BLENDER_VERSION) {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, bmain->subversionfile);
BKE_blender_version_blendfile_string_from_values(
current_ver_str, sizeof(current_ver_str), BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
}
else {
BKE_blender_version_blendfile_string_from_values(
writer_ver_str, sizeof(writer_ver_str), bmain->versionfile, -1);
BKE_blender_version_blendfile_string_from_values(
current_ver_str, sizeof(current_ver_str), BLENDER_VERSION, -1);
}
char message_line1[256];
char message_line2[256];
SNPRINTF(message_line1,
TIP_("This file was saved by a newer version of Blender (%s)"),
writer_ver_str);
SNPRINTF(message_line2,
TIP_("Saving it with this Blender (%s) may cause loss of data"),
current_ver_str);
uiItemL(layout, message_line1, ICON_NONE);
uiItemL(layout, message_line2, ICON_NONE);
}
static void save_file_forwardcompat_cancel(bContext *C, void *arg_block, void *UNUSED(arg_data))
{
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, arg_block);
}
static void save_file_forwardcompat_cancel_button(uiBlock *block, wmGenericCallback *post_action)
{
uiBut *but = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, IFACE_("Cancel"), 0, 0, 0, UI_UNIT_Y, NULL, 0, 0, 0, 0, "");
UI_but_func_set(but, save_file_forwardcompat_cancel, block, post_action);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
}
static void save_file_forwardcompat_overwrite(bContext *C, void *arg_block, void *arg_data)
{
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, arg_block);
/* Re-use operator properties as defined for the initial 'save' operator, which triggered this
* 'forward compat' popup. */
wmGenericCallback *callback = WM_generic_callback_steal(arg_data);
PointerRNA operator_propptr = {0};
PointerRNA *operator_propptr_p = &operator_propptr;
IDProperty *operator_idproperties = callback->user_data;
WM_operator_properties_alloc(&operator_propptr_p, &operator_idproperties, "WM_OT_save_mainfile");
WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, operator_propptr_p, NULL);
WM_generic_callback_free(callback);
}
static void save_file_forwardcompat_overwrite_button(uiBlock *block,
wmGenericCallback *post_action)
{
uiBut *but = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, IFACE_("Overwrite"), 0, 0, 0, UI_UNIT_Y, NULL, 0, 0, 0, 0, "");
UI_but_func_set(but, save_file_forwardcompat_overwrite, block, post_action);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_flag_enable(but, UI_BUT_REDALERT);
}
static void save_file_forwardcompat_saveas(bContext *C, void *arg_block, void *UNUSED(arg_data))
{
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, arg_block);
WM_operator_name_call(C, "WM_OT_save_as_mainfile", WM_OP_INVOKE_DEFAULT, NULL, NULL);
}
static void save_file_forwardcompat_saveas_button(uiBlock *block, wmGenericCallback *post_action)
{
uiBut *but = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, IFACE_("Save As..."), 0, 0, 0, UI_UNIT_Y, NULL, 0, 0, 0, 0, "");
UI_but_func_set(but, save_file_forwardcompat_saveas, block, post_action);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
}
static uiBlock *block_create_save_file_forwardcompat_dialog(bContext *C,
ARegion *region,
void *arg1)
{
wmGenericCallback *post_action = arg1;
Main *bmain = CTX_data_main(C);
uiBlock *block = UI_block_begin(C, region, save_file_forwardcompat_dialog_name, UI_EMBOSS);
UI_block_flag_enable(
block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT);
UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP);
uiLayout *layout = uiItemsAlertBox(block, 34, ALERT_ICON_WARNING);
/* Title. */
uiItemL_ex(
layout, TIP_("Overwrite file with an older Blender version?"), ICON_NONE, true, false);
/* Filename. */
const char *blendfile_path = BKE_main_blendfile_path(CTX_data_main(C));
char filename[FILE_MAX];
if (blendfile_path[0] != '\0') {
BLI_split_file_part(blendfile_path, filename, sizeof(filename));
}
else {
SNPRINTF(filename, "%s.blend", DATA_("untitled"));
/* Since this dialog should only be shown when re-saving an existing file, current filepath
* should never be empty. */
BLI_assert_unreachable();
}
uiItemL(layout, filename, ICON_NONE);
/* Detailed message info. */
file_forwardcompat_detailed_info_show(layout, bmain);
uiItemS_ex(layout, 4.0f);
/* Buttons. */
uiLayout *split = uiLayoutSplit(layout, 0.3f, true);
uiLayoutSetScaleY(split, 1.2f);
uiLayoutColumn(split, false);
save_file_forwardcompat_overwrite_button(block, post_action);
uiLayout *split_right = uiLayoutSplit(split, 0.1f, true);
uiLayoutColumn(split_right, false);
/* Empty space. */
uiLayoutColumn(split_right, false);
save_file_forwardcompat_cancel_button(block, post_action);
uiLayoutColumn(split_right, false);
save_file_forwardcompat_saveas_button(block, post_action);
UI_block_bounds_set_centered(block, 14 * UI_DPI_FAC);
return block;
}
void wm_save_file_forwardcompat_dialog(bContext *C, wmOperator *op)
{
if (!UI_popup_block_name_exists(CTX_wm_screen(C), save_file_forwardcompat_dialog_name)) {
wmGenericCallback *callback = MEM_callocN(sizeof(*callback), __func__);
callback->exec = NULL;
callback->user_data = IDP_CopyProperty(op->properties);
callback->free_user_data = wm_free_operator_properties_callback;
UI_popup_block_invoke(
C, block_create_save_file_forwardcompat_dialog, callback, free_post_file_close_action);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Close File Dialog
* \{ */
@ -3570,10 +3769,22 @@ static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_dat
bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0';
if (file_has_been_saved_before) {
if (WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL, NULL) &
OPERATOR_CANCELLED) {
if (bmain->has_forward_compatibility_issues) {
/* Need to invoke to get the filebrowser and choose where to save the new file.
* This also makes it impossible to keep on going with current operation, which is why
* callback cannot be executed anymore.
*
* This is the same situation as what happens when the file has never been saved before
* (outer `else` statement, below). */
WM_operator_name_call(C, "WM_OT_save_as_mainfile", WM_OP_INVOKE_DEFAULT, NULL, NULL);
execute_callback = false;
}
else {
if (WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL, NULL) &
OPERATOR_CANCELLED) {
execute_callback = false;
}
}
}
else {
WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_INVOKE_DEFAULT, NULL, NULL);
@ -3602,10 +3813,27 @@ static void wm_block_file_close_discard_button(uiBlock *block, wmGenericCallback
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
}
static void wm_block_file_close_save_button(uiBlock *block, wmGenericCallback *post_action)
static void wm_block_file_close_save_button(uiBlock *block,
wmGenericCallback *post_action,
const bool has_forwardcompat_issues)
{
uiBut *but = uiDefIconTextBut(
block, UI_BTYPE_BUT, 0, 0, IFACE_("Save"), 0, 0, 0, UI_UNIT_Y, 0, 0, 0, 0, 0, "");
block,
UI_BTYPE_BUT,
0,
0,
/* Forward compatibility issues force using 'save as' operator instead of 'save' one. */
has_forwardcompat_issues ? IFACE_("Save As...") : IFACE_("Save"),
0,
0,
0,
UI_UNIT_Y,
NULL,
0,
0,
0,
0,
"");
UI_but_func_set(but, wm_block_file_close_save, block, post_action);
UI_but_drawflag_disable(but, UI_BUT_TEXT_LEFT);
UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT);
@ -3635,6 +3863,8 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C,
uiLayout *layout = uiItemsAlertBox(block, 34, ALERT_ICON_QUESTION);
const bool has_forwardcompat_issues = bmain->has_forward_compatibility_issues;
/* Title. */
uiItemL_ex(layout, TIP_("Save changes before closing?"), ICON_NONE, true, false);
@ -3649,6 +3879,11 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C,
}
uiItemL(layout, filename, ICON_NONE);
/* Potential forward compatibility issues message. */
if (has_forwardcompat_issues) {
file_forwardcompat_detailed_info_show(layout, bmain);
}
/* Image Saving Warnings. */
ReportList reports;
BKE_reports_init(&reports, RPT_STORE);
@ -3753,7 +3988,7 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C,
uiLayoutSetScaleY(split, 1.2f);
uiLayoutColumn(split, false);
wm_block_file_close_save_button(block, post_action);
wm_block_file_close_save_button(block, post_action, has_forwardcompat_issues);
uiLayoutColumn(split, false);
wm_block_file_close_discard_button(block, post_action);
@ -3779,19 +4014,13 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C,
wm_block_file_close_cancel_button(block, post_action);
uiLayoutColumn(split_right, false);
wm_block_file_close_save_button(block, post_action);
wm_block_file_close_save_button(block, post_action, has_forwardcompat_issues);
}
UI_block_bounds_set_centered(block, 14 * U.dpi_fac);
return block;
}
static void free_post_file_close_action(void *arg)
{
wmGenericCallback *action = (wmGenericCallback *)arg;
WM_generic_callback_free(action);
}
void wm_close_file_dialog(bContext *C, wmGenericCallback *post_action)
{
if (!UI_popup_block_name_exists(CTX_wm_screen(C), close_file_dialog_name)) {
@ -3803,12 +4032,6 @@ void wm_close_file_dialog(bContext *C, wmGenericCallback *post_action)
}
}
static void wm_free_operator_properties_callback(void *user_data)
{
IDProperty *properties = (IDProperty *)user_data;
IDP_FreeProperty(properties);
}
bool wm_operator_close_file_dialog_if_needed(bContext *C,
wmOperator *op,
wmGenericCallbackFn post_action_fn)

View file

@ -86,6 +86,13 @@ bool wm_operator_close_file_dialog_if_needed(bContext *C,
* 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);
/**
* Confirmation dialog when user is about to save the current blend file, and it was prviously
* created by a newer version of Blender.
*
* Important to ask confirmation, as this is a very common scenario of data loss.
*/
void wm_save_file_forwardcompat_dialog(bContext *C, wmOperator *op);
void WM_OT_save_homefile(struct wmOperatorType *ot);
void WM_OT_save_userpref(struct wmOperatorType *ot);