SVN: UX improvements #136
@ -20,7 +20,7 @@ class SVN_Operator:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def update_file_list(context):
|
def update_file_list(context):
|
||||||
repo = context.scene.svn.get_repo(context)
|
repo = context.scene.svn.get_repo(context)
|
||||||
repo.update_file_filter(context)
|
repo.refresh_ui_lists(context)
|
||||||
|
|
||||||
def execute_svn_command(self, context, command: List[str], use_cred=False) -> str:
|
def execute_svn_command(self, context, command: List[str], use_cred=False) -> str:
|
||||||
# Since a status update might already be being requested when an SVN operator is run,
|
# Since a status update might already be being requested when an SVN operator is run,
|
||||||
@ -51,7 +51,6 @@ class SVN_Operator_Single_File(SVN_Operator):
|
|||||||
ret = self._execute(context)
|
ret = self._execute(context)
|
||||||
|
|
||||||
file = self.get_file(context)
|
file = self.get_file(context)
|
||||||
if file:
|
|
||||||
Processes.start('Status')
|
Processes.start('Status')
|
||||||
redraw_viewport()
|
redraw_viewport()
|
||||||
|
|
||||||
@ -107,7 +106,7 @@ class May_Modifiy_Current_Blend(SVN_Operator_Single_File, Warning_Operator):
|
|||||||
return current_blend and current_blend.svn_path == self.file_rel_path
|
return current_blend and current_blend.svn_path == self.file_rel_path
|
||||||
|
|
||||||
reload_file: BoolProperty(
|
reload_file: BoolProperty(
|
||||||
name="Reload File",
|
name="Reload File (Keep UI)",
|
||||||
description="Reload the file after the operation is completed. The UI layout will be preserved",
|
description="Reload the file after the operation is completed. The UI layout will be preserved",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
@ -305,6 +304,7 @@ class SVN_OT_trash_file(SVN_Operator_Single_File, Warning_Operator, Operator):
|
|||||||
bl_options = {'INTERNAL'}
|
bl_options = {'INTERNAL'}
|
||||||
|
|
||||||
file_rel_path: StringProperty()
|
file_rel_path: StringProperty()
|
||||||
|
missing_file_allowed = False
|
||||||
|
|
||||||
def get_warning_text(self, context):
|
def get_warning_text(self, context):
|
||||||
return "Are you sure you want to move this file to the recycle bin?\n " + self.file_rel_path
|
return "Are you sure you want to move this file to the recycle bin?\n " + self.file_rel_path
|
||||||
|
@ -444,15 +444,15 @@ class SVN_repository(PropertyGroup):
|
|||||||
"""When user clicks on a different file, the latest log entry of that file
|
"""When user clicks on a different file, the latest log entry of that file
|
||||||
should become the active log entry."""
|
should become the active log entry."""
|
||||||
|
|
||||||
latest_idx = self.get_latest_revision_of_file(
|
latest_rev = self.get_latest_revision_of_file(
|
||||||
self.active_file.svn_path)
|
self.active_file.svn_path)
|
||||||
# SVN Revisions are not 0-indexed, so we need to subtract 1.
|
# SVN Revisions are not 0-indexed, so we need to subtract 1.
|
||||||
self.log_active_index = latest_idx-1
|
self.log_active_index = latest_rev-1
|
||||||
|
|
||||||
space = context.space_data
|
space = context.space_data
|
||||||
if space and space.type == 'FILE_BROWSER':
|
if space and space.type == 'FILE_BROWSER':
|
||||||
# Set the active file in the file browser to whatever was selected in the SVN Files panel.
|
# Set the active file in the file browser to whatever was selected in the SVN Files panel.
|
||||||
self.log_active_index_filebrowser = latest_idx-1
|
self.log_active_index_filebrowser = latest_rev-1
|
||||||
|
|
||||||
space.params.directory = self.active_file.absolute_path.parent.as_posix().encode()
|
space.params.directory = self.active_file.absolute_path.parent.as_posix().encode()
|
||||||
space.params.filename = self.active_file.name.encode()
|
space.params.filename = self.active_file.name.encode()
|
||||||
@ -462,7 +462,7 @@ class SVN_repository(PropertyGroup):
|
|||||||
relative_path=self.active_file.name)
|
relative_path=self.active_file.name)
|
||||||
Processes.start('Activate File')
|
Processes.start('Activate File')
|
||||||
|
|
||||||
# Filter out log entries that did not affect the selected file.
|
# Set the filter flag of the log entries based on whether they affect the active file or not.
|
||||||
self.log.foreach_set(
|
self.log.foreach_set(
|
||||||
'affects_active_file',
|
'affects_active_file',
|
||||||
[log_entry.changes_file(self.active_file)
|
[log_entry.changes_file(self.active_file)
|
||||||
@ -513,28 +513,10 @@ class SVN_repository(PropertyGroup):
|
|||||||
return self.get_file_by_absolute_path(bpy.data.filepath)
|
return self.get_file_by_absolute_path(bpy.data.filepath)
|
||||||
|
|
||||||
### File List UIList filter properties ###
|
### File List UIList filter properties ###
|
||||||
# Filtering properties are normally stored on the UIList,
|
def refresh_ui_lists(self, context):
|
||||||
# but then they cannot be accessed from anywhere else,
|
"""Refresh the file UI list based on filter settings.
|
||||||
# since template_list() does not return the UIList instance.
|
Also triggers a refresh of the SVN UIList, through the update callback of
|
||||||
# We need to be able to access them outside of drawing code, to be able to
|
external_files_active_index."""
|
||||||
# ensure that a filtered out entry can never be the active one.
|
|
||||||
|
|
||||||
def force_good_active_index(self, context) -> bool:
|
|
||||||
"""
|
|
||||||
We want to avoid having the active file entry be invisible due to filtering.
|
|
||||||
If the active element is being filtered out, set the active element to
|
|
||||||
something that is visible.
|
|
||||||
"""
|
|
||||||
if len(self.external_files) == 0:
|
|
||||||
return
|
|
||||||
if not self.active_file.show_in_filelist:
|
|
||||||
for i, file in enumerate(self.external_files):
|
|
||||||
if file.show_in_filelist:
|
|
||||||
self.external_files_active_index = i
|
|
||||||
return
|
|
||||||
|
|
||||||
def update_file_filter(self, context):
|
|
||||||
"""Should run when any of the SVN file list search filters are changed."""
|
|
||||||
|
|
||||||
UI_LIST = bpy.types.UI_UL_list
|
UI_LIST = bpy.types.UI_UL_list
|
||||||
if self.file_search_filter:
|
if self.file_search_filter:
|
||||||
@ -555,12 +537,24 @@ class SVN_repository(PropertyGroup):
|
|||||||
|
|
||||||
file.show_in_filelist = not file.has_default_status
|
file.show_in_filelist = not file.has_default_status
|
||||||
|
|
||||||
self.force_good_active_index(context)
|
if len(self.external_files) == 0:
|
||||||
|
return
|
||||||
|
if self.active_file.show_in_filelist:
|
||||||
|
# Trigger the update callback, because that refreshes the SVN Log UI.
|
||||||
|
self.external_files_active_index = self.external_files_active_index
|
||||||
|
return
|
||||||
|
|
||||||
|
# Make sure the active file isn't now being filtered out.
|
||||||
|
# If it is, change the active file to the first visible one.
|
||||||
|
for i, file in enumerate(self.external_files):
|
||||||
|
if file.show_in_filelist:
|
||||||
|
self.external_files_active_index = i
|
||||||
|
return
|
||||||
|
|
||||||
file_search_filter: StringProperty(
|
file_search_filter: StringProperty(
|
||||||
name="Search Filter",
|
name="Search Filter",
|
||||||
description="Only show entries that contain this string",
|
description="Only show entries that contain this string",
|
||||||
update=update_file_filter
|
update=refresh_ui_lists
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -277,8 +277,7 @@ def update_file_list(context, file_statuses: Dict[str, Tuple[str, str, int]]):
|
|||||||
if file_entry.svn_path not in svn_paths:
|
if file_entry.svn_path not in svn_paths:
|
||||||
repo.remove_file_entry(file_entry)
|
repo.remove_file_entry(file_entry)
|
||||||
|
|
||||||
repo.update_file_filter(context)
|
repo.refresh_ui_lists(context)
|
||||||
repo.force_good_active_index(context)
|
|
||||||
|
|
||||||
|
|
||||||
def get_repo_file_statuses(svn_status_str: str) -> Dict[str, Tuple[str, str, int]]:
|
def get_repo_file_statuses(svn_status_str: str) -> Dict[str, Tuple[str, str, int]]:
|
||||||
|
@ -3,26 +3,10 @@
|
|||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Context, UIList, Operator
|
from bpy.types import Context, UIList, Operator
|
||||||
from bpy.props import StringProperty
|
from bpy.props import StringProperty, BoolProperty
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
class SVN_OT_open_blend_file(Operator):
|
|
||||||
# This is needed because drawing a button for wm.open_mainfile in the UI
|
|
||||||
# directly simply does not work; Blender just opens a full-screen filebrowser,
|
|
||||||
# instead of opening the .blend file. Probably a bug.
|
|
||||||
bl_idname = "svn.open_blend_file"
|
|
||||||
bl_label = "Open Blend File"
|
|
||||||
bl_description = "Open Blend File"
|
|
||||||
bl_options = {'INTERNAL'}
|
|
||||||
|
|
||||||
filepath: StringProperty()
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
bpy.ops.wm.open_mainfile(filepath=self.filepath, load_ui=False)
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
|
|
||||||
def check_context_match(context: Context, uilayout_type: str, bl_idname: str) -> bool:
|
def check_context_match(context: Context, uilayout_type: str, bl_idname: str) -> bool:
|
||||||
"""For example, when right-clicking on a UIList, the uilayout_type will
|
"""For example, when right-clicking on a UIList, the uilayout_type will
|
||||||
be `ui_list` and the bl_idname is that of the UIList being right-clicked.
|
be `ui_list` and the bl_idname is that of the UIList being right-clicked.
|
||||||
@ -39,8 +23,17 @@ def svn_file_list_context_menu(self: UIList, context: Context) -> None:
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
active_file = context.scene.svn.get_repo(context).active_file
|
active_file = context.scene.svn.get_repo(context).active_file
|
||||||
if active_file.name.endswith("blend"):
|
if active_file.name.endswith("blend"):
|
||||||
layout.operator("svn.open_blend_file",
|
op = layout.operator("wm.open_mainfile",
|
||||||
text=f"Open {active_file.name}").filepath = active_file.absolute_path
|
text=f"Open {active_file.name}")
|
||||||
|
op.filepath = active_file.absolute_path
|
||||||
|
op.display_file_selector = False
|
||||||
|
op.load_ui = True
|
||||||
|
op = layout.operator("wm.open_mainfile",
|
||||||
|
text=f"Open {active_file.name} (Keep UI)")
|
||||||
|
op.filepath = active_file.absolute_path
|
||||||
|
op.display_file_selector = False
|
||||||
|
op.load_ui = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
layout.operator("wm.path_open",
|
layout.operator("wm.path_open",
|
||||||
text=f"Open {active_file.name}").filepath = str(Path(active_file.absolute_path))
|
text=f"Open {active_file.name}").filepath = str(Path(active_file.absolute_path))
|
||||||
@ -73,5 +66,3 @@ def unregister():
|
|||||||
bpy.types.UI_MT_list_item_context_menu.remove(svn_file_list_context_menu)
|
bpy.types.UI_MT_list_item_context_menu.remove(svn_file_list_context_menu)
|
||||||
bpy.types.UI_MT_list_item_context_menu.remove(svn_log_list_context_menu)
|
bpy.types.UI_MT_list_item_context_menu.remove(svn_log_list_context_menu)
|
||||||
|
|
||||||
|
|
||||||
registry = [SVN_OT_open_blend_file]
|
|
||||||
|
@ -34,8 +34,8 @@ def draw_outdated_file_warning(self, context):
|
|||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bpy.types.VIEW3D_HT_header.prepend(draw_outdated_file_warning)
|
bpy.types.TOPBAR_MT_editor_menus.append(draw_outdated_file_warning)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
bpy.types.VIEW3D_HT_header.remove(draw_outdated_file_warning)
|
bpy.types.TOPBAR_MT_editor_menus.remove(draw_outdated_file_warning)
|
||||||
|
Loading…
Reference in New Issue
Block a user