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.

Part of #109151 (PR !110109).
This commit is contained in:
Bastien Montagne
2023-07-24 14:18:22 +02:00
parent 830730e2cc
commit a1d7ec7139
10 changed files with 472 additions and 28 deletions

View File

@@ -28,7 +28,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

@@ -138,6 +138,13 @@ 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;
/** Commit timestamp from `buildinfo`. */
uint64_t build_commit_timestamp;
/** Commit Hash from `buildinfo`. */
@@ -458,6 +465,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

@@ -65,6 +65,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"
@@ -410,6 +411,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;
@@ -1078,6 +1081,53 @@ 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 != nullptr);
for (BHead *bhead = blo_bhead_first(fd); bhead; bhead = blo_bhead_next(fd, bhead)) {
if (bhead->code != BLO_CODE_GLOB) {
continue;
}
FileGlobal *fg = static_cast<FileGlobal *>(read_struct(fd, bhead, "Global"));
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);
@@ -1090,6 +1140,10 @@ static FileData *blo_decode_and_check(FileData *fd, ReportList *reports)
blo_filedata_free(fd);
fd = nullptr;
}
if (is_minversion_older_than_blender(fd, reports)) {
blo_filedata_free(fd);
fd = nullptr;
}
}
else {
BKE_reportf(
@@ -3010,10 +3064,15 @@ static BHead *read_global(BlendFileData *bfd, FileData *fd, BHead *bhead)
{
FileGlobal *fg = static_cast<FileGlobal *>(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;
STRNCPY(bfd->main->build_hash, fg->build_hash);

View File

@@ -18,6 +18,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

@@ -2496,6 +2496,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

@@ -42,6 +42,7 @@
#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"
@@ -72,6 +73,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"
@@ -6507,6 +6509,126 @@ void uiTemplateInputStatus(uiLayout *layout, 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_SCALE_FAC));
UI_block_align_begin(block);
/* Background for icon. */
but = uiDefBut(block,
UI_BTYPE_ROUNDBOX,
0,
"",
0,
0,
UI_UNIT_X + (6 * UI_SCALE_FAC),
UI_UNIT_Y,
nullptr,
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_SCALE_FAC),
0,
UI_UNIT_X + width,
UI_UNIT_Y,
nullptr,
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_SCALE_FAC),
0,
UI_UNIT_X,
UI_UNIT_Y,
nullptr,
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,
nullptr,
0.0f,
0.0f,
0.0f,
0.0f,
compat_error_msg);
UI_block_emboss_set(block, previous_emboss);
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -602,10 +602,10 @@ static void get_stats_string(char *info,
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[BLI_STR_FORMAT_INT64_BYTE_UNIT_SIZE];
size_t ofs = 0;
@@ -685,7 +685,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)
@@ -695,7 +695,7 @@ const char *ED_info_statistics_string(Main *bmain, Scene *scene, ViewLayer *view
STATUSBAR_SHOW_VERSION |
STATUSBAR_SHOW_SCENE_DURATION;
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

@@ -1840,6 +1840,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

@@ -63,6 +63,7 @@
#include "BKE_appdir.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"
@@ -3321,6 +3322,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, nullptr);
if (!is_save_as && RNA_boolean_get(op->ptr, "exit")) {
@@ -3425,7 +3432,13 @@ static int wm_save_mainfile_invoke(bContext *C, wmOperator *op, const wmEvent *
}
if (blendfile_path[0] != '\0') {
ret = wm_save_as_mainfile_exec(C, op);
if (CTX_data_main(C)->has_forward_compatibility_issues) {
wm_save_file_forwardcompat_dialog(C, op);
ret = OPERATOR_INTERFACE;
}
else {
ret = wm_save_as_mainfile_exec(C, op);
}
}
else {
WM_event_add_fileselect(C, op);
@@ -3723,6 +3736,206 @@ 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 * /*arg_data*/)
{
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, static_cast<uiBlock *>(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, nullptr, 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, static_cast<uiBlock *>(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(
static_cast<wmGenericCallback *>(arg_data));
PointerRNA operator_propptr = {};
PointerRNA *operator_propptr_p = &operator_propptr;
IDProperty *operator_idproperties = static_cast<IDProperty *>(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, nullptr);
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, nullptr, 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 * /*arg_data*/)
{
wmWindow *win = CTX_wm_window(C);
UI_popup_block_close(C, win, static_cast<uiBlock *>(arg_block));
WM_operator_name_call(C, "WM_OT_save_as_mainfile", WM_OP_INVOKE_DEFAULT, nullptr, nullptr);
}
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,
nullptr,
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 = static_cast<wmGenericCallback *>(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_path_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_SCALE_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_cnew<wmGenericCallback>(__func__);
callback->exec = nullptr;
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
* \{ */
@@ -3774,11 +3987,23 @@ 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, nullptr, nullptr) &
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, nullptr, nullptr);
execute_callback = false;
}
else {
if (WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, nullptr, nullptr) &
OPERATOR_CANCELLED)
{
execute_callback = false;
}
}
}
else {
WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_INVOKE_DEFAULT, nullptr, nullptr);
@@ -3820,10 +4045,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, nullptr, 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,
nullptr,
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);
@@ -3849,6 +4091,8 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo
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);
@@ -3863,6 +4107,11 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo
}
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);
@@ -3969,7 +4218,7 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo
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);
@@ -3995,19 +4244,13 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo
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 * UI_SCALE_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)) {
@@ -4019,12 +4262,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

@@ -89,6 +89,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);