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:
@@ -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 = (
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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", "", "");
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user