Outliner: Synced selection and active element highlighting
authorNathan Craddock <nzcraddock@gmail.com>
Thu, 8 Aug 2019 04:27:07 +0000 (22:27 -0600)
committerNathan Craddock <nzcraddock@gmail.com>
Fri, 16 Aug 2019 18:30:53 +0000 (12:30 -0600)
Adds a toggle to the filter menu for outliner synced selection. Enabled
by default, this ensures selection is synced between objects, bones, and
sequences. An active outliner element theme color is added to indicate
which element is active.

Synced selection is controlled on the operator level. Each operator
that modifies selection for objects, bones, sequences, or outliner
elements needs to call the respective ED_outliner_select_sync_from..
function to tag outliners to be synced.

Syncing is done lazily on outliner draw.

26 files changed:
release/datafiles/userdef/userdef_default_theme.c
release/scripts/presets/interface_theme/blender_light.xml
release/scripts/startup/bl_ui/space_outliner.py
source/blender/blenloader/intern/versioning_280.c
source/blender/blenloader/intern/versioning_userdef.c
source/blender/editors/armature/armature_select.c
source/blender/editors/armature/pose_select.c
source/blender/editors/include/ED_outliner.h
source/blender/editors/include/UI_resources.h
source/blender/editors/interface/resources.c
source/blender/editors/object/object_add.c
source/blender/editors/object/object_select.c
source/blender/editors/space_outliner/CMakeLists.txt
source/blender/editors/space_outliner/outliner_draw.c
source/blender/editors/space_outliner/outliner_edit.c
source/blender/editors/space_outliner/outliner_intern.h
source/blender/editors/space_outliner/outliner_select.c
source/blender/editors/space_outliner/outliner_sync.c [new file with mode: 0644]
source/blender/editors/space_outliner/space_outliner.c
source/blender/editors/space_sequencer/sequencer_select.c
source/blender/editors/space_view3d/view3d_select.c
source/blender/makesdna/DNA_outliner_types.h
source/blender/makesdna/DNA_space_types.h
source/blender/makesdna/DNA_windowmanager_types.h
source/blender/makesrna/intern/rna_space.c
source/blender/makesrna/intern/rna_userdef.c

index 8611a9ae88112db1f3f4fa8fe7a0a07c98b28d96..9f037fa4793a0b5335d3a880673b5526c5a6bef6 100644 (file)
@@ -748,7 +748,8 @@ const bTheme U_theme_default = {
     .outline_width = 1,
     .facedot_size = 4,
     .match = RGBA(0x337f334c),
-    .selected_highlight = RGBA(0x314e784c),
+    .selected_highlight = RGBA(0x223a5bff),
+    .active = RGBA(0x3b5689ff),
     .selected_object = RGBA(0xe96a00ff),
     .active_object = RGBA(0xffaf29ff),
     .edited_object = RGBA(0x00806266),
index dc0bb6b629e31e06b49f9968b3303f80235ff707..49b01ec330908b40da4e2d8815630fb7f442409a 100644 (file)
       <ThemeOutliner
         match="#337f33"
         selected_highlight="#7a8e99"
+        active="#92aab7"
         selected_object="#ffddb3"
         active_object="#ffffff"
         edited_object="#0080624d"
index 6832e3d463b4a9f37286e95ad5f46dcdde005622..7bf203d8e39a828af0e2ceff70e8cf6cff844a74 100644 (file)
@@ -46,6 +46,10 @@ class OUTLINER_HT_header(Header):
 
         layout.separator_spacer()
 
+        if display_mode == 'SEQUENCE':
+            row = layout.row(align=True)
+            row.prop(space, "use_sync_select", icon="UV_SYNC_SELECT", text="")
+
         row = layout.row(align=True)
         if display_mode in {'SCENES', 'VIEW_LAYER'}:
             row.popover(
@@ -328,6 +332,10 @@ class OUTLINER_PT_filter(Panel):
             col.prop(space, "use_sort_alpha")
             layout.separator()
 
+        row = layout.row(align=True)
+        row.prop(space, "use_sync_select", text="Sync Selection")
+        layout.separator()
+
         col = layout.column(align=True)
         col.label(text="Search:")
         col.prop(space, "use_filter_complete", text="Exact Match")
index c3706f375fe915943650d3e5cea5b3d169048b80..1d993185ac7638772f72b0d1dfc5dcd56a9dfc6c 100644 (file)
@@ -3644,6 +3644,19 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
     LISTBASE_FOREACH (bArmature *, arm, &bmain->armatures) {
       arm->flag &= ~(ARM_FLAG_UNUSED_6);
     }
+
+    /* Marks each outliner as dirty so a sync will occur as an outliner draws. */
+    for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
+      for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
+        for (SpaceLink *space = sa->spacedata.first; space; space = space->next) {
+          if (space->spacetype == SPACE_OUTLINER) {
+            SpaceOutliner *soutliner = (SpaceOutliner *)space;
+            soutliner->sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_ALL;
+            soutliner->flag |= SO_SYNC_SELECT;
+          }
+        }
+      }
+    }
   }
 
   if (!MAIN_VERSION_ATLEAST(bmain, 281, 1)) {
index e987a623d0b221880f6449136efc95c03452c6d2..b01d2765963a44c15ce7ee4af034dc53b0193568 100644 (file)
@@ -141,6 +141,9 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
     FROM_DEFAULT_V4_UCHAR(space_outliner.row_alternate);
   }
 
+  FROM_DEFAULT_V4_UCHAR(space_outliner.selected_highlight);
+  FROM_DEFAULT_V4_UCHAR(space_outliner.active);
+
   /**
    * Include next version bump.
    */
index 23ddf77e63dbac36292f5cca53bb721da688d536..eff621d7b71645b85dc0600880992e01e8bb400b 100644 (file)
@@ -46,6 +46,7 @@
 
 #include "ED_armature.h"
 #include "ED_object.h"
+#include "ED_outliner.h"
 #include "ED_screen.h"
 #include "ED_select_utils.h"
 #include "ED_view3d.h"
@@ -356,6 +357,8 @@ static int armature_select_linked_invoke(bContext *C, wmOperator *op, const wmEv
     }
   }
 
+  ED_outliner_select_sync_from_edit_bone_tag(C);
+
   ED_armature_edit_sync_selection(arm->edbo);
 
   WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, base->object);
@@ -1027,6 +1030,8 @@ static int armature_de_select_all_exec(bContext *C, wmOperator *op)
   }
   CTX_DATA_END;
 
+  ED_outliner_select_sync_from_edit_bone_tag(C);
+
   WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, NULL);
 
   return OPERATOR_FINISHED;
@@ -1148,6 +1153,8 @@ static int armature_de_select_more_exec(bContext *C, wmOperator *UNUSED(op))
     WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
   }
   MEM_freeN(objects);
+
+  ED_outliner_select_sync_from_edit_bone_tag(C);
   return OPERATOR_FINISHED;
 }
 
@@ -1178,6 +1185,8 @@ static int armature_de_select_less_exec(bContext *C, wmOperator *UNUSED(op))
     WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
   }
   MEM_freeN(objects);
+
+  ED_outliner_select_sync_from_edit_bone_tag(C);
   return OPERATOR_FINISHED;
 }
 
@@ -1569,6 +1578,8 @@ static int armature_select_similar_exec(bContext *C, wmOperator *op)
 
 #undef STRUCT_SIZE_AND_OFFSET
 
+  ED_outliner_select_sync_from_edit_bone_tag(C);
+
   return OPERATOR_FINISHED;
 }
 
