From 776604b366413b7bdcb1b5dc739c15eec6fa2af5 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 00:06:05 +0100 Subject: [PATCH 01/13] Blender SVN: UI for auto update feature --- scripts-blender/addons/blender_svn/prefs.py | 9 +++++++++ .../addons/blender_svn/threaded/svn_status.py | 3 +++ scripts-blender/addons/blender_svn/ui/ui_file_list.py | 4 +++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_svn/prefs.py b/scripts-blender/addons/blender_svn/prefs.py index 1078f4c8..8cb1a43a 100644 --- a/scripts-blender/addons/blender_svn/prefs.py +++ b/scripts-blender/addons/blender_svn/prefs.py @@ -26,6 +26,12 @@ class SVN_addon_preferences(AddonPreferences): default=False ) + do_auto_updates: BoolProperty( + name="Auto-Update", + description="Automatically download updates as they appear on the remote", + default=False + ) + repositories: CollectionProperty(type=SVN_repository) def init_repo_list(self): @@ -110,6 +116,9 @@ class SVN_addon_preferences(AddonPreferences): def is_busy(self): return Processes.is_running('Commit', 'Update') + + ### Keeping a backup of repository data in blender_svn.txt. ### + ### This is important so it doesn't get lost on add=on disable. ### loading: BoolProperty( name="Loading", description="Disable the credential update callbacks while loading repo data to avoid infinite loops", diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 29c04cc4..24d73695 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -165,6 +165,9 @@ class BGP_SVN_Status(BackgroundProcess): update_file_list_svn_list(context, self.list_command_output) repo.refresh_ui_lists(context) self.timestamp_last_update = time.time() + if prefs.do_auto_updates: + if any([f.repos_status != 'normal' for f in repo.external_files]): + print("Automatic updates would start now.") def get_ui_message(self, context): time_since_last_update = time.time() - self.timestamp_last_update diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index 2a2d5fc4..de0f3011 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -223,7 +223,9 @@ def draw_file_list(context, layout): col.separator() col.operator("svn.commit", icon='EXPORT', text="") - col.operator("svn.update_all", icon='IMPORT', text="").revision = 0 + col.prop(prefs, 'do_auto_updates', icon='TEMP', text="") + if not prefs.do_auto_updates: + col.operator("svn.update_all", icon='IMPORT', text="").revision = 0 col.separator() col.operator("svn.cleanup", icon='BRUSH_DATA', text="") -- 2.30.2 From bf0e711d11242473753e8d472a0f7e71843bfce3 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 00:06:36 +0100 Subject: [PATCH 02/13] Blender SVN: UI for directory depth feature --- .../blender_svn/operators/simple_commands.py | 41 +++++++++++++++++++ .../addons/blender_svn/repository.py | 27 ++++++++---- .../addons/blender_svn/ui/ui_context_menus.py | 16 +++++--- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index 6ec997f3..d07aa0d9 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -456,6 +456,46 @@ class SVN_OT_resolve_conflict(May_Modifiy_Current_Blend, Operator): file_entry.status = 'normal' +class SVN_OT_set_directory_depth(Operator): + bl_idname = "svn.set_directory_depth" + bl_label = "Set Directory Depth" + bl_description = "Set update depth of this directory" + bl_options = {'INTERNAL'} + + depth: EnumProperty( + name="Depth", + description="The depth to which this directory should be updated", # For non-directory entries, this property is not used. + items=[ + ('INFINITY', 'Recursive (Default)', "Default update behaviour: Updates will recursively pull in any files or subdirectories not already present", 'OUTLINER', 3), + ('IMMEDIATES', 'Non-Recursive', "Updates will pull in any files or subdirectories not already present; those subdirectories' will be left empty", 'PRESET', 2), + ('FILES', 'Files Only', "Updates will pull in any files not already present, but not subdirectories", 'FILE', 1), + ('EMPTY', 'Empty', "Updates will not pull in any files or subdirectories not already present", 'SELECT_SET', 0), + ], + default='INFINITY' + ) + + @classmethod + def description(cls, context, properties): + depth = cls.__annotations__['depth'] + desc = depth.keywords['description'] + items = depth.keywords['items'] + for identifier, name, item_desc, icon, num in items: + if identifier == properties.depth: + return desc + ":\n" + item_desc + + def execute(self, context): + repo = context.scene.svn.get_repo(context) + active_file = repo.active_file + + if not active_file.is_dir: + self.report({'ERROR'}, "Active file entry is not a directory") + return {'CANCELLED'} + + active_file.depth = self.depth + + return {'FINISHED'} + + class SVN_OT_cleanup(SVN_Operator, Operator): bl_idname = "svn.cleanup" bl_label = "SVN Cleanup" @@ -499,5 +539,6 @@ registry = [ SVN_OT_trash_file, SVN_OT_remove_file, SVN_OT_resolve_conflict, + SVN_OT_set_directory_depth, SVN_OT_cleanup, ] diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 218394d1..27e6d951 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -48,6 +48,17 @@ class SVN_file(PropertyGroup): options=set() ) + depth: EnumProperty( + name="Depth", + description="The depth to which this directory should be updated. If this is a file and not a directory, this value isn't used", + items=[ + ('EMPTY', 'empty', "Updates will not pull in any files or subdirectories not already present"), + ('FILES', 'files', "Updates will pull in any files not already present, but not subdirectories"), + ('IMMEDIATES', 'immediates', "Updates will pull in any files or subdirectories not already present; those subdirectories' will have depth=empty"), + ('INFINITY', 'infinity', "Default update behaviour: Updates will pull in any files or subdirectories not already present; those subdirectories' will have depth-infinity"), + ], + default='INFINITY' + ) status: EnumProperty( name="Status", description="SVN Status of the file in the local repository (aka working copy)", @@ -76,22 +87,18 @@ class SVN_file(PropertyGroup): ("SKIP_ONCE", "Skip Once", "File status is predicted by a working-copy svn file operation, like Revert. Next status update should be ignored, and this enum should be set to SKIPPED_ONCE."), ("SKIPPED_ONCE", "Skipped Once", "File status update was skipped. Next status update can be considered accurate, and this flag can be reset to NONE. Until then, operations on this file should remain disabled."), ], - description="Internal flag that notes what process set a predicted status on this file. Should be empty string when the status is not predicted but confirmed. When svn commit/update predicts a status, that status should not be overwritten until the process is finished. With instantaneous processes, a single status update should be ignored since it may be outdated", + description="Internal flag that notes what process set a predicted status on this file. Used to prevent conflicting operations while one operation is in progress", options=set() ) include_in_commit: BoolProperty( name="Commit", - description="Whether this file should be included in the commit or not", + description="Whether this file should be included in the commit or not. Used by the commit operator UI", default=False, options=set() ) @property - def is_outdated(self): - return self.repos_status == 'modified' and self.status == 'normal' - - @property - def is_dir(self): + def is_dir(self) -> bool: if self.exists: return Path(self.absolute_path).is_dir() else: @@ -152,7 +159,7 @@ class SVN_file(PropertyGroup): return 'QUESTION' @property - def has_default_status(self): + def has_default_status(self) -> bool: return self.status == 'normal' and self.repos_status == 'none' and self.status_prediction_type == 'NONE' show_in_filelist: BoolProperty( @@ -161,6 +168,8 @@ class SVN_file(PropertyGroup): default=False ) + ### File size - Update a string representation one time when file_size_KiB is set. ### + ### We want to avoid re-calculating that string on every re-draw for optimization. ### def get_file_size(self): num = self.file_size_KiB for unit in ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB"): @@ -516,7 +525,7 @@ class SVN_repository(PropertyGroup): ) @property - def active_file(self) -> SVN_file: + def active_file(self) -> Optional[SVN_file]: if len(self.external_files) == 0: return return self.external_files[self.external_files_active_index] diff --git a/scripts-blender/addons/blender_svn/ui/ui_context_menus.py b/scripts-blender/addons/blender_svn/ui/ui_context_menus.py index ed9748b8..0a41c621 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_context_menus.py +++ b/scripts-blender/addons/blender_svn/ui/ui_context_menus.py @@ -24,23 +24,29 @@ def svn_file_list_context_menu(self: UIList, context: Context) -> None: repo = context.scene.svn.get_repo(context) active_file = repo.active_file file_abs_path = repo.get_file_abspath(active_file) + + layout.operator("wm.path_open", + text=f"Open Containing Folder", icon='FILE_FOLDER').filepath = Path(file_abs_path).parent.as_posix() + if active_file.name.endswith("blend"): op = layout.operator("wm.open_mainfile", - text=f"Open {active_file.name}") + text=f"Open {active_file.name}", icon='FILE_BLEND') op.filepath = str(file_abs_path) op.display_file_selector = False op.load_ui = True op = layout.operator("wm.open_mainfile", - text=f"Open {active_file.name} (Keep UI)") + text=f"Open {active_file.name} (Keep UI)", icon='FILE_BLEND') op.filepath = str(file_abs_path) op.display_file_selector = False op.load_ui = False else: layout.operator("wm.path_open", - text=f"Open {active_file.name}").filepath = str(file_abs_path) - layout.operator("wm.path_open", - text=f"Open Containing Folder").filepath = Path(file_abs_path).parent.as_posix() + text=f"Open {active_file.name}", icon=active_file.file_icon).filepath = str(file_abs_path) + + if active_file.is_dir: + layout.operator_menu_enum('svn.set_directory_depth', 'depth', text="Set SVN Depth", icon='OUTLINER') + layout.separator() -- 2.30.2 From 2c6ad7e5e685aa9227ad655267a4befbf3174fb2 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 02:03:20 +0100 Subject: [PATCH 03/13] Blender SVN: Ability to cancel running operations --- .../addons/blender_svn/operators/simple_commands.py | 12 ++++++++++++ .../addons/blender_svn/ui/ui_file_list.py | 3 +++ 2 files changed, 15 insertions(+) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index d07aa0d9..89be760d 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -528,6 +528,17 @@ class SVN_OT_cleanup(SVN_Operator, Operator): return {"FINISHED"} +class SVN_OT_cancel_running_operation(Operator): + bl_idname = "svn.cancel" + bl_label = "SVN Cancel Operation" + bl_description = "Cancel the running operation" + bl_options = {'INTERNAL'} + + def execute(self, context): + Processes.kill('Commit') + Processes.kill('Update') + return {'FINISHED'} + registry = [ SVN_OT_update_single, SVN_OT_revert_and_update, @@ -541,4 +552,5 @@ registry = [ SVN_OT_resolve_conflict, SVN_OT_set_directory_depth, SVN_OT_cleanup, + SVN_OT_cancel_running_operation, ] diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index de0f3011..f3038b6c 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -227,6 +227,9 @@ def draw_file_list(context, layout): if not prefs.do_auto_updates: col.operator("svn.update_all", icon='IMPORT', text="").revision = 0 + if prefs.is_busy: + col.operator('svn.cancel', text="", icon="X") + col.separator() col.operator("svn.cleanup", icon='BRUSH_DATA', text="") -- 2.30.2 From a3ddd47dd58cde2171c5f8f17a1deafa88371132 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 02:04:11 +0100 Subject: [PATCH 04/13] Blender SVN: Fix inconsistent filepath separators --- scripts-blender/addons/blender_svn/threaded/svn_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 24d73695..cb94c201 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -230,7 +230,7 @@ def update_file_list_svn_status(context, file_statuses: Dict[str, Tuple[str, str new_files_on_repo = set() for filepath_str, status_info in file_statuses.items(): svn_path = Path(filepath_str) - svn_path_str = str(filepath_str) + svn_path_str = str(svn_path.as_posix()) suffix = svn_path.suffix if ( (suffix.startswith(".r") and suffix[2:].isdecimal()) -- 2.30.2 From 547d96b05819858a3eb02ae851868f89e8c30ba0 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 02:13:10 +0100 Subject: [PATCH 05/13] Blender SVN: More set-depth UI, disable `svn list` --- .../addons/blender_svn/constants.py | 14 +++++- .../blender_svn/operators/simple_commands.py | 49 ++++++++++++------- scripts-blender/addons/blender_svn/prefs.py | 18 +++++-- .../addons/blender_svn/repository.py | 19 +++---- .../addons/blender_svn/threaded/svn_status.py | 12 ++--- .../addons/blender_svn/ui/ui_file_list.py | 20 +++++--- 6 files changed, 85 insertions(+), 47 deletions(-) diff --git a/scripts-blender/addons/blender_svn/constants.py b/scripts-blender/addons/blender_svn/constants.py index c992e08e..a4434178 100644 --- a/scripts-blender/addons/blender_svn/constants.py +++ b/scripts-blender/addons/blender_svn/constants.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-or-later # (c) 2022, Blender Foundation - Demeter Dzadik +import bpy from collections import OrderedDict SVN_STATUS_DATA = OrderedDict( @@ -94,7 +95,6 @@ SVN_STATUS_DATA = OrderedDict( ] ) - # Based on PySVN/svn/constants.py/STATUS_TYPE_LOOKUP. ENUM_SVN_STATUS = [ (status, status.title(), @@ -119,3 +119,15 @@ SVN_STATUS_CHAR_TO_NAME = { SVN_STATUS_NAME_TO_CHAR = {value: key for key, value in SVN_STATUS_CHAR_TO_NAME.items()} + +DEPTH_ENUM = bpy.props.EnumProperty( + name="Depth", + description="The depth to which this directory should be updated", # For non-directory entries, this property is not used. + items=[ + ('INFINITY', 'Recursive (Default)', "Default update behaviour: Updates will recursively pull in any files or subdirectories not already present", 'OUTLINER', 3), + ('IMMEDIATES', 'Non-Recursive', "Updates will pull in any files or subdirectories not already present; those subdirectories' will be left empty", 'PRESET', 2), + ('FILES', 'Files Only', "Updates will pull in any files not already present, but not subdirectories", 'FILE', 1), + ('EMPTY', 'Empty', "Updates will not pull in any files or subdirectories not already present", 'SELECT_SET', 0), + ], + default='INFINITY' + ) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index 89be760d..75bfe885 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -14,6 +14,7 @@ from send2trash import send2trash from ..threaded.execute_subprocess import execute_svn_command from ..threaded.background_process import Processes from ..util import get_addon_prefs, redraw_viewport +from ..constants import DEPTH_ENUM class SVN_Operator: @@ -456,42 +457,49 @@ class SVN_OT_resolve_conflict(May_Modifiy_Current_Blend, Operator): file_entry.status = 'normal' -class SVN_OT_set_directory_depth(Operator): +class SVN_OT_set_directory_depth(SVN_Operator_Single_File, Operator): bl_idname = "svn.set_directory_depth" bl_label = "Set Directory Depth" bl_description = "Set update depth of this directory" bl_options = {'INTERNAL'} - depth: EnumProperty( - name="Depth", - description="The depth to which this directory should be updated", # For non-directory entries, this property is not used. - items=[ - ('INFINITY', 'Recursive (Default)', "Default update behaviour: Updates will recursively pull in any files or subdirectories not already present", 'OUTLINER', 3), - ('IMMEDIATES', 'Non-Recursive', "Updates will pull in any files or subdirectories not already present; those subdirectories' will be left empty", 'PRESET', 2), - ('FILES', 'Files Only', "Updates will pull in any files not already present, but not subdirectories", 'FILE', 1), - ('EMPTY', 'Empty', "Updates will not pull in any files or subdirectories not already present", 'SELECT_SET', 0), - ], - default='INFINITY' + depth: DEPTH_ENUM + popup: BoolProperty( + name="Popup", + description="Whether the operator should use a pop-up prompt", + default=False ) @classmethod def description(cls, context, properties): - depth = cls.__annotations__['depth'] - desc = depth.keywords['description'] - items = depth.keywords['items'] + desc = DEPTH_ENUM.keywords['description'] + items = DEPTH_ENUM.keywords['items'] for identifier, name, item_desc, icon, num in items: if identifier == properties.depth: return desc + ":\n" + item_desc - def execute(self, context): - repo = context.scene.svn.get_repo(context) - active_file = repo.active_file + def invoke(self, context, event): + if self.popup: + return context.window_manager.invoke_props_dialog(self) + return self.execute(context) - if not active_file.is_dir: + def draw(self, context): + layout = self.layout + layout.use_property_split=True + layout.use_property_decorate=False + layout.prop(self, 'depth') + + def execute(self, context): + file_entry = self.get_file(context) + + if not file_entry.is_dir: self.report({'ERROR'}, "Active file entry is not a directory") return {'CANCELLED'} - active_file.depth = self.depth + file_entry.depth = self.depth + + # Depth info needs to be saved to file to make sure it doesn't get lost. + get_addon_prefs(context).save_repo_info_to_file() return {'FINISHED'} @@ -509,8 +517,11 @@ class SVN_OT_cleanup(SVN_Operator, Operator): def execute(self, context: Context) -> Set[str]: repo = context.scene.svn.get_repo(context) + prefs = get_addon_prefs(context) repo.external_files.clear() + # Load folder depth data from file. + prefs.load_repo_info_from_file() self.execute_svn_command(context, ["svn", "cleanup"]) repo.reload_svn_log(context) diff --git a/scripts-blender/addons/blender_svn/prefs.py b/scripts-blender/addons/blender_svn/prefs.py index 8cb1a43a..6681135c 100644 --- a/scripts-blender/addons/blender_svn/prefs.py +++ b/scripts-blender/addons/blender_svn/prefs.py @@ -129,11 +129,13 @@ class SVN_addon_preferences(AddonPreferences): saved_props = {'url', 'directory', 'name', 'username', 'password', 'display_name'} repo_data = {} - for repo in self['repositories']: - directory = repo.get('directory', '') + for repo_dict, repo in zip(self['repositories'], self.repositories): + directory = repo_dict.get('directory', '') repo_data[directory] = { - key: value for key, value in repo.to_dict().items() if key in saved_props} + key: value for key, value in repo_dict.to_dict().items() if key in saved_props} + + repo_data[directory]['custom_directory_depths'] = {dir_entry.svn_path: dir_entry.depth for dir_entry in repo.external_files if dir_entry.is_dir and dir_entry.depth != 'INFINITY'} filepath = Path(bpy.utils.user_resource('CONFIG')) / \ Path("blender_svn.txt") @@ -157,7 +159,15 @@ class SVN_addon_preferences(AddonPreferences): repo = self.repositories.add() repo.directory = directory for key, value in repo_data.items(): - setattr(repo, key, value) + if key == 'custom_directory_depths': + for dir_path, depth in repo_data['custom_directory_depths'].items(): + dir_entry = self.external_files.get(dir_path) + if not dir_entry: + dir_entry = self.external_files.add() + dir_entry.svn_path = dir_path + dir_entry.depth = depth + else: + setattr(repo, key, value) finally: self.loading = False diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 27e6d951..5adec38d 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -48,17 +48,13 @@ class SVN_file(PropertyGroup): options=set() ) - depth: EnumProperty( - name="Depth", - description="The depth to which this directory should be updated. If this is a file and not a directory, this value isn't used", - items=[ - ('EMPTY', 'empty', "Updates will not pull in any files or subdirectories not already present"), - ('FILES', 'files', "Updates will pull in any files not already present, but not subdirectories"), - ('IMMEDIATES', 'immediates', "Updates will pull in any files or subdirectories not already present; those subdirectories' will have depth=empty"), - ('INFINITY', 'infinity', "Default update behaviour: Updates will pull in any files or subdirectories not already present; those subdirectories' will have depth-infinity"), - ], - default='INFINITY' - ) + depth: constants.DEPTH_ENUM + @property + def depth_icon(self): + for identifier, name, item_desc, icon, num in constants.DEPTH_ENUM.keywords['items']: + if identifier == self.depth: + return icon + status: EnumProperty( name="Status", description="SVN Status of the file in the local repository (aka working copy)", @@ -269,6 +265,7 @@ class SVN_repository(PropertyGroup): url: StringProperty( name="URL", description="URL of the remote repository", + update=update_repo_info_file ) def update_directory(self, context): diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index cb94c201..62680c44 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -153,16 +153,16 @@ class BGP_SVN_Status(BackgroundProcess): # NOTE: This one-by-one updating functionality conflicts with a potential # future support for partial check-outs, so that would require storing user-intended # partially checked out folders separately somewhere. - self.list_command_output = execute_svn_command( - context, - ["svn", "list", "--recursive", "--xml"], - use_cred=True, - ) + # self.list_command_output = execute_svn_command( + # context, + # ["svn", "list", "--recursive", "--xml"], + # use_cred=True, + # ) def process_output(self, context, prefs): repo = context.scene.svn.get_repo(context) update_file_list_svn_status(context, svn_status_xml_to_dict(self.output)) - update_file_list_svn_list(context, self.list_command_output) + # update_file_list_svn_list(context, self.list_command_output) repo.refresh_ui_lists(context) self.timestamp_last_update = time.time() if prefs.do_auto_updates: diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index f3038b6c..8b7271ee 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -74,8 +74,9 @@ class SVN_UL_file_list(UIList): # I think it's better to let the user know in advance. statuses.append('conflicted') # Updating the file will create an actual conflict. - ops.append(ops_ui.operator( - 'svn.update_single', text="", icon='IMPORT')) + if not prefs.do_auto_updates: + ops.append(ops_ui.operator( + 'svn.update_single', text="", icon='IMPORT')) elif file_entry.status == 'conflicted': ops.append(ops_ui.operator('svn.resolve_conflict', @@ -88,19 +89,26 @@ class SVN_UL_file_list(UIList): # From user POV it makes a bit more sense to call a file that doesn't # exist yet "added" instead of "outdated". statuses.append('added') - ops.append(ops_ui.operator( - 'svn.update_single', text="", icon='IMPORT')) + if not prefs.do_auto_updates: + ops.append(ops_ui.operator( + 'svn.update_single', text="", icon='IMPORT')) elif file_entry.status == 'normal' and file_entry.repos_status == 'modified': # From user POV, this file is outdated, not 'normal'. statuses = ['none'] - ops.append(ops_ui.operator( - 'svn.update_single', text="", icon='IMPORT')) + if not prefs.do_auto_updates: + ops.append(ops_ui.operator( + 'svn.update_single', text="", icon='IMPORT')) elif file_entry.status in ['normal', 'external', 'ignored']: pass else: print("Unknown file status: ", file_entry.svn_path, file_entry.status, file_entry.repos_status) + if file_entry.is_dir: + op = ops_ui.operator('svn.set_directory_depth', text="", icon=file_entry.depth_icon) + op.popup=True + ops.append(op) + for op in ops: if hasattr(op, 'file_rel_path'): op.file_rel_path = file_entry.svn_path -- 2.30.2 From a41384c13cbcdf2618ee72aa5b7da99f8fa7996a Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 02:58:25 +0100 Subject: [PATCH 06/13] Fix depth info not getting preserved --- scripts-blender/addons/blender_svn/prefs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts-blender/addons/blender_svn/prefs.py b/scripts-blender/addons/blender_svn/prefs.py index 6681135c..70b3ccc6 100644 --- a/scripts-blender/addons/blender_svn/prefs.py +++ b/scripts-blender/addons/blender_svn/prefs.py @@ -159,15 +159,15 @@ class SVN_addon_preferences(AddonPreferences): repo = self.repositories.add() repo.directory = directory for key, value in repo_data.items(): - if key == 'custom_directory_depths': - for dir_path, depth in repo_data['custom_directory_depths'].items(): - dir_entry = self.external_files.get(dir_path) - if not dir_entry: - dir_entry = self.external_files.add() - dir_entry.svn_path = dir_path - dir_entry.depth = depth - else: setattr(repo, key, value) + + if 'custom_directory_depths' in repo_data: + for dir_path, depth in repo_data['custom_directory_depths'].items(): + dir_entry = repo.external_files.get(dir_path) + if not dir_entry: + dir_entry = repo.external_files.add() + dir_entry.svn_path = dir_path + dir_entry.depth = depth finally: self.loading = False -- 2.30.2 From 7aa18915dad12c05f790e8b4661ae4290ac3b8db Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 02:58:51 +0100 Subject: [PATCH 07/13] Fix active index being invalid on cleanup --- scripts-blender/addons/blender_svn/operators/simple_commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index 75bfe885..f6ca2c19 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -520,6 +520,7 @@ class SVN_OT_cleanup(SVN_Operator, Operator): prefs = get_addon_prefs(context) repo.external_files.clear() + repo.external_files_active_index = -1 # Load folder depth data from file. prefs.load_repo_info_from_file() self.execute_svn_command(context, ["svn", "cleanup"]) -- 2.30.2 From e09786f11c038f5513aed97b1dcf9c25335a4849 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 02:59:44 +0100 Subject: [PATCH 08/13] Draw file size in the UI (temp) --- scripts-blender/addons/blender_svn/ui/ui_file_list.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index 8b7271ee..6b2537d8 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -45,6 +45,7 @@ class SVN_UL_file_list(UIList): if self.show_file_paths: filepath_ui.prop(file_entry, 'name', text="", emboss=False, icon=file_entry.file_icon) + filepath_ui.label(text="Size: "+file_entry.file_size) else: filepath_ui.label(text=file_entry.file_name, icon=file_entry.file_icon) -- 2.30.2 From 0e8ef5035f7ca1b12e306aa9ca713cfafe65c1aa Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 03:01:11 +0100 Subject: [PATCH 09/13] Fix `svn list` inconsistent file paths But leave it disabled for now because it's super slow and should only run on demand. --- scripts-blender/addons/blender_svn/threaded/svn_status.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 62680c44..81b57e27 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -337,10 +337,11 @@ def update_file_list_svn_list(context, svn_list_str: str) -> Dict: file_infos = svn_list_xml['lists']['list']['entry'] for file_info in file_infos: - svn_path = file_info['name'] + svn_path = str(Path(file_info['name']).as_posix()) kind = file_info['@kind'] file_entry = repo.external_files.get(svn_path) if not file_entry: + continue file_entry = repo.external_files.add() file_entry.svn_path = svn_path file_entry.absolute_path = str(repo.svn_to_absolute_path(svn_path)) -- 2.30.2 From 559b57f83e025d80b50cf00b804da008aa400ea2 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Sun, 24 Mar 2024 04:44:14 +0100 Subject: [PATCH 10/13] Enable auto-updates, but lose per-file feedback for now --- scripts-blender/addons/blender_svn/threaded/svn_status.py | 2 +- scripts-blender/addons/blender_svn/threaded/update.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 81b57e27..37a711b2 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -167,7 +167,7 @@ class BGP_SVN_Status(BackgroundProcess): self.timestamp_last_update = time.time() if prefs.do_auto_updates: if any([f.repos_status != 'normal' for f in repo.external_files]): - print("Automatic updates would start now.") + Processes.start('Update') def get_ui_message(self, context): time_since_last_update = time.time() - self.timestamp_last_update diff --git a/scripts-blender/addons/blender_svn/threaded/update.py b/scripts-blender/addons/blender_svn/threaded/update.py index dfb83f26..c3445774 100644 --- a/scripts-blender/addons/blender_svn/threaded/update.py +++ b/scripts-blender/addons/blender_svn/threaded/update.py @@ -64,7 +64,7 @@ class BGP_SVN_Update(BackgroundProcess): print(self.message) self.set_predicted_file_status(file) - command = ["svn", "up", file.svn_path, "--accept", "postpone", "--depth", "empty"] + command = ["svn", "up", file.svn_path, "--accept", "postpone"]#, "--depth", "empty"] if self.revision > 0: command.insert(2, f"-r{self.revision}") self.output = execute_svn_command(context, command, use_cred=True) -- 2.30.2 From 40b12996f4c766a33182b812d476ccdf05d725be Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Tue, 26 Mar 2024 02:48:00 +0100 Subject: [PATCH 11/13] Blender SVN: Pretty good file tree view! Also comes with: - svn_path setter now ensures consistent path separators - File entries should now be added via new file_add() which also sets tree data - Better UI messages for revert and update - Revert and update now disable auto-update - Auto-update now actually works - Skip adding an entry for the SVN root directory - SVN List is now a separate BackgroundProcess that is started only when a new file entry is added that isn't on the local repo. So it runs rarely. - Tree view works well with search TODO: - Tree view currently not mutually exclusive with showing full paths, but will be soon. - File and folder sizes are not yet displayed. - UI for folder depth setting (sparse check-out) is pretty ugly atm. --- .../blender_svn/operators/simple_commands.py | 5 +- .../blender_svn/operators/svn_update.py | 16 ++++- scripts-blender/addons/blender_svn/prefs.py | 4 +- .../addons/blender_svn/repository.py | 57 +++++++++++++-- .../addons/blender_svn/threaded/svn_log.py | 2 +- .../addons/blender_svn/threaded/svn_status.py | 72 ++++++++++++------- .../addons/blender_svn/ui/ui_file_list.py | 33 ++++++--- 7 files changed, 141 insertions(+), 48 deletions(-) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index f6ca2c19..a5692f83 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -182,7 +182,7 @@ class SVN_OT_update_single(May_Modifiy_Current_Blend, Operator): class SVN_OT_download_file_revision(May_Modifiy_Current_Blend, Operator): bl_idname = "svn.download_file_revision" bl_label = "Download Revision" - bl_description = "Download this revision of this file" + bl_description = "Download this revision of this file. Automatic updates will be disabled and need to be re-enabled manually" bl_options = {'INTERNAL'} missing_file_allowed = True @@ -212,6 +212,8 @@ class SVN_OT_download_file_revision(May_Modifiy_Current_Blend, Operator): return {'CANCELLED'} self.svn_download_file_revision(context, self.file_rel_path, self.revision) + prefs = get_addon_prefs(context) + prefs.do_auto_updates = False self.report( {'INFO'}, f"Checked out revision {self.revision} of {self.file_rel_path}" @@ -549,6 +551,7 @@ class SVN_OT_cancel_running_operation(Operator): def execute(self, context): Processes.kill('Commit') Processes.kill('Update') + Processes.kill('List') return {'FINISHED'} registry = [ diff --git a/scripts-blender/addons/blender_svn/operators/svn_update.py b/scripts-blender/addons/blender_svn/operators/svn_update.py index 4be00342..aa491765 100644 --- a/scripts-blender/addons/blender_svn/operators/svn_update.py +++ b/scripts-blender/addons/blender_svn/operators/svn_update.py @@ -15,7 +15,7 @@ from ..util import get_addon_prefs class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): bl_idname = "svn.update_all" bl_label = "SVN Update All" - bl_description = "Download all the latest updates from the remote repository" + bl_description = "Update entire repository" bl_options = {'INTERNAL'} revision: IntProperty( @@ -24,6 +24,16 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): default=0 ) + @classmethod + def description(cls, context, properties): + if properties.revision == 0: + return "Update entire repository to the latest version" + text = f"Revert entire repository to r{properties.revision}" + prefs = get_addon_prefs(context) + if prefs.do_auto_updates: + text += ". This will disable auto-updates, which can be re-enabled again manually" + return text + @classmethod def poll(cls, context): if get_addon_prefs(context).is_busy: @@ -72,6 +82,10 @@ class SVN_OT_update_all(May_Modifiy_Current_Blend, Operator): def execute(self, context: Context) -> Set[str]: Processes.stop('Status') + + prefs = get_addon_prefs(context) + prefs.do_auto_updates = False + if self.reload_file: current_file = context.scene.svn.get_repo(context).current_blend_file command = ["svn", "up", current_file.svn_path, "--accept", "postpone"] diff --git a/scripts-blender/addons/blender_svn/prefs.py b/scripts-blender/addons/blender_svn/prefs.py index 70b3ccc6..2c6617eb 100644 --- a/scripts-blender/addons/blender_svn/prefs.py +++ b/scripts-blender/addons/blender_svn/prefs.py @@ -165,8 +165,7 @@ class SVN_addon_preferences(AddonPreferences): for dir_path, depth in repo_data['custom_directory_depths'].items(): dir_entry = repo.external_files.get(dir_path) if not dir_entry: - dir_entry = repo.external_files.add() - dir_entry.svn_path = dir_path + dir_entry = repo.file_add(dir_path) dir_entry.depth = depth finally: self.loading = False @@ -185,7 +184,6 @@ class SVN_addon_preferences(AddonPreferences): else: draw_repo_list(self, context) - def draw_prefs_no_svn(self, context): terminal, url = "terminal", "https://subversion.apache.org/packages.html" system = platform.system() diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 5adec38d..6d0cfb12 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -36,7 +36,9 @@ class SVN_file(PropertyGroup): @svn_path.setter def svn_path(self, value): - self.name = value + path = Path(value) + self.name = str(path.as_posix()) + self.tree_depth = len(path.parents)-1 @property def file_name(self): @@ -180,6 +182,14 @@ class SVN_file(PropertyGroup): file_size_KiB: FloatProperty(description="One KibiByte (KiB) is 1024 bytes", update=update_file_size) file_size: StringProperty(description="File size for displaying in the UI") + ### File tree ### + def update_is_expanded(self, context): + repo = context.scene.svn.get_repo(context) + repo.external_files_active_index = repo.external_files.find(self.name) + repo.refresh_ui_lists(context) + is_expanded: BoolProperty(update=update_is_expanded, description="Whether this directory's contents should be shown in file tree view") + tree_depth: IntProperty(description="Number of indentations in the tree, ie. number of parents. Set automatically when svn_path is set") + has_children: BoolProperty(description="Whether this is a directory with any children. Updated whenever a new file entry is added") class SVN_log(PropertyGroup): """Property Group that can represent an SVN log entry.""" @@ -431,6 +441,15 @@ class SVN_repository(PropertyGroup): ### SVN File List. ### external_files: CollectionProperty(type=SVN_file) + def file_add(self, svn_path: Path or str) -> SVN_file: + file_entry = self.external_files.add() + file_entry.svn_path = svn_path + file_entry.absolute_path = str(self.svn_to_absolute_path(svn_path)) + parent = self.get_parent_file(file_entry) + if parent: + parent.has_children = True + return file_entry + def remove_file_entry(self, file_entry: SVN_file): """Remove a file entry from the file list, based on its filepath.""" for i, f in enumerate(self.external_files): @@ -521,6 +540,9 @@ class SVN_repository(PropertyGroup): options=set(), ) + def get_parent_file(self, file: SVN_file) -> Optional[SVN_file]: + return self.external_files.get(str(Path(file.svn_path).parent.as_posix())) + @property def active_file(self) -> Optional[SVN_file]: if len(self.external_files) == 0: @@ -565,7 +587,11 @@ class SVN_repository(PropertyGroup): Also triggers a refresh of the SVN UIList, through the update callback of external_files_active_index.""" + if len(self.external_files) == 0: + return + UI_LIST = bpy.types.UI_UL_list + if self.file_search_filter: filter_list = UI_LIST.filter_items_by_name( self.file_search_filter, @@ -576,17 +602,29 @@ class SVN_repository(PropertyGroup): ) filter_list = [bool(val) for val in filter_list] self.external_files.foreach_set('show_in_filelist', filter_list) - else: + if self.use_file_tree_display: + for file in self.external_files: + if file.show_in_filelist: + parent = self.get_parent_file(file) + while parent: + parent.show_in_filelist = True + parent = self.get_parent_file(parent) + + if self.use_file_tree_display: + for file in self.external_files: + parent = self.get_parent_file(file) + while parent: + if not parent.is_expanded: + file.show_in_filelist = False + break + parent = self.get_parent_file(parent) + elif not self.file_search_filter: for file in self.external_files: if file == self.current_blend_file: file.show_in_filelist = True continue - file.show_in_filelist = not file.has_default_status - if len(self.external_files) == 0: - return - # Make sure the active file isn't now being filtered out. # If it is, change the active file to the first visible one. if self.active_file.show_in_filelist: @@ -602,6 +640,13 @@ class SVN_repository(PropertyGroup): update=refresh_ui_lists ) + use_file_tree_display: BoolProperty( + name="File Display Mode", + description="Whether the full file tree sould be drawn instead of just modified files as a flat list", + default=False, + update=refresh_ui_lists + ) + registry = [ SVN_file, diff --git a/scripts-blender/addons/blender_svn/threaded/svn_log.py b/scripts-blender/addons/blender_svn/threaded/svn_log.py index 6f0e8847..670ca684 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_log.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_log.py @@ -74,7 +74,7 @@ def reload_svn_log(self, context): file_path = Path(file_path) log_file_entry = log_entry.changed_files.add() - log_file_entry.svn_path = str(file_path.as_posix()) + log_file_entry.svn_path = file_path log_file_entry.revision = r_number log_file_entry.status = constants.SVN_STATUS_CHAR_TO_NAME[status_char] diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 37a711b2..7d9976d8 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -137,7 +137,6 @@ class BGP_SVN_Status(BackgroundProcess): def __init__(self): self.timestamp_last_update = 0 - self.list_command_output = "" super().__init__() def acquire_output(self, context, prefs): @@ -146,27 +145,14 @@ class BGP_SVN_Status(BackgroundProcess): ["svn", "status", "--show-updates", "--verbose", "--xml"], use_cred=True, ) - # The list command includes file size info and also files of directories - # which have their Depth set to Empty, which is used for a partial check-out, - # which we also use for updating files and folders one-by-one instead of - # all-at-once, so we can provide more live feedback in the UI. - # NOTE: This one-by-one updating functionality conflicts with a potential - # future support for partial check-outs, so that would require storing user-intended - # partially checked out folders separately somewhere. - # self.list_command_output = execute_svn_command( - # context, - # ["svn", "list", "--recursive", "--xml"], - # use_cred=True, - # ) def process_output(self, context, prefs): repo = context.scene.svn.get_repo(context) update_file_list_svn_status(context, svn_status_xml_to_dict(self.output)) - # update_file_list_svn_list(context, self.list_command_output) repo.refresh_ui_lists(context) self.timestamp_last_update = time.time() if prefs.do_auto_updates: - if any([f.repos_status != 'normal' for f in repo.external_files]): + if any([f.repos_status != 'none' for f in repo.external_files]): Processes.start('Update') def get_ui_message(self, context): @@ -178,6 +164,34 @@ class BGP_SVN_Status(BackgroundProcess): return f"Updating repo status..." +class BGP_SVN_List(BackgroundProcess): + """The `svn list` command includes file size info and files excluded via + partial checkout functionality (`--set-depth empty`). + However, it is very slow, so this should be used sparingly. + """ + + name = "List" + needs_authentication = True + timeout = 30 + repeat_delay = 0 + debug = False + + def acquire_output(self, context, prefs): + self.output = execute_svn_command( + context, + ["svn", "list", "--recursive", "--xml"], + use_cred=True, + ) + + def process_output(self, context, prefs): + repo = context.scene.svn.get_repo(context) + update_file_list_svn_list(context, self.output) + repo.refresh_ui_lists(context) + + def get_ui_message(self, context): + return f"Updating file list..." + + class BGP_SVN_Authenticate(BGP_SVN_Status): name = "Authenticate" needs_authentication = False @@ -228,7 +242,11 @@ def update_file_list_svn_status(context, file_statuses: Dict[str, Tuple[str, str svn_paths = [] new_files_on_repo = set() - for filepath_str, status_info in file_statuses.items(): + file_statuses = [(key, value) for key, value in file_statuses.items()] + file_statuses.sort(key=lambda x: x[0]) + for filepath_str, status_info in file_statuses: + if filepath_str == ".": + continue svn_path = Path(filepath_str) svn_path_str = str(svn_path.as_posix()) suffix = svn_path.suffix @@ -251,8 +269,7 @@ def update_file_list_svn_status(context, file_statuses: Dict[str, Tuple[str, str entry_existed = True if not file_entry: entry_existed = False - file_entry = repo.external_files.add() - file_entry.svn_path = svn_path_str + file_entry = repo.file_add(svn_path_str) if not file_entry.exists: new_files_on_repo.add((file_entry.svn_path, repos_status)) @@ -266,7 +283,6 @@ def update_file_list_svn_status(context, file_statuses: Dict[str, Tuple[str, str file_entry.status = wc_status file_entry.repos_status = repos_status file_entry.status_prediction_type = 'NONE' - file_entry.absolute_path = str(repo.svn_to_absolute_path(svn_path)) if new_files_on_repo: # File entry status has changed between local and repo. @@ -280,6 +296,7 @@ def update_file_list_svn_status(context, file_statuses: Dict[str, Tuple[str, str "\nUpdating log...\n", ) Processes.start('Log') + Processes.start('List') # Remove file entries who no longer seem to have an SVN status. # This can happen if an unversioned file was removed from the filesystem, @@ -327,24 +344,27 @@ def svn_status_xml_to_dict(svn_status_str: str) -> Dict[str, Tuple[str, str, int def update_file_list_svn_list(context, svn_list_str: str) -> Dict: - repo = context.scene.svn.get_repo(context) - try: - svn_list_xml = xmltodict.parse(svn_list_str) - except: - # This seems to fail with an "ExpatError" on Windows...? + if not svn_list_str: + # This shouldn't happen. return + repo = context.scene.svn.get_repo(context) + svn_list_xml = xmltodict.parse(svn_list_str) file_infos = svn_list_xml['lists']['list']['entry'] for file_info in file_infos: svn_path = str(Path(file_info['name']).as_posix()) + if svn_path == ".": + continue kind = file_info['@kind'] file_entry = repo.external_files.get(svn_path) if not file_entry: + # `svn list` will only catch new file entries which are missing due to + # a sparse checkout. + # TODO: The ability to include files that are excluded by a sparse checkout may be useful still. continue - file_entry = repo.external_files.add() + file_entry = repo.file_add(svn_path) file_entry.svn_path = svn_path - file_entry.absolute_path = str(repo.svn_to_absolute_path(svn_path)) if not file_entry.exists: file_entry.status = 'none' file_entry.repos_status = 'added' diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index 6b2537d8..571c37e3 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -24,6 +24,7 @@ class SVN_UL_file_list(UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): # As long as there are any items, always draw the filters. self.use_filter_show = True + repo = context.scene.svn.get_repo(context) if self.layout_type != 'DEFAULT': raise NotImplemented @@ -42,10 +43,19 @@ class SVN_UL_file_list(UIList): ops_ui.enabled = file_entry.status_prediction_type == 'NONE' and not prefs.is_busy + if repo.use_file_tree_display: + split = filepath_ui.split(factor=0.02 * file_entry.tree_depth + 0.00001) + split.row() + row = split.row(align=True) + filepath_ui = row.row(align=True) + if file_entry.has_children: + icon = 'DOWNARROW_HLT' if file_entry.is_expanded else 'RIGHTARROW' + filepath_ui.prop(file_entry, 'is_expanded', text="", icon=icon, emboss=False) + else: + filepath_ui.label(text="", icon='BLANK1') + if self.show_file_paths: - filepath_ui.prop(file_entry, 'name', text="", - emboss=False, icon=file_entry.file_icon) - filepath_ui.label(text="Size: "+file_entry.file_size) + filepath_ui.label(text=file_entry.name, icon=file_entry.file_icon) else: filepath_ui.label(text=file_entry.file_name, icon=file_entry.file_icon) @@ -62,7 +72,7 @@ class SVN_UL_file_list(UIList): ops.append(ops_ui.operator( 'svn.unadd_file', text="", icon='REMOVE')) elif file_entry.status == 'unversioned': - ops.append(ops_ui.operator('svn.add_file', text="", icon='ADD')) + ops.append(ops_ui.operator('svn.file_add', text="", icon='ADD')) ops.append(ops_ui.operator( 'svn.trash_file', text="", icon='TRASH')) @@ -151,15 +161,18 @@ class SVN_UL_file_list(UIList): """Custom filtering UI. Toggles are stored in addon preferences, see cls_filter_items(). """ + repo = context.scene.svn.get_repo(context) + if not repo: + return main_row = layout.row() row = main_row.row(align=True) - row.prop(self, 'show_file_paths', text="", - toggle=True, icon="FILE_FOLDER") + row.prop(repo, 'use_file_tree_display', text="", expand=True, icon='OUTLINER' if repo.use_file_tree_display else 'PRESET') + file_paths = row.row() + file_paths.enabled = not repo.use_file_tree_display + file_paths.prop(self, 'show_file_paths', text="", toggle=True, icon="FILE_FOLDER") - repo = context.scene.svn.get_repo(context) - if repo: - row.prop(repo, 'file_search_filter', text="") + row.prop(repo, 'file_search_filter', text="") def draw_process_info(context, layout): @@ -168,7 +181,7 @@ def draw_process_info(context, layout): any_error = False col = layout.column() for process in Processes.processes.values(): - if process.name not in {'Commit', 'Update', 'Log', 'Status', 'Authenticate'}: + if process.name in {'Redraw Viewport', 'Activate File'}: continue if process.error: -- 2.30.2 From 57af4abb3643feeacbcc75d1202e985329568bc3 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Tue, 26 Mar 2024 03:40:29 +0100 Subject: [PATCH 12/13] Blender SVN: Add popover for filelist display settings - Don't force the search filter to be visible when it's empty - Add a popover menu for file list display settings - Tree View / Flat View is now an enum - Switching to Tree View forces show_file_paths=False - Fix active blend file was not displaying in flat view without search filter - Increase indent distance of tree view - Fix two cases of console errors on Reload Scripts TODO: - Still no file sizes - The fact that Auto-Update isn't always visible in the UI feels wrong --- .../addons/blender_svn/constants.py | 2 +- .../addons/blender_svn/repository.py | 42 ++++++++---- .../threaded/execute_subprocess.py | 3 + .../addons/blender_svn/ui/ui_file_list.py | 66 +++++++++++++------ 4 files changed, 80 insertions(+), 33 deletions(-) diff --git a/scripts-blender/addons/blender_svn/constants.py b/scripts-blender/addons/blender_svn/constants.py index a4434178..6a51e26f 100644 --- a/scripts-blender/addons/blender_svn/constants.py +++ b/scripts-blender/addons/blender_svn/constants.py @@ -124,7 +124,7 @@ DEPTH_ENUM = bpy.props.EnumProperty( name="Depth", description="The depth to which this directory should be updated", # For non-directory entries, this property is not used. items=[ - ('INFINITY', 'Recursive (Default)', "Default update behaviour: Updates will recursively pull in any files or subdirectories not already present", 'OUTLINER', 3), + ('INFINITY', 'Recursive (Default)', "Updates will recursively pull in any files or subdirectories not already present", 'OUTLINER', 3), ('IMMEDIATES', 'Non-Recursive', "Updates will pull in any files or subdirectories not already present; those subdirectories' will be left empty", 'PRESET', 2), ('FILES', 'Files Only', "Updates will pull in any files not already present, but not subdirectories", 'FILE', 1), ('EMPTY', 'Empty', "Updates will not pull in any files or subdirectories not already present", 'SELECT_SET', 0), diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 6d0cfb12..4817245c 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -187,7 +187,7 @@ class SVN_file(PropertyGroup): repo = context.scene.svn.get_repo(context) repo.external_files_active_index = repo.external_files.find(self.name) repo.refresh_ui_lists(context) - is_expanded: BoolProperty(update=update_is_expanded, description="Whether this directory's contents should be shown in file tree view") + is_expanded: BoolProperty(name="Show Contents", update=update_is_expanded, description="Whether this directory's contents should be shown in file tree view") tree_depth: IntProperty(description="Number of indentations in the tree, ie. number of parents. Set automatically when svn_path is set") has_children: BoolProperty(description="Whether this is a directory with any children. Updated whenever a new file entry is added") @@ -303,6 +303,9 @@ class SVN_repository(PropertyGroup): dir_path = Path(self.directory) # TODO: This property is checked pretty often, so we run `svn info` pretty often. Might not be a big deal, but maybe it's a bit overkill? root_dir, base_url = get_svn_info(self.directory) + if not hasattr(self, 'directory'): + # This can happen when running Reload Scripts, resulting in a console error. + return False return ( dir_path.exists() and dir_path.is_dir() and @@ -475,7 +478,7 @@ class SVN_repository(PropertyGroup): return svn_dir / svn_path def get_file_by_absolute_path(self, abs_path: str or Path) -> Optional[SVN_file]: - rel_path = str(self.absolute_to_svn_path(abs_path)) + rel_path = str(self.absolute_to_svn_path(abs_path).as_posix()) if rel_path: return self.external_files.get(rel_path) @@ -578,8 +581,9 @@ class SVN_repository(PropertyGroup): return svn_file @property - def current_blend_file(self) -> SVN_file: - return self.get_file_by_absolute_path(bpy.data.filepath) + def current_blend_file(self) -> Optional[SVN_file]: + if bpy.data.filepath: + return self.get_file_by_absolute_path(bpy.data.filepath) ### File List UIList filter properties ### def refresh_ui_lists(self, context): @@ -602,15 +606,17 @@ class SVN_repository(PropertyGroup): ) filter_list = [bool(val) for val in filter_list] self.external_files.foreach_set('show_in_filelist', filter_list) - if self.use_file_tree_display: + if self.display_mode == 'TREE': for file in self.external_files: if file.show_in_filelist: parent = self.get_parent_file(file) while parent: parent.show_in_filelist = True parent = self.get_parent_file(parent) + else: + self.external_files.foreach_set('show_in_filelist', [True] * len(self.external_files)) - if self.use_file_tree_display: + if self.display_mode == 'TREE': for file in self.external_files: parent = self.get_parent_file(file) while parent: @@ -620,10 +626,9 @@ class SVN_repository(PropertyGroup): parent = self.get_parent_file(parent) elif not self.file_search_filter: for file in self.external_files: - if file == self.current_blend_file: - file.show_in_filelist = True - continue file.show_in_filelist = not file.has_default_status + if self.current_blend_file: + self.current_blend_file.show_in_filelist = True # Make sure the active file isn't now being filtered out. # If it is, change the active file to the first visible one. @@ -640,11 +645,24 @@ class SVN_repository(PropertyGroup): update=refresh_ui_lists ) - use_file_tree_display: BoolProperty( + show_file_paths: BoolProperty( + name="Show File Paths", + description="Show file paths relative to the SVN root, instead of just the file name" + ) + + def update_tree_display(self, context): + if self.display_mode == 'TREE': + self.show_file_paths = False + self.refresh_ui_lists(context) + + display_mode: EnumProperty( name="File Display Mode", description="Whether the full file tree sould be drawn instead of just modified files as a flat list", - default=False, - update=refresh_ui_lists + items=[ + ('FLAT', "Modifications", "Display only modified files as a flat list", 'PRESET', 0), + ('TREE', "Tree View", "Display the full tree of the entire repository", 'OUTLINER', 1), + ], + update=update_tree_display ) diff --git a/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py b/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py index 7ee07ba8..3a8b9596 100644 --- a/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py +++ b/scripts-blender/addons/blender_svn/threaded/execute_subprocess.py @@ -38,6 +38,9 @@ def execute_svn_command( So any file paths that are part of the command should be relative to the SVN root. """ + if not hasattr(context.scene, 'svn'): + # This can happen during Reload Scripts, throwing a console error. + return repo = context.scene.svn.get_repo(context) if "svn" not in command: command.insert(0, "svn") diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index 571c37e3..5ebc7ce5 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -16,14 +16,8 @@ class SVN_UL_file_list(UIList): # Value that indicates that this item has passed the filter process successfully. See rna_ui.c. UILST_FLT_ITEM = 1 << 30 - show_file_paths: BoolProperty( - name="Show File Paths", - description="Show file paths relative to the SVN root, instead of just the file name" - ) - def draw_item(self, context, layout, data, item, icon, active_data, active_propname): - # As long as there are any items, always draw the filters. - self.use_filter_show = True + # As long as there are any items, and a search term is typed in, always draw the filter. repo = context.scene.svn.get_repo(context) if self.layout_type != 'DEFAULT': @@ -43,8 +37,8 @@ class SVN_UL_file_list(UIList): ops_ui.enabled = file_entry.status_prediction_type == 'NONE' and not prefs.is_busy - if repo.use_file_tree_display: - split = filepath_ui.split(factor=0.02 * file_entry.tree_depth + 0.00001) + if repo.display_mode == 'TREE': + split = filepath_ui.split(factor=0.05 * file_entry.tree_depth + 0.00001) split.row() row = split.row(align=True) filepath_ui = row.row(align=True) @@ -54,7 +48,7 @@ class SVN_UL_file_list(UIList): else: filepath_ui.label(text="", icon='BLANK1') - if self.show_file_paths: + if repo.show_file_paths: filepath_ui.label(text=file_entry.name, icon=file_entry.file_icon) else: filepath_ui.label(text=file_entry.file_name, icon=file_entry.file_icon) @@ -155,6 +149,10 @@ class SVN_UL_file_list(UIList): return flt_flags, flt_neworder def filter_items(self, context, data, propname): + # As long as a search term is typed in, draw the filter. + repo = context.scene.svn.get_repo(context) + if repo.file_search_filter: + self.use_filter_show = True return type(self).cls_filter_items(context, data, propname) def draw_filter(self, context, layout): @@ -164,15 +162,7 @@ class SVN_UL_file_list(UIList): repo = context.scene.svn.get_repo(context) if not repo: return - main_row = layout.row() - row = main_row.row(align=True) - - row.prop(repo, 'use_file_tree_display', text="", expand=True, icon='OUTLINER' if repo.use_file_tree_display else 'PRESET') - file_paths = row.row() - file_paths.enabled = not repo.use_file_tree_display - file_paths.prop(self, 'show_file_paths', text="", toggle=True, icon="FILE_FOLDER") - - row.prop(repo, 'file_search_filter', text="") + layout.prop(repo, 'file_search_filter', text="") def draw_process_info(context, layout): @@ -206,6 +196,33 @@ def draw_process_info(context, layout): ", ".join([p.name for p in Processes.running_processes])) +class SVN_PT_filelist_options(bpy.types.Panel): + bl_space_type = 'VIEW_3D' + bl_region_type = 'WINDOW' + bl_label = "Options" + bl_options = {'INSTANCED'} + + def draw(self, context): + layout = self.layout + + prefs = get_addon_prefs(context) + repo = context.scene.svn.get_repo(context) + + layout.label(text="Display Mode:") + layout.prop(repo, 'display_mode', text=" ", expand=True) + + layout.use_property_split=True + layout.use_property_decorate=False + + file_paths = layout.row() + file_paths.enabled = repo.display_mode == 'FLAT' + file_paths.prop(repo, 'show_file_paths') + + layout.separator() + + layout.prop(prefs, 'do_auto_updates', icon='TEMP') + + def draw_file_list(context, layout): prefs = get_addon_prefs(context) repo = prefs.active_repo @@ -243,14 +260,22 @@ def draw_file_list(context, layout): col = row.column() + col.popover( + panel="SVN_PT_filelist_options", + text="", + icon='PREFERENCES', + ) + col.separator() + col.operator("svn.commit", icon='EXPORT', text="") - col.prop(prefs, 'do_auto_updates', icon='TEMP', text="") if not prefs.do_auto_updates: col.operator("svn.update_all", icon='IMPORT', text="").revision = 0 if prefs.is_busy: col.operator('svn.cancel', text="", icon="X") + else: + col.label(text="", icon='BLANK1') col.separator() col.operator("svn.cleanup", icon='BRUSH_DATA', text="") @@ -258,4 +283,5 @@ def draw_file_list(context, layout): registry = [ SVN_UL_file_list, + SVN_PT_filelist_options, ] -- 2.30.2 From 504edc7895e6b888e846ca9540b483a4030d27b6 Mon Sep 17 00:00:00 2001 From: Demeter Dzadik Date: Tue, 26 Mar 2024 18:15:19 +0100 Subject: [PATCH 13/13] Blender SVN: Proper File Size display - Move process info to the bottom so it doesn't make the UI jump around. - Allow multiple processes' message to be shown. - Calculate folder sizes. - Tweak filelist UI columns. - Auto-update button now always visible. --- .../blender_svn/operators/simple_commands.py | 3 +- .../addons/blender_svn/repository.py | 46 ++++++- .../addons/blender_svn/threaded/svn_status.py | 7 +- .../addons/blender_svn/ui/ui_file_list.py | 114 ++++++++++++------ .../addons/blender_svn/ui/ui_sidebar.py | 2 +- 5 files changed, 124 insertions(+), 48 deletions(-) diff --git a/scripts-blender/addons/blender_svn/operators/simple_commands.py b/scripts-blender/addons/blender_svn/operators/simple_commands.py index a5692f83..1e900203 100644 --- a/scripts-blender/addons/blender_svn/operators/simple_commands.py +++ b/scripts-blender/addons/blender_svn/operators/simple_commands.py @@ -545,13 +545,12 @@ class SVN_OT_cleanup(SVN_Operator, Operator): class SVN_OT_cancel_running_operation(Operator): bl_idname = "svn.cancel" bl_label = "SVN Cancel Operation" - bl_description = "Cancel the running operation" + bl_description = "Cancel ongoing commit/update operation" bl_options = {'INTERNAL'} def execute(self, context): Processes.kill('Commit') Processes.kill('Update') - Processes.kill('List') return {'FINISHED'} registry = [ diff --git a/scripts-blender/addons/blender_svn/repository.py b/scripts-blender/addons/blender_svn/repository.py index 4817245c..6b06691c 100644 --- a/scripts-blender/addons/blender_svn/repository.py +++ b/scripts-blender/addons/blender_svn/repository.py @@ -168,16 +168,21 @@ class SVN_file(PropertyGroup): ### File size - Update a string representation one time when file_size_KiB is set. ### ### We want to avoid re-calculating that string on every re-draw for optimization. ### - def get_file_size(self): + def get_file_size_ui_str(self): num = self.file_size_KiB for unit in ("KiB", "MiB", "GiB", "TiB", "PiB", "EiB"): if num < 1024: - return f"{num:3.1f} {unit}" + num_digits = len(str(num).split(".")[0]) + file_size = f"{num:0.{max(0, 3-num_digits)}f} {unit}" + if "." not in file_size: + file_size = file_size.replace(" ", " ") + return file_size num /= 1024.0 - return f"{num:.1f} YiB" + + return "Really Big" def update_file_size(self, _context): - self.file_size = self.get_file_size() + self.file_size = self.get_file_size_ui_str() file_size_KiB: FloatProperty(description="One KibiByte (KiB) is 1024 bytes", update=update_file_size) file_size: StringProperty(description="File size for displaying in the UI") @@ -531,6 +536,30 @@ class SVN_repository(PropertyGroup): for log_entry in self.log] ) + def get_root_subdirs(self): + for f in self.external_files: + if not self.get_parent_file(f): + yield f + + def find_dir_children(self, dir: SVN_file): + for f in self.external_files: + if self.get_parent_file(f) == dir: + yield f + + def calculate_folder_sizes(self): + for root_subdir in self.get_root_subdirs(): + self.calculate_folder_size_recursive(root_subdir) + + def calculate_folder_size_recursive(self, dir: SVN_file): + size = 0 + for child in self.find_dir_children(dir): + if child.is_dir: + self.calculate_folder_size_recursive(child) + size += child.file_size_KiB + + dir.file_size_KiB = size + return size + prev_external_files_active_index: IntProperty( name="Previous Active Index", description="Internal value to avoid triggering the update callback unnecessarily", @@ -645,6 +674,11 @@ class SVN_repository(PropertyGroup): update=refresh_ui_lists ) + show_file_size: BoolProperty( + name="Show Size On Disk", + description="Show size of each file and aggregate size of folders", + default=False + ) show_file_paths: BoolProperty( name="Show File Paths", description="Show file paths relative to the SVN root, instead of just the file name" @@ -659,8 +693,8 @@ class SVN_repository(PropertyGroup): name="File Display Mode", description="Whether the full file tree sould be drawn instead of just modified files as a flat list", items=[ - ('FLAT', "Modifications", "Display only modified files as a flat list", 'PRESET', 0), - ('TREE', "Tree View", "Display the full tree of the entire repository", 'OUTLINER', 1), + ('FLAT', "Changes", "Display only modified files as a flat list", 'PRESET', 0), + ('TREE', "File Tree", "Display the full tree of the entire repository", 'OUTLINER', 1), ], update=update_tree_display ) diff --git a/scripts-blender/addons/blender_svn/threaded/svn_status.py b/scripts-blender/addons/blender_svn/threaded/svn_status.py index 7d9976d8..49e14629 100644 --- a/scripts-blender/addons/blender_svn/threaded/svn_status.py +++ b/scripts-blender/addons/blender_svn/threaded/svn_status.py @@ -179,7 +179,7 @@ class BGP_SVN_List(BackgroundProcess): def acquire_output(self, context, prefs): self.output = execute_svn_command( context, - ["svn", "list", "--recursive", "--xml"], + ["svn", "list", "--recursive", "--xml", "-r", "HEAD"], use_cred=True, ) @@ -296,6 +296,8 @@ def update_file_list_svn_status(context, file_statuses: Dict[str, Tuple[str, str "\nUpdating log...\n", ) Processes.start('Log') + + if any([not f.is_dir and not f.file_size_KiB for f in repo.external_files]): Processes.start('List') # Remove file entries who no longer seem to have an SVN status. @@ -372,6 +374,9 @@ def update_file_list_svn_list(context, svn_list_str: str) -> Dict: if kind == 'file': file_entry.file_size_KiB = float(file_info['size']) / 1024.0 + # Calculate size of folders. + repo.calculate_folder_sizes() + @bpy.app.handlers.persistent def mark_current_file_as_modified(_dummy1=None, _dummy2=None): diff --git a/scripts-blender/addons/blender_svn/ui/ui_file_list.py b/scripts-blender/addons/blender_svn/ui/ui_file_list.py index 5ebc7ce5..568d680a 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_file_list.py +++ b/scripts-blender/addons/blender_svn/ui/ui_file_list.py @@ -5,12 +5,28 @@ import time import bpy from bpy.types import UIList -from bpy.props import BoolProperty from .. import constants from ..util import get_addon_prefs, dots from ..threaded.background_process import Processes +def file_list_ui_split(layout, context): + repo = context.scene.svn.get_repo(context) + main_split = layout.split(factor=0.9) + left_side = main_split.row() + + filepath_ui = left_side.row() + if repo.show_file_size: + filesize_ui = layout.row() + filesize_ui.alignment='RIGHT' + else: + filesize_ui = None + status_ui = layout.row(align=True) + status_ui.alignment='RIGHT' + ops_ui = layout.row(align=True) + + return filepath_ui, filesize_ui, status_ui, ops_ui + class SVN_UL_file_list(UIList): # Value that indicates that this item has passed the filter process successfully. See rna_ui.c. @@ -26,20 +42,13 @@ class SVN_UL_file_list(UIList): file_entry = item prefs = get_addon_prefs(context) - main_row = layout.row() - split = main_row.split(factor=0.6) - filepath_ui = split.row() - split = split.split(factor=0.4) - status_ui = split.row(align=True) - - ops_ui = split.row(align=True) - ops_ui.alignment = 'RIGHT' + filepath_ui, filesize_ui, status_ui, ops_ui = file_list_ui_split(layout, context) ops_ui.enabled = file_entry.status_prediction_type == 'NONE' and not prefs.is_busy if repo.display_mode == 'TREE': split = filepath_ui.split(factor=0.05 * file_entry.tree_depth + 0.00001) - split.row() + split.label() row = split.row(align=True) filepath_ui = row.row(align=True) if file_entry.has_children: @@ -53,6 +62,29 @@ class SVN_UL_file_list(UIList): else: filepath_ui.label(text=file_entry.file_name, icon=file_entry.file_icon) + if repo.show_file_size: + icon_map = { + 'KiB' : 'DECORATE', + 'MiB' : 'RADIOBUT_ON', + 'GiB' : 'SHADING_SOLID', + 'TiB' : 'SHADING_SOLID', + 'PiB' : 'SHADING_SOLID', + 'EiB' : 'SHADING_SOLID', + } + icon = 'BLANK1 ' + if file_entry.file_size_KiB == 0: + icon = 'BLANK1' + text=" " + else: + icon = icon_map[file_entry.file_size[-3:]] + text=file_entry.file_size + width = get_sidebar_width(context) + if width < 800: + text = text.replace("iB", "") + filesize_ui.label(text=text) + else: + filesize_ui.label(text=text, icon=icon) + statuses = [file_entry.status] # SVN operations ops = [] @@ -114,10 +146,16 @@ class SVN_UL_file_list(UIList): op.popup=True ops.append(op) + for i in range(max(0, 2-len(ops))): + ops_ui.label(text="", icon='BLANK1') + for op in ops: if hasattr(op, 'file_rel_path'): op.file_rel_path = file_entry.svn_path + for i in range(max(0, 2-len(statuses))): + status_ui.label(text="", icon='BLANK1') + # Populate the status icons. for status in statuses: icon = constants.SVN_STATUS_DATA[status][0] @@ -167,8 +205,6 @@ class SVN_UL_file_list(UIList): def draw_process_info(context, layout): prefs = get_addon_prefs(context) - process_message = "" - any_error = False col = layout.column() for process in Processes.processes.values(): if process.name in {'Redraw Viewport', 'Activate File'}: @@ -180,17 +216,14 @@ def draw_process_info(context, layout): warning = row.operator( 'svn.clear_error', text=f"SVN {process.name}: Error Occurred. Hover to view", icon='ERROR') warning.process_id = process.name - any_error = True - break + continue if process.is_running: message = process.get_ui_message(context) if message: message = message.replace("...", dots()) - process_message = f"SVN: {message}" + col.label(text=message) - if not any_error and process_message: - col.label(text=process_message) if prefs.debug_mode: col.label(text="Processes: " + ", ".join([p.name for p in Processes.running_processes])) @@ -204,24 +237,24 @@ class SVN_PT_filelist_options(bpy.types.Panel): def draw(self, context): layout = self.layout - - prefs = get_addon_prefs(context) - repo = context.scene.svn.get_repo(context) - - layout.label(text="Display Mode:") - layout.prop(repo, 'display_mode', text=" ", expand=True) - layout.use_property_split=True layout.use_property_decorate=False + layout.ui_units_x = 17.0 + + repo = context.scene.svn.get_repo(context) + + layout.prop(repo, 'display_mode', text="Display Mode", expand=True) file_paths = layout.row() file_paths.enabled = repo.display_mode == 'FLAT' file_paths.prop(repo, 'show_file_paths') - layout.separator() - - layout.prop(prefs, 'do_auto_updates', icon='TEMP') + layout.row().prop(repo, 'show_file_size') +def get_sidebar_width(context) -> float: + for region in context.area.regions: + if region.type == 'UI': + return region.width def draw_file_list(context, layout): prefs = get_addon_prefs(context) @@ -235,20 +268,25 @@ def draw_file_list(context, layout): row.label(text="Repository is not authenticated.", icon='ERROR') return - main_col = layout.column() - main_row = main_col.row() - split = main_row.split(factor=0.6) - filepath_row = split.row() - filepath_row.label(text=" Filepath") + width = get_sidebar_width(context) + layout.label(text=str(width)) - status_row = split.row() - status_row.label(text=" Status") + top_row = layout.row() + box = top_row.box().row() + filepath_ui, filesize_ui, status_ui, ops_ui = file_list_ui_split(box, context) + filepath_ui.alignment='CENTER' + filepath_ui.label(text="File") - ops_row = main_row.row() - ops_row.alignment = 'RIGHT' - ops_row.label(text="Operations") + if repo.show_file_size: + filesize_ui.label(text="Size") + status_ui.label(text="Status") - row = main_col.row() + ops_ui.alignment = 'RIGHT' + ops_ui.label(text="Actions") + + top_row.column().prop(prefs, 'do_auto_updates', text="", icon='TEMP') + + row = layout.row() row.template_list( "SVN_UL_file_list", "svn_file_list", diff --git a/scripts-blender/addons/blender_svn/ui/ui_sidebar.py b/scripts-blender/addons/blender_svn/ui/ui_sidebar.py index a0e47123..2ddd3bd5 100644 --- a/scripts-blender/addons/blender_svn/ui/ui_sidebar.py +++ b/scripts-blender/addons/blender_svn/ui/ui_sidebar.py @@ -53,8 +53,8 @@ class VIEW3D_PT_svn_files(Panel): layout.use_property_split = True layout.use_property_decorate = False - draw_process_info(context, layout) draw_file_list(context, layout) + draw_process_info(context, layout) registry = [ -- 2.30.2