@@ -1663,6 +1674,8 @@ static int armature_select_hierarchy_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
+  ED_outliner_select_sync_from_edit_bone_tag(C);
+
   ED_armature_edit_sync_selection(arm->edbo);
 
   WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
@@ -1748,6 +1761,8 @@ static int armature_select_mirror_exec(bContext *C, wmOperator *op)
       arm->act_edbone = ebone_mirror_act;
     }
 
+    ED_outliner_select_sync_from_edit_bone_tag(C);
+
     ED_armature_edit_sync_selection(arm->edbo);
 
     WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, ob);
@@ -1876,6 +1891,7 @@ static int armature_shortest_path_pick_invoke(bContext *C, wmOperator *op, const
 
   if (changed) {
     arm->act_edbone = ebone_dst;
+    ED_outliner_select_sync_from_edit_bone_tag(C);
     ED_armature_edit_sync_selection(arm->edbo);
     WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, obedit);
 
index 8434fee6e78b365c423004796d93e8b6a419f7a2..beec2f8358f6aa31178dbdbbd4004bfd8b57b421 100644 (file)
@@ -54,6 +54,7 @@
 #include "ED_keyframing.h"
 #include "ED_mesh.h"
 #include "ED_object.h"
+#include "ED_outliner.h"
 #include "ED_screen.h"
 #include "ED_select_utils.h"
 #include "ED_view3d.h"
@@ -449,6 +450,8 @@ static int pose_select_connected_invoke(bContext *C, wmOperator *op, const wmEve
     selectconnected_posebonechildren(base->object, curBone, extend);
   }
 
+  ED_outliner_select_sync_from_pose_bone_tag(C);
+
   ED_pose_bone_select_tag_update(base->object);
 
   return OPERATOR_FINISHED;
@@ -514,6 +517,8 @@ static int pose_de_select_all_exec(bContext *C, wmOperator *op)
   }
   CTX_DATA_END;
 
+  ED_outliner_select_sync_from_pose_bone_tag(C);
+
   WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, NULL);
 
   return OPERATOR_FINISHED;
@@ -560,6 +565,8 @@ static int pose_select_parent_exec(bContext *C, wmOperator *UNUSED(op))
     return OPERATOR_CANCELLED;
   }
 
+  ED_outliner_select_sync_from_pose_bone_tag(C);
+
   ED_pose_bone_select_tag_update(ob);
   return OPERATOR_FINISHED;
 }
@@ -624,6 +631,8 @@ static int pose_select_constraint_target_exec(bContext *C, wmOperator *UNUSED(op
     return OPERATOR_CANCELLED;
   }
 
+  ED_outliner_select_sync_from_pose_bone_tag(C);
+
   return OPERATOR_FINISHED;
 }
 
@@ -712,6 +721,8 @@ static int pose_select_hierarchy_exec(bContext *C, wmOperator *op)
     return OPERATOR_CANCELLED;
   }
 
+  ED_outliner_select_sync_from_pose_bone_tag(C);
+
   ED_pose_bone_select_tag_update(ob);
 
   return OPERATOR_FINISHED;
@@ -1061,6 +1072,8 @@ static int pose_select_grouped_exec(bContext *C, wmOperator *op)
 
   /* report done status */
   if (changed) {
+    ED_outliner_select_sync_from_pose_bone_tag(C);
+
     return OPERATOR_FINISHED;
   }
   else {
@@ -1172,6 +1185,8 @@ static int pose_select_mirror_exec(bContext *C, wmOperator *op)
   }
   MEM_freeN(objects);
 
+  ED_outliner_select_sync_from_pose_bone_tag(C);
+
   return OPERATOR_FINISHED;
 }
 
index e94aedc2b2bd29e685461ec120c2721abad73849..a28b1b8483a27c533503b9e37e7dd977c6ade293 100644 (file)
@@ -30,4 +30,15 @@ bool ED_outliner_collections_editor_poll(struct bContext *C);
 
 void ED_outliner_selected_objects_get(const struct bContext *C, struct ListBase *objects);
 
+void ED_outliner_select_sync_from_object_tag(struct bContext *C);
+void ED_outliner_select_sync_from_edit_bone_tag(struct bContext *C);
+void ED_outliner_select_sync_from_pose_bone_tag(struct bContext *C);
+void ED_outliner_select_sync_from_sequence_tag(struct bContext *C);
+
+bool ED_outliner_select_sync_is_dirty(const struct bContext *C);
+
+void ED_outliner_select_sync_from_outliner(struct bContext *C, struct SpaceOutliner *soops);
+
+void ED_outliner_select_sync_flag_outliners(const struct bContext *C);
+
 #endif /*  __ED_OUTLINER_H__ */
index 3b080b6df95c96acec9479c9b2bac884e2ed4570..29022adac6ce134612bdf640460cc1bef7f549ec 100644 (file)
@@ -255,6 +255,7 @@ typedef enum ThemeColorID {
 
   TH_MATCH,            /* highlight color for search matches */
   TH_SELECT_HIGHLIGHT, /* highlight color for selected outliner item */
+  TH_SELECT_ACTIVE,    /* highlight color for active outliner item */
   TH_SELECTED_OBJECT,  /* selected object color for outliner */
   TH_ACTIVE_OBJECT,    /* active object color for outliner */
   TH_EDITED_OBJECT,    /* edited object color for outliner */
index c31de60c7edb73805e5acf0c44737a0a7859aa2a..7c5d5401d0870ac213ab73eecc6ec165d88b7d1a 100644 (file)
@@ -794,6 +794,10 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
           cp = ts->selected_highlight;
           break;
 
+        case TH_SELECT_ACTIVE:
+          cp = ts->active;
+          break;
+
         case TH_SELECTED_OBJECT:
           cp = ts->selected_object;
           break;
index 7e031866dec99f0ea2e26cb4b4378d9d26b48b4f..80d150506ad04cbd29e595aa605bb82ff868234b 100644 (file)
 #include "ED_mesh.h"
 #include "ED_node.h"
 #include "ED_object.h"
+#include "ED_outliner.h"
 #include "ED_physics.h"
 #include "ED_render.h"
 #include "ED_screen.h"
@@ -502,6 +503,8 @@ Object *ED_object_add_type(bContext *C,
   /* TODO(sergey): Use proper flag for tagging here. */
   DEG_id_tag_update(&scene->id, 0);
 
+  ED_outliner_select_sync_from_object_tag(C);
+
   return ob;
 }
 
index da06707ebac3c9ee8403e8c9f895c1b0236598e9..28242b986f152f18b31d1031ad19b13c0a53d22b 100644 (file)
@@ -69,6 +69,7 @@
 
 #include "ED_armature.h"
 #include "ED_object.h"
+#include "ED_outliner.h"
 #include "ED_screen.h"
 #include "ED_select_utils.h"
 #include "ED_keyframing.h"
@@ -436,6 +437,8 @@ static int object_select_by_type_exec(bContext *C, wmOperator *op)
   DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
   WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
 
+  ED_outliner_select_sync_from_object_tag(C);
+
   return OPERATOR_FINISHED;
 }
 
@@ -717,6 +720,7 @@ static int object_select_linked_exec(bContext *C, wmOperator *op)
   if (changed) {
     DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
     WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+    ED_outliner_select_sync_from_object_tag(C);
     return OPERATOR_FINISHED;
   }
 
@@ -1100,6 +1104,7 @@ static int object_select_grouped_exec(bContext *C, wmOperator *op)
   if (changed) {
     DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
     WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+    ED_outliner_select_sync_from_object_tag(C);
     return OPERATOR_FINISHED;
   }
 
@@ -1150,6 +1155,8 @@ static int object_select_all_exec(bContext *C, wmOperator *op)
     DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
     WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
 
+    ED_outliner_select_sync_from_object_tag(C);
+
     return OPERATOR_FINISHED;
   }
   else if (any_visible == false) {
@@ -1218,6 +1225,8 @@ static int object_select_same_collection_exec(bContext *C, wmOperator *op)
   DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
   WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
 
+  ED_outliner_select_sync_from_object_tag(C);
+
   return OPERATOR_FINISHED;
 }
 
@@ -1281,6 +1290,8 @@ static int object_select_mirror_exec(bContext *C, wmOperator *op)
   DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
   WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
 
+  ED_outliner_select_sync_from_object_tag(C);
+
   return OPERATOR_FINISHED;
 }
 
@@ -1369,6 +1380,9 @@ static int object_select_more_exec(bContext *C, wmOperator *UNUSED(op))
     Scene *scene = CTX_data_scene(C);
     DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
     WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+
+    ED_outliner_select_sync_from_object_tag(C);
+
     return OPERATOR_FINISHED;
   }
   else {
@@ -1399,6 +1413,9 @@ static int object_select_less_exec(bContext *C, wmOperator *UNUSED(op))
     Scene *scene = CTX_data_scene(C);
     DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
     WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+
+    ED_outliner_select_sync_from_object_tag(C);
+
     return OPERATOR_FINISHED;
   }
   else {
@@ -1448,6 +1465,8 @@ static int object_select_random_exec(bContext *C, wmOperator *op)
   DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
   WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
 
+  ED_outliner_select_sync_from_object_tag(C);
+
   return OPERATOR_FINISHED;
 }
 
index d235dd47136ac8570a3b4587ff9ccce0b55418a8..616915dbc2ce4aa9f232b54fdddc2fb3dd2682d7 100644 (file)
@@ -41,6 +41,7 @@ set(SRC
   outliner_edit.c
   outliner_ops.c
   outliner_select.c
+  outliner_sync.c
   outliner_tools.c
   outliner_tree.c
   outliner_utils.c
index e4881a6f13ddeb1adc0b14544bcf8a8071078a12..cab8e45b827f05bdb4f677b4d318dbfbbe88052b 100644 (file)
@@ -60,6 +60,7 @@
 #include "ED_armature.h"
 #include "ED_keyframing.h"
 #include "ED_object.h"
+#include "ED_outliner.h"
 #include "ED_screen.h"
 
 #include "WM_api.h"
@@ -3193,6 +3194,7 @@ static void outliner_draw_highlights_recursive(unsigned pos,
                                                const SpaceOutliner *soops,
                                                const ListBase *lb,
                                                const float col_selection[4],
+                                               const float col_active[4],
                                                const float col_highlight[4],
                                                const float col_searchmatch[4],
                                                int start_x,
@@ -3206,7 +3208,11 @@ static void outliner_draw_highlights_recursive(unsigned pos,
     const int start_y = *io_start_y;
 
     /* selection status */
-    if (tselem->flag & TSE_SELECTED) {
+    if ((tselem->flag & TSE_ACTIVE) && (tselem->flag & TSE_SELECTED)) {
+      immUniformColor4fv(col_active);
+      immRecti(pos, 0, start_y, (int)ar->v2d.cur.xmax, start_y + UI_UNIT_Y);
+    }
+    else if (tselem->flag & TSE_SELECTED) {
       immUniformColor4fv(col_selection);
       immRecti(pos, 0, start_y, (int)ar->v2d.cur.xmax, start_y + UI_UNIT_Y);
     }
@@ -3260,6 +3266,7 @@ static void outliner_draw_highlights_recursive(unsigned pos,
                                          soops,
                                          &te->subtree,
                                          col_selection,
+                                         col_active,
                                          col_highlight,
                                          col_searchmatch,
                                          start_x + UI_UNIT_X,
@@ -3271,10 +3278,12 @@ static void outliner_draw_highlights_recursive(unsigned pos,
 static void outliner_draw_highlights(ARegion *ar, SpaceOutliner *soops, int startx, int *starty)
 {
   const float col_highlight[4] = {1.0f, 1.0f, 1.0f, 0.13f};
-  float col_selection[4], col_searchmatch[4];
+  float col_selection[4], col_active[4], col_searchmatch[4];
 
   UI_GetThemeColor3fv(TH_SELECT_HIGHLIGHT, col_selection);
   col_selection[3] = 1.0f; /* no alpha */
+  UI_GetThemeColor3fv(TH_SELECT_ACTIVE, col_active);
+  col_active[3] = 1.0f; /* no alpha */
   UI_GetThemeColor4fv(TH_MATCH, col_searchmatch);
   col_searchmatch[3] = 0.5f;
 
@@ -3282,8 +3291,16 @@ static void outliner_draw_highlights(ARegion *ar, SpaceOutliner *soops, int star
   GPUVertFormat *format = immVertexFormat();
   uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_I32, 2, GPU_FETCH_INT_TO_FLOAT);
   immBindBuiltinProgram(GPU_SHADER_2D_UNIFORM_COLOR);
-  outliner_draw_highlights_recursive(
-      pos, ar, soops, &soops->tree, col_selection, col_highlight, col_searchmatch, startx, starty);
+  outliner_draw_highlights_recursive(pos,
+                                     ar,
+                                     soops,
+                                     &soops->tree,
+                                     col_selection,
+                                     col_active,
+                                     col_highlight,
+                                     col_searchmatch,
+                                     startx,
+                                     starty);
   immUnbindProgram();
   GPU_blend(false);
 }
@@ -3439,6 +3456,17 @@ void draw_outliner(const bContext *C)
 
   outliner_build_tree(mainvar, scene, view_layer, soops, ar);  // always
 
+  /* If global sync select is dirty, flag other outliners */
+  if (ED_outliner_select_sync_is_dirty(C)) {
+    ED_outliner_select_sync_flag_outliners(C);
+  }
+
+  /* Sync selection state from view layer */
+  if (!ELEM(soops->outlinevis, SO_LIBRARIES, SO_DATA_API, SO_ID_ORPHANS) &&
+      soops->flag & SO_SYNC_SELECT) {
+    outliner_sync_selection(C, soops);
+  }
+
   /* force display to pixel coords */
   v2d->flag |= (V2D_PIXELOFS_X | V2D_PIXELOFS_Y);
   /* set matrix for 2d-view controls */
index de6e89e47c4230b6c036aa74ac1f678b6ba5119e..57fea544c774b8420adf67a85551f4d2c6d4ec11 100644 (file)
@@ -1103,6 +1103,10 @@ static int outliner_select_all_exec(bContext *C, wmOperator *op)
       break;
   }
 
+  if (soops->flag & SO_SYNC_SELECT) {
+    ED_outliner_select_sync_from_outliner(C, soops);
+  }
+
   DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
   WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
   ED_region_tag_redraw_no_rebuild(ar);
index fa28d119244ec400fe4cf579fab961bad3196521..c12fdcc6d1f8dc1386c97a916dacd9707915292c 100644 (file)
@@ -253,6 +253,8 @@ void outliner_object_mode_toggle(struct bContext *C,
                                  ViewLayer *view_layer,
                                  Base *base);
 
+void outliner_element_activate(struct SpaceOutliner *soops, struct TreeStoreElem *tselem);
+
 /* outliner_edit.c ---------------------------------------------- */
 typedef void (*outliner_operation_cb)(struct bContext *C,
                                       struct ReportList *,
@@ -457,4 +459,8 @@ bool outliner_tree_traverse(const SpaceOutliner *soops,
                             void *customdata);
 float outliner_restrict_columns_width(const struct SpaceOutliner *soops);
 
+/* outliner_sync.c ---------------------------------------------- */
+
+void outliner_sync_selection(const struct bContext *C, struct SpaceOutliner *soops);
+
 #endif /* __OUTLINER_INTERN_H__ */
index c932766ab93d4d707a0adfe36dc88ac9d4075c38..38dfe439b9c3f108f41c44f6850a72d56d91f832 100644 (file)
@@ -54,6 +54,7 @@
 
 #include "ED_armature.h"
 #include "ED_object.h"
+#include "ED_outliner.h"
 #include "ED_screen.h"
 #include "ED_select_utils.h"
 #include "ED_sequencer.h"
@@ -1083,6 +1084,13 @@ eOLDrawState tree_element_type_active(bContext *C,
 
 /* ================================================ */
 
+/* Activate a tree store element and set the walk navigation start element */
+void outliner_element_activate(SpaceOutliner *soops, TreeStoreElem *tselem)
+{
+  outliner_flag_set(&soops->tree, TSE_ACTIVE, false);
+  tselem->flag |= TSE_ACTIVE;
+}
+
 /**
  * Action when clicking to activate an item (typically under the mouse cursor),
  * but don't do any cursor intersection checks.
@@ -1114,7 +1122,8 @@ static void do_outliner_item_activate_tree_element(bContext *C,
   else if (tselem->type == TSE_POSE_BASE) {
     /* Support pose mode toggle, keeping the active object as is. */
   }
-  else {
+  else if (soops->flag & SO_SYNC_SELECT) {
+    /* Only activate when synced selection is enabled */
     tree_element_set_active_object(C,
                                    scene,
                                    view_layer,
@@ -1125,6 +1134,9 @@ static void do_outliner_item_activate_tree_element(bContext *C,
                                    recursive && tselem->type == 0);
   }
 
+  /* Mark as active in the outliner */
+  outliner_element_activate(soops, tselem);
+
   if (tselem->type == 0) {  // the lib blocks
     /* editmode? */
     if (te->idcode == ID_SCE) {
@@ -1189,7 +1201,7 @@ static void do_outliner_item_activate_tree_element(bContext *C,
       tree_element_active(C, scene, view_layer, soops, te, OL_SETSEL_NORMAL, false);
     }
   }
-  else {
+  else if (soops->flag & SO_SYNC_SELECT) {
     tree_element_type_active(C,
                              scene,
                              view_layer,
@@ -1211,7 +1223,8 @@ void outliner_item_select(SpaceOutliner *soops,
                           const bool toggle)
 {
   TreeStoreElem *tselem = TREESTORE(te);
-  const short new_flag = toggle ? (tselem->flag ^ TSE_SELECTED) : (tselem->flag | TSE_SELECTED);
+  const short new_flag = (toggle && (tselem->flag & TSE_ACTIVE)) ? (tselem->flag ^ TSE_SELECTED) :
+                                                                   (tselem->flag | TSE_SELECTED);
 
   if (extend == false) {
     outliner_flag_set(&soops->tree, TSE_SELECTED, false);
@@ -1318,6 +1331,10 @@ static int outliner_item_do_activate_from_cursor(bContext *C,
       ED_region_tag_redraw_no_rebuild(ar);
     }
     ED_undo_push(C, "Outliner selection change");
+
+    if (soops->flag & SO_SYNC_SELECT) {
+      ED_outliner_select_sync_from_outliner(C, soops);
+    }
   }
 
   return OPERATOR_FINISHED;
@@ -1402,6 +1419,10 @@ static int outliner_box_select_exec(bContext *C, wmOperator *op)
   WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
   ED_region_tag_redraw(ar);
 
+  if (soops->flag & SO_SYNC_SELECT) {
+    ED_outliner_select_sync_from_outliner(C, soops);
+  }
+
   return OPERATOR_FINISHED;
 }
 
diff --git a/source/blender/editors/space_outliner/outliner_sync.c b/source/blender/editors/space_outliner/outliner_sync.c
new file mode 100644 (file)
index 0000000..a8aeb7e
--- /dev/null
@@ -0,0 +1,548 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2004 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup spoutliner
+ */
+
+#include <stdio.h>
+
+#include "DNA_armature_types.h"
+#include "DNA_layer_types.h"
+#include "DNA_outliner_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_sequence_types.h"
+#include "DNA_space_types.h"
+
+#include "BLI_compiler_compat.h"
+#include "BLI_ghash.h"
+
+#include "BKE_armature.h"
+#include "BKE_context.h"
+#include "BKE_layer.h"
+#include "BKE_main.h"
+#include "BKE_sequencer.h"
+
+#include "DEG_depsgraph.h"
+
+#include "ED_armature.h"
+#include "ED_object.h"
+#include "ED_outliner.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "outliner_intern.h"
+
+/* Functions for tagging outliner selection syncing is dirty from operators */
+void ED_outliner_select_sync_from_object_tag(bContext *C)
+{
+  wmWindowManager *wm = CTX_wm_manager(C);
+  wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_OBJECT;
+}
+
+void ED_outliner_select_sync_from_edit_bone_tag(bContext *C)
+{
+  wmWindowManager *wm = CTX_wm_manager(C);
+  wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE;
+}
+
+void ED_outliner_select_sync_from_pose_bone_tag(bContext *C)
+{
+  wmWindowManager *wm = CTX_wm_manager(C);
+  wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE;
+}
+
+void ED_outliner_select_sync_from_sequence_tag(bContext *C)
+{
+  wmWindowManager *wm = CTX_wm_manager(C);
+  wm->outliner_sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE;
+}
+
+bool ED_outliner_select_sync_is_dirty(const bContext *C)
+{
+  wmWindowManager *wm = CTX_wm_manager(C);
+  return wm->outliner_sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_ALL;
+}
+
+/* Copy sync select dirty flag from window manager to all outliners to be synced lazily on draw */
+void ED_outliner_select_sync_flag_outliners(const bContext *C)
+{
+  Main *bmain = CTX_data_main(C);
+  wmWindowManager *wm = CTX_wm_manager(C);
+
+  for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
+    for (ScrArea *sa = screen->areabase.first; sa; sa = sa->next) {
+      for (SpaceLink *sl = sa->spacedata.first; sl; sl = sl->next) {
+        if (sl->spacetype == SPACE_OUTLINER) {
+          SpaceOutliner *soutliner = (SpaceOutliner *)sl;
+
+          soutliner->sync_select_dirty |= wm->outliner_sync_select_dirty;
+        }
+      }
+    }
+  }
+
+  /* Clear global sync flag */
+  wm->outliner_sync_select_dirty = 0;
+}
+
+/**
+ * Outliner sync select dirty flags are not enough to determine which types to sync,
+ * outliner display mode also needs to be considered. This stores the types of data
+ * to sync to increase code clarity.
+ */
+typedef struct SyncSelectTypes {
+  bool object;
+  bool edit_bone;
+  bool pose_bone;
+  bool sequence;
+} SyncSelectTypes;
+
+/**
+ * Set which types of data to sync when syncing selection from the outliner based on object
+ * interaction mode and outliner display mode
+ */
+static void outliner_sync_select_from_outliner_set_types(bContext *C,
+                                                         SpaceOutliner *soops,
+                                                         SyncSelectTypes *sync_types)
+{
+  Object *obact = CTX_data_active_object(C);
+  Object *obedit = CTX_data_edit_object(C);
+
+  const bool sequence_view = soops->outlinevis == SO_SEQUENCE;
+
+  sync_types->object = !sequence_view && (obact && obact->mode == OB_MODE_OBJECT);
+  sync_types->edit_bone = !sequence_view && (obedit && obedit->type == OB_ARMATURE);
+  sync_types->pose_bone = !sequence_view && (obact && obact->mode == OB_MODE_POSE);
+  sync_types->sequence = sequence_view;
+}
+
+/**
+ * Current dirty flags and outliner display mode determine which type of syncing should occur.
+ * This is to ensure sync flag data is not lost on sync in the wrong display mode.
+ */
+static void outliner_sync_select_to_outliner_set_types(const bContext *C,
+                                                       SpaceOutliner *soops,
+                                                       SyncSelectTypes *sync_types)
+{
+  Object *obact = CTX_data_active_object(C);
+  Object *obedit = CTX_data_edit_object(C);
+
+  const bool sequence_view = soops->outlinevis == SO_SEQUENCE;
+
+  sync_types->object = !sequence_view && (obact && obact->mode == OB_MODE_OBJECT) &&
+                       (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_OBJECT);
+  sync_types->edit_bone = !sequence_view && (obedit && obedit->type == OB_ARMATURE) &&
+                          (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE);
+  sync_types->pose_bone = !sequence_view && (obact && obact->mode == OB_MODE_POSE) &&
+                          (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE);
+  sync_types->sequence = sequence_view &&
+                         (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE);
+}
+
+/**
+ * Stores items selected from a sync from the outliner. Prevents syncing the selection
+ * state of the last instance of an object linked in multiple collections.
+ */
+typedef struct SelectedItems {
+  GSet *objects;
+  GSet *edit_bones;
+  GSet *pose_bones;
+} SelectedItems;
+
+static void selected_items_init(SelectedItems *selected_items)
+{
+  selected_items->objects = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
+  selected_items->edit_bones = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
+  selected_items->pose_bones = BLI_gset_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, __func__);
+}
+
+static void selected_items_free(SelectedItems *selected_items)
+{
+  BLI_gset_free(selected_items->objects, NULL);
+  BLI_gset_free(selected_items->edit_bones, NULL);
+  BLI_gset_free(selected_items->pose_bones, NULL);
+}
+
+/* Check if an instance of this object been selected by the sync */
+static bool is_object_selected(GSet *selected_objects, Base *base)
+{
+  return BLI_gset_haskey(selected_objects, base);
+}
+
+/* Check if an instance of this edit bone been selected by the sync */
+static bool is_edit_bone_selected(GSet *selected_ebones, EditBone *ebone)
+{
+  return BLI_gset_haskey(selected_ebones, ebone);
+}
+
+/* Check if an instance of this pose bone been selected by the sync */
+static bool is_pose_bone_selected(GSet *selected_pbones, bPoseChannel *pchan)
+{
+  return BLI_gset_haskey(selected_pbones, pchan);
+}
+
+/* Add element's data to selected item set */
+static void add_selected_item(GSet *selected, void *data)
+{
+  BLI_gset_add(selected, data);
+}
+
+static void outliner_select_sync_to_object(ViewLayer *view_layer,
+                                           TreeElement *te,
+                                           TreeStoreElem *tselem,
+                                           GSet *selected_objects)
+{
+  Object *ob = (Object *)tselem->id;
+  Base *base = (te->directdata) ? (Base *)te->directdata :
+                                  BKE_view_layer_base_find(view_layer, ob);
+
+  if (base && (base->flag & BASE_SELECTABLE)) {
+    if (tselem->flag & TSE_SELECTED) {
+      ED_object_base_select(base, BA_SELECT);
+
+      add_selected_item(selected_objects, base);
+    }
+    else if (!is_object_selected(selected_objects, base)) {
+      ED_object_base_select(base, BA_DESELECT);
+    }
+  }
+}
+
+static void outliner_select_sync_to_edit_bone(ViewLayer *view_layer,
+                                              TreeElement *te,
+                                              TreeStoreElem *tselem,
+                                              GSet *selected_ebones)
+{
+  bArmature *arm = (bArmature *)tselem->id;
+  EditBone *ebone = (EditBone *)te->directdata;
+
+  short bone_flag = ebone->flag;
+
+  if (EBONE_SELECTABLE(arm, ebone)) {
+    if (tselem->flag & TSE_SELECTED) {
+      ebone->flag |= (BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
+
+      add_selected_item(selected_ebones, ebone);
+    }
+    else if (!is_edit_bone_selected(selected_ebones, ebone)) {
+      ebone->flag &= ~(BONE_SELECTED | BONE_TIPSEL | BONE_ROOTSEL);
+    }
+  }
+
+  /* Tag if selection changed */
+  if (bone_flag != ebone->flag) {
+    Object *obedit = OBEDIT_FROM_VIEW_LAYER(view_layer);
+    DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
+    WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, obedit);
+  }
+}
+
+static void outliner_select_sync_to_pose_bone(TreeElement *te,
+                                              TreeStoreElem *tselem,
+                                              GSet *selected_pbones)
+{
+  Object *ob = (Object *)tselem->id;
+  bArmature *arm = ob->data;
+  bPoseChannel *pchan = (bPoseChannel *)te->directdata;
+
+  short bone_flag = pchan->bone->flag;
+
+  if (PBONE_SELECTABLE(arm, pchan->bone)) {
+    if (tselem->flag & TSE_SELECTED) {
+      pchan->bone->flag |= BONE_SELECTED;
+
+      add_selected_item(selected_pbones, pchan);
+    }
+    else if (!is_pose_bone_selected(selected_pbones, pchan)) {
+      pchan->bone->flag &= ~BONE_SELECTED;
+    }
+  }
+
+  /* Tag if selection changed */
+  if (bone_flag != pchan->bone->flag) {
+    DEG_id_tag_update(&arm->id, ID_RECALC_SELECT);
+    WM_main_add_notifier(NC_OBJECT | ND_BONE_SELECT, ob);
+  }
+}
+
+static void outliner_select_sync_to_sequence(Scene *scene, TreeStoreElem *tselem)
+{
+  Sequence *seq = (Sequence *)tselem->id;
+
+  if (tselem->flag & TSE_ACTIVE) {
+    BKE_sequencer_active_set(scene, seq);
+  }
+
+  if (tselem->flag & TSE_SELECTED) {
+    seq->flag |= SELECT;
+  }
+  else {
+    seq->flag &= ~SELECT;
+  }
+}
+
+/** Sync select and active flags from outliner to active view layer, bones, and sequencer. */
+static void outliner_sync_selection_from_outliner(Scene *scene,
+                                                  ViewLayer *view_layer,
+                                                  ListBase *tree,
+                                                  const SyncSelectTypes *sync_types,
+                                                  SelectedItems *selected_items)
+{
+
+  for (TreeElement *te = tree->first; te; te = te->next) {
+    TreeStoreElem *tselem = TREESTORE(te);
+
+    if (tselem->type == 0 && te->idcode == ID_OB) {
+      if (sync_types->object) {
+        outliner_select_sync_to_object(view_layer, te, tselem, selected_items->objects);
+      }
+    }
+    else if (tselem->type == TSE_EBONE) {
+      if (sync_types->edit_bone) {
+        outliner_select_sync_to_edit_bone(view_layer, te, tselem, selected_items->edit_bones);
+      }
+    }
+    else if (tselem->type == TSE_POSE_CHANNEL) {
+      if (sync_types->pose_bone) {
+        outliner_select_sync_to_pose_bone(te, tselem, selected_items->pose_bones);
+      }
+    }
+    else if (tselem->type == TSE_SEQUENCE) {
+      if (sync_types->sequence) {
+        outliner_select_sync_to_sequence(scene, tselem);
+      }
+    }
+
+    outliner_sync_selection_from_outliner(
+        scene, view_layer, &te->subtree, sync_types, selected_items);
+  }
+}
+
+/* Set clean outliner and mark other outliners for syncing */
+void ED_outliner_select_sync_from_outliner(bContext *C, SpaceOutliner *soops)
+{
+  /* Don't sync in certain outliner display modes */
+  if (ELEM(soops->outlinevis, SO_LIBRARIES, SO_DATA_API, SO_ID_ORPHANS)) {
+    return;
+  }
+
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+
+  SyncSelectTypes sync_types;
+  outliner_sync_select_from_outliner_set_types(C, soops, &sync_types);
+
+  /* To store elements that have been selected to prevent linked object sync errors */
+  SelectedItems selected_items;
+
+  selected_items_init(&selected_items);
+
+  outliner_sync_selection_from_outliner(
+      scene, view_layer, &soops->tree, &sync_types, &selected_items);
+
+  selected_items_free(&selected_items);
+
+  /* Tag for updates */
+  if (sync_types.object) {
+    DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
+    WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+  }
+  if (sync_types.sequence) {
+    WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
+  }
+
+  /* Clear outliner sync select dirty flag to prevent a sync to the outliner on draw */
+  soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_ALL;
+}
+
+static void outliner_select_sync_from_object(ViewLayer *view_layer,
+                                             SpaceOutliner *soops,
+                                             Object *obact,
+                                             TreeElement *te,
+                                             TreeStoreElem *tselem)
+{
+  Object *ob = (Object *)tselem->id;
+  Base *base = (te->directdata) ? (Base *)te->directdata :
+                                  BKE_view_layer_base_find(view_layer, ob);
+  const bool is_selected = (base != NULL) && ((base->flag & BASE_SELECTED) != 0);
+
+  if (base && (ob == obact)) {
+    outliner_element_activate(soops, tselem);
+  }
+
+  if (is_selected) {
+    tselem->flag |= TSE_SELECTED;
+  }
+  else {
+    tselem->flag &= ~TSE_SELECTED;
+  }
+}
+
+static void outliner_select_sync_from_edit_bone(SpaceOutliner *soops,
+                                                EditBone *ebone_active,
+                                                TreeElement *te,
+                                                TreeStoreElem *tselem)
+{
+  EditBone *ebone = (EditBone *)te->directdata;
+
+  if (ebone == ebone_active) {
+    outliner_element_activate(soops, tselem);
+  }
+
+  if (ebone->flag & BONE_SELECTED) {
+    tselem->flag |= TSE_SELECTED;
+  }
+  else {
+    tselem->flag &= ~TSE_SELECTED;
+  }
+}
+
+static void outliner_select_sync_from_pose_bone(SpaceOutliner *soops,
+                                                bPoseChannel *pchan_active,
+                                                TreeElement *te,
+                                                TreeStoreElem *tselem)
+{
+  bPoseChannel *pchan = (bPoseChannel *)te->directdata;
+  Bone *bone = pchan->bone;
+
+  if (pchan == pchan_active) {
+    outliner_element_activate(soops, tselem);
+  }
+
+  if (bone->flag & BONE_SELECTED) {
+    tselem->flag |= TSE_SELECTED;
+  }
+  else {
+    tselem->flag &= ~TSE_SELECTED;
+  }
+}
+
+static void outliner_select_sync_from_sequence(SpaceOutliner *soops,
+                                               Sequence *sequence_active,
+                                               TreeStoreElem *tselem)
+{
+  Sequence *seq = (Sequence *)tselem->id;
+
+  if (seq == sequence_active) {
+    outliner_element_activate(soops, tselem);
+  }
+
+  if (seq->flag & SELECT) {
+    tselem->flag |= TSE_SELECTED;
+  }
+  else {
+    tselem->flag &= ~TSE_SELECTED;
+  }
+}
+
+/**
+ * Contains active object, bones, and sequence for syncing to prevent getting active data
+ * repeatedly throughout syncing to the outliner.
+ */
+typedef struct SyncSelectActiveData {
+  Object *object;
+  EditBone *edit_bone;
+  bPoseChannel *pose_channel;
+  Sequence *sequence;
+} SyncSelectActiveData;
+
+/** Sync select and active flags from active view layer, bones, and sequences to the outliner. */
+static void outliner_sync_selection_to_outliner(ViewLayer *view_layer,
+                                                SpaceOutliner *soops,
+                                                ListBase *tree,
+                                                SyncSelectActiveData *active_data,
+                                                const SyncSelectTypes *sync_types)
+{
+  for (TreeElement *te = tree->first; te; te = te->next) {
+    TreeStoreElem *tselem = TREESTORE(te);
+
+    if (tselem->type == 0 && te->idcode == ID_OB) {
+      if (sync_types->object) {
+        outliner_select_sync_from_object(view_layer, soops, active_data->object, te, tselem);
+      }
+    }
+    else if (tselem->type == TSE_EBONE) {
+      if (sync_types->edit_bone) {
+        outliner_select_sync_from_edit_bone(soops, active_data->edit_bone, te, tselem);
+      }
+    }
+    else if (tselem->type == TSE_POSE_CHANNEL) {
+      if (sync_types->pose_bone) {
+        outliner_select_sync_from_pose_bone(soops, active_data->pose_channel, te, tselem);
+      }
+    }
+    else if (tselem->type == TSE_SEQUENCE) {
+      if (sync_types->sequence) {
+        outliner_select_sync_from_sequence(soops, active_data->sequence, tselem);
+      }
+    }
+    else {
+      tselem->flag &= ~TSE_SELECTED;
+    }
+
+    /* Sync subtree elements */
+    outliner_sync_selection_to_outliner(view_layer, soops, &te->subtree, active_data, sync_types);
+  }
+}
+
+/* Get active data from context */
+static void get_sync_select_active_data(const bContext *C, SyncSelectActiveData *active_data)
+{
+  Scene *scene = CTX_data_scene(C);
+  ViewLayer *view_layer = CTX_data_view_layer(C);
+  active_data->object = OBACT(view_layer);
+  active_data->edit_bone = CTX_data_active_bone(C);
+  active_data->pose_channel = CTX_data_active_pose_bone(C);
+  active_data->sequence = BKE_sequencer_active_get(scene);
+}
+
+/* If outliner is dirty sync selection from view layer and sequwncer */
+void outliner_sync_selection(const bContext *C, SpaceOutliner *soops)
+{
+  if (soops->sync_select_dirty & WM_OUTLINER_SYNC_SELECT_FROM_ALL) {
+    ViewLayer *view_layer = CTX_data_view_layer(C);
+
+    /* Set which types of data to sync from sync dirty flag and outliner display mode */
+    SyncSelectTypes sync_types;
+    outliner_sync_select_to_outliner_set_types(C, soops, &sync_types);
+
+    /* Store active object, bones, and sequence */
+    SyncSelectActiveData active_data;
+    get_sync_select_active_data(C, &active_data);
+
+    outliner_sync_selection_to_outliner(
+        view_layer, soops, &soops->tree, &active_data, &sync_types);
+
+    /* Keep any unsynced data in the dirty flag */
+    if (sync_types.object) {
+      soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_OBJECT;
+    }
+    if (sync_types.edit_bone) {
+      soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE;
+    }
+    if (sync_types.pose_bone) {
+      soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE;
+    }
+    if (sync_types.sequence) {
+      soops->sync_select_dirty &= ~WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE;
+    }
+  }
+}
index 091efc56c0984f7fc4c0e708bf234f89fc896974..48180888f62af9ba3afef464d3c55e73485a9db0 100644 (file)
@@ -304,6 +304,8 @@ static SpaceLink *outliner_new(const ScrArea *UNUSED(area), const Scene *UNUSED(
   soutliner->filter_id_type = ID_GR;
   soutliner->show_restrict_flags = SO_RESTRICT_ENABLE | SO_RESTRICT_HIDE;
   soutliner->outlinevis = SO_VIEW_LAYER;
+  soutliner->sync_select_dirty |= WM_OUTLINER_SYNC_SELECT_FROM_ALL;
+  soutliner->flag |= SO_SYNC_SELECT;
 
   /* header */
   ar = MEM_callocN(sizeof(ARegion), "header for outliner");
@@ -349,6 +351,9 @@ static SpaceLink *outliner_duplicate(SpaceLink *sl)
   soutlinern->treestore = NULL;
   soutlinern->treehash = NULL;
 
+  soutlinern->flag |= (soutliner->flag & SO_SYNC_SELECT);
+  soutlinern->sync_select_dirty = WM_OUTLINER_SYNC_SELECT_FROM_ALL;
+
   return (SpaceLink *)soutlinern;
 }
 
index 57f86059d9d0df2a5ac83d40f44fb6e1747b346c..affb6d3fd882ee5456ff9e3bcff03dec14cee391 100644 (file)
@@ -41,6 +41,7 @@
 
 /* for menu/popup icons etc etc*/
 
+#include "ED_outliner.h"
 #include "ED_screen.h"
 #include "ED_sequencer.h"
 #include "ED_select_utils.h"
@@ -49,6 +50,7 @@
 
 /* own include */
 #include "sequencer_intern.h"
+
 static void *find_nearest_marker(int UNUSED(d1), int UNUSED(d2))
 {
   return NULL;
@@ -254,6 +256,8 @@ static int sequencer_de_select_all_exec(bContext *C, wmOperator *op)
     }
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -293,6 +297,8 @@ static int sequencer_select_inverse_exec(bContext *C, wmOperator *UNUSED(op))
     }
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -542,6 +548,8 @@ static int sequencer_select_invoke(bContext *C, wmOperator *op, const wmEvent *e
     }
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   /* allowing tweaks */
@@ -668,6 +676,8 @@ static int sequencer_select_more_exec(bContext *C, wmOperator *UNUSED(op))
     return OPERATOR_CANCELLED;
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -699,6 +709,8 @@ static int sequencer_select_less_exec(bContext *C, wmOperator *UNUSED(op))
     return OPERATOR_CANCELLED;
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -750,6 +762,8 @@ static int sequencer_select_linked_pick_invoke(bContext *C, wmOperator *op, cons
     selected = select_more_less_seq__internal(scene, 1, 1);
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -784,6 +798,8 @@ static int sequencer_select_linked_exec(bContext *C, wmOperator *UNUSED(op))
     selected = select_more_less_seq__internal(scene, true, true);
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -832,6 +848,8 @@ static int sequencer_select_handles_exec(bContext *C, wmOperator *op)
     }
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -876,6 +894,8 @@ static int sequencer_select_active_side_exec(bContext *C, wmOperator *op)
   select_active_side(
       ed->seqbasep, RNA_enum_get(op->ptr, "side"), seq_act->machine, seq_act->startdisp);
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -934,6 +954,8 @@ static int sequencer_box_select_exec(bContext *C, wmOperator *op)
     }
   }
 
+  ED_outliner_select_sync_from_sequence_tag(C);
+
   WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
 
   return OPERATOR_FINISHED;
@@ -1311,6 +1333,7 @@ static int sequencer_select_grouped_exec(bContext *C, wmOperator *op)
   }
 
   if (changed) {
+    ED_outliner_select_sync_from_sequence_tag(C);
     WM_event_add_notifier(C, NC_SCENE | ND_SEQUENCER | NA_SELECTED, scene);
     return OPERATOR_FINISHED;
   }
index 725f655c8cea53f0cc3a1c1fadc9f2884b3884fb..37ea10773bfa1eae5a0d5b06ab3c5f40caaeff94 100644 (file)
@@ -88,6 +88,7 @@
 #include "ED_particle.h"
 #include "ED_mesh.h"
 #include "ED_object.h"
+#include "ED_outliner.h"
 #include "ED_screen.h"
 #include "ED_select_utils.h"
 #include "ED_sculpt.h"
@@ -1280,9 +1281,15 @@ static bool view3d_lasso_select(
     }
     else if (ob && (ob->mode & OB_MODE_POSE)) {
       changed_multi |= do_lasso_select_pose(vc, mcords, moves, sel_op);
+      if (changed_multi) {
+        ED_outliner_select_sync_from_pose_bone_tag(C);
+      }
     }
     else {
       changed_multi |= do_lasso_select_objects(vc, mcords, moves, sel_op);
+      if (changed_multi) {
+        ED_outliner_select_sync_from_object_tag(C);
+      }
     }
   }
   else { /* Edit Mode */
@@ -1303,6 +1310,9 @@ static bool view3d_lasso_select(
           break;
         case OB_ARMATURE:
           changed = do_lasso_select_armature(vc, mcords, moves, sel_op);
+          if (changed) {
+            ED_outliner_select_sync_from_edit_bone_tag(C);
+          }
           break;
         case OB_MBALL:
           changed = do_lasso_select_meta(vc, mcords, moves, sel_op);
@@ -1488,6 +1498,9 @@ static int object_select_menu_exec(bContext *C, wmOperator *op)
     Scene *scene = CTX_data_scene(C);
     DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
     WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, scene);
+
+    ED_outliner_select_sync_from_object_tag(C);
+
     return OPERATOR_FINISHED;
   }
   else {
@@ -2350,6 +2363,9 @@ static int view3d_select_exec(bContext *C, wmOperator *op)
       if (!retval && deselect_all) {
         retval = ED_armature_edit_deselect_all_visible_multi(C);
       }
+      if (retval) {
+        ED_outliner_select_sync_from_edit_bone_tag(C);
+      }
     }
     else if (obedit->type == OB_LATTICE) {
       retval = ED_lattice_select_pick(C, location, extend, deselect, toggle);
@@ -2410,6 +2426,15 @@ static int view3d_select_exec(bContext *C, wmOperator *op)
         DEG_id_tag_update(&scene->id, ID_RECALC_SELECT);
       }
     }
+
+    if (retval) {
+      if (obact && obact->mode & OB_MODE_POSE) {
+        ED_outliner_select_sync_from_pose_bone_tag(C);
+      }
+      else {
+        ED_outliner_select_sync_from_object_tag(C);
+      }
+    }
   }
 
   /* Pass-through allows tweaks
@@ -3230,6 +3255,7 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op)
           if (changed) {
             DEG_id_tag_update(&vc.obedit->id, ID_RECALC_SELECT);
             WM_event_add_notifier(C, NC_OBJECT | ND_BONE_SELECT, vc.obedit);
+            ED_outliner_select_sync_from_edit_bone_tag(C);
           }
           break;
         case OB_LATTICE:
@@ -3264,9 +3290,15 @@ static int view3d_box_select_exec(bContext *C, wmOperator *op)
     }
     else if (vc.obact && vc.obact->mode & OB_MODE_POSE) {
       changed_multi = do_pose_box_select(C, &vc, &rect, sel_op);
+      if (changed_multi) {
+        ED_outliner_select_sync_from_pose_bone_tag(C);
+      }
     }
     else { /* object mode with none active */
       changed_multi = do_object_box_select(C, &vc, &rect, sel_op);
+      if (changed_multi) {
+        ED_outliner_select_sync_from_object_tag(C);
+      }
     }
   }
 
@@ -3890,7 +3922,8 @@ static bool mball_circle_select(ViewContext *vc,
 
 /** Callbacks for circle selection in Editmode */
 
-static bool obedit_circle_select(ViewContext *vc,
+static bool obedit_circle_select(bContext *C,
+                                 ViewContext *vc,
                                  wmGenericUserData *wm_userdata,
                                  const eSelectOp sel_op,
                                  const int mval[2],
@@ -3911,6 +3944,9 @@ static bool obedit_circle_select(ViewContext *vc,
       break;
     case OB_ARMATURE:
       changed = armature_circle_select(vc, sel_op, mval, rad);
+      if (changed) {
+        ED_outliner_select_sync_from_edit_bone_tag(C);
+      }
       break;
     case OB_MBALL:
       changed = mball_circle_select(vc, sel_op, mval, rad);
@@ -3999,7 +4035,7 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
       obedit = vc.obedit;
 
       if (obedit) {
-        obedit_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius);
+        obedit_circle_select(C, &vc, wm_userdata, sel_op, mval, (float)radius);
       }
       else if (BKE_paint_select_face_test(obact)) {
         paint_facesel_circle_select(&vc, wm_userdata, sel_op, mval, (float)radius);
@@ -4009,6 +4045,7 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
       }
       else if (obact->mode & OB_MODE_POSE) {
         pose_circle_select(&vc, sel_op, mval, (float)radius);
+        ED_outliner_select_sync_from_pose_bone_tag(C);
       }
       else {
         BLI_assert(0);
@@ -4029,6 +4066,8 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
     if (object_circle_select(&vc, sel_op, mval, (float)radius)) {
       DEG_id_tag_update(&vc.scene->id, ID_RECALC_SELECT);
       WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc.scene);
+
+      ED_outliner_select_sync_from_object_tag(C);
     }
   }
 
index 1462d955f81e77b5789a8e713b8b4fea6ec94c01..6e830987725c672a1fb319de0195122ee9ae848e 100644 (file)
@@ -60,6 +60,8 @@ enum {
   TSE_DRAG_INTO = (1 << 6),
   TSE_DRAG_BEFORE = (1 << 7),
   TSE_DRAG_AFTER = (1 << 8),
+  /* Needed because outliner-only elements can be active */
+  TSE_ACTIVE = (1 << 9),
   TSE_DRAG_ANY = (TSE_DRAG_INTO | TSE_DRAG_BEFORE | TSE_DRAG_AFTER),
 };
 
index 505042432ed7b4741999b1eae281b28af9b6d6c4..707b312e967a43c4dadbe3dfb601d85c5810f435 100644 (file)
@@ -244,7 +244,12 @@ typedef struct SpaceOutliner {
   char search_string[64];
   struct TreeStoreElem search_tse;
 
-  short flag, outlinevis, storeflag, search_flags;
+  short flag, outlinevis, storeflag;
+  char search_flags;
+
+  /** Selection syncing flag (#WM_OUTLINER_SYNC_SELECT_FROM_OBJECT and similar flags). */
+  char sync_select_dirty;
+
   int filter;
   char filter_state;
   char show_restrict_flags;
@@ -263,6 +268,7 @@ typedef enum eSpaceOutliner_Flag {
   SO_FLAG_UNUSED_1 = (1 << 2), /* cleared */
   SO_HIDE_KEYINGSETINFO = (1 << 3),
   SO_SKIP_SORT_ALPHA = (1 << 4),
+  SO_SYNC_SELECT = (1 << 5),
 } eSpaceOutliner_Flag;
 
 /* SpaceOutliner.filter */
index 8dcae41aaa2c157a45cfa65dba7d0a2ca214e5a3..cacc79608ade35c166d5ce43658ad53b52cb95c6 100644 (file)
@@ -131,12 +131,15 @@ typedef struct wmWindowManager {
   ListBase windows;
 
   /** Set on file read. */
-  int initialized;
+  short initialized;
   /** Indicator whether data was saved. */
   short file_saved;
   /** Operator stack depth to avoid nested undo pushes. */
   short op_undo_depth;
 
+  /** Set after selection to notify outliner to sync. Stores type of selection */
+  short outliner_sync_select_dirty;
+
   /** Operator registry. */
   ListBase operators;
 
@@ -186,6 +189,18 @@ enum {
   WM_KEYCONFIG_IS_INITIALIZED = (1 << 1),
 };
 
+/* wmWindowManager.outliner_sync_select_dirty */
+enum {
+  WM_OUTLINER_SYNC_SELECT_FROM_OBJECT = (1 << 0),
+  WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE = (1 << 1),
+  WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE = (1 << 2),
+  WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE = (1 << 3),
+};
+
+#define WM_OUTLINER_SYNC_SELECT_FROM_ALL \
+  (WM_OUTLINER_SYNC_SELECT_FROM_OBJECT | WM_OUTLINER_SYNC_SELECT_FROM_EDIT_BONE | \
+   WM_OUTLINER_SYNC_SELECT_FROM_POSE_BONE | WM_OUTLINER_SYNC_SELECT_FROM_SEQUENCE)
+
 #define WM_KEYCONFIG_STR_DEFAULT "blender"
 
 /* IME is win32 only! */
index 28f64e4e1f020597369689cc789949af99a041f4..87472081f79e12c16e44a2daab8c03386fb8cd76 100644 (file)
@@ -2815,6 +2815,12 @@ static void rna_def_space_outliner(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Sort Alphabetically", "");
   RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL);
 
+  prop = RNA_def_property(srna, "use_sync_select", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", SO_SYNC_SELECT);
+  RNA_def_property_ui_text(
+      prop, "Sync Outliner Selection", "Sync outliner selection with other editors");
+  RNA_def_property_update(prop, NC_SPACE | ND_SPACE_OUTLINER, NULL);
+
   /* Granular restriction column option. */
   prop = RNA_def_property(srna, "show_restrict_column_enable", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "show_restrict_flags", SO_RESTRICT_ENABLE);
index 77ca48fc05dab4630e9845f9f4fd5dc26f922fff..72a3455b1206c847b783ef3d4ad5bbd1df7c1fa8 100644 (file)
@@ -2297,6 +2297,11 @@ static void rna_def_userdef_theme_space_outliner(BlenderRNA *brna)
   RNA_def_property_ui_text(prop, "Selected Highlight", "");
   RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
 
+  prop = RNA_def_property(srna, "active", PROP_FLOAT, PROP_COLOR_GAMMA);
+  RNA_def_property_array(prop, 3);
+  RNA_def_property_ui_text(prop, "Active Highlight", "");
+  RNA_def_property_update(prop, 0, "rna_userdef_theme_update");
+
   prop = RNA_def_property(srna, "selected_object", PROP_FLOAT, PROP_COLOR_GAMMA);
   RNA_def_property_array(prop, 3);
   RNA_def_property_ui_text(prop, "Selected Objects", "");