UI Code Quality: Convert Outliner Blender File mode to new tree buiding design

See https://developer.blender.org/D9499.

Also:
* Add `space_outliner/tree/common.cc` for functions shared between display
  modes.
* I had to add a cast to `ListBaseWrapper` to make it work with ID lists.
* Cleanup: Remove internal `Tree` alias for `ListBase`. That was more confusing
  than helpful.
This commit is contained in:
Julian Eisel 2020-11-07 20:13:37 +01:00
parent ad0c387fdf
commit 44d8fafd7f
8 changed files with 277 additions and 163 deletions

View File

@ -56,7 +56,8 @@ template<typename T> class ListBaseWrapper {
Iterator &operator++()
{
current_ = current_->next;
/* Some types store next/prev using `void *`, so cast is necessary. */
current_ = static_cast<T *>(current_->next);
return *this;
}

View File

@ -44,7 +44,9 @@ set(SRC
outliner_tree.c
outliner_utils.c
space_outliner.c
tree/common.cc
tree/tree_view.cc
tree/tree_view_libraries.cc
tree/tree_view_view_layer.cc
outliner_intern.h

View File

@ -1382,110 +1382,6 @@ static void outliner_add_seq_dup(SpaceOutliner *space_outliner,
/* ----------------------------------------------- */
static const char *outliner_idcode_to_plural(short idcode)
{
const char *propname = BKE_idtype_idcode_to_name_plural(idcode);
PropertyRNA *prop = RNA_struct_type_find_property(&RNA_BlendData, propname);
return (prop) ? RNA_property_ui_name(prop) : "UNKNOWN";
}
static bool outliner_library_id_show(Library *lib, ID *id, short filter_id_type)
{
if (id->lib != lib) {
return false;
}
if (filter_id_type == ID_GR) {
/* Don't show child collections of non-scene master collection,
* they are already shown as children. */
Collection *collection = (Collection *)id;
bool has_non_scene_parent = false;
LISTBASE_FOREACH (CollectionParent *, cparent, &collection->parents) {
if (!(cparent->collection->flag & COLLECTION_IS_MASTER)) {
has_non_scene_parent = true;
}
}
if (has_non_scene_parent) {
return false;
}
}
return true;
}
static TreeElement *outliner_add_library_contents(Main *mainvar,
SpaceOutliner *space_outliner,
ListBase *lb,
Library *lib)
{
TreeElement *ten, *tenlib = NULL;
ListBase *lbarray[MAX_LIBARRAY];
int a, tot;
short filter_id_type = (space_outliner->filter & SO_FILTER_ID_TYPE) ?
space_outliner->filter_id_type :
0;
if (filter_id_type) {
lbarray[0] = which_libbase(mainvar, space_outliner->filter_id_type);
tot = 1;
}
else {
tot = set_listbasepointers(mainvar, lbarray);
}
for (a = 0; a < tot; a++) {
if (lbarray[a] && lbarray[a]->first) {
ID *id = lbarray[a]->first;
const bool is_library = (GS(id->name) == ID_LI) && (lib != NULL);
/* check if there's data in current lib */
for (; id; id = id->next) {
if (id->lib == lib) {
break;
}
}
/* We always want to create an entry for libraries, even if/when we have no more IDs from
* them. This invalid state is important to show to user as well.*/
if (id != NULL || is_library) {
if (!tenlib) {
/* Create library tree element on demand, depending if there are any data-blocks. */
if (lib) {
tenlib = outliner_add_element(space_outliner, lb, lib, NULL, 0, 0);
}
else {
tenlib = outliner_add_element(space_outliner, lb, mainvar, NULL, TSE_ID_BASE, 0);
tenlib->name = IFACE_("Current File");
}
}
/* Create data-block list parent element on demand. */
if (id != NULL) {
if (filter_id_type) {
ten = tenlib;
}
else {
ten = outliner_add_element(
space_outliner, &tenlib->subtree, lbarray[a], NULL, TSE_ID_BASE, 0);
ten->directdata = lbarray[a];
ten->name = outliner_idcode_to_plural(GS(id->name));
}
for (id = lbarray[a]->first; id; id = id->next) {
if (outliner_library_id_show(lib, id, filter_id_type)) {
outliner_add_element(space_outliner, &ten->subtree, id, ten, 0, 0);
}
}
}
}
}
}
return tenlib;
}
static void outliner_add_orphaned_datablocks(Main *mainvar, SpaceOutliner *space_outliner)
{
TreeElement *ten;
@ -2328,61 +2224,13 @@ void outliner_build_tree(Main *mainvar,
&source_data);
}
if (!BLI_listbase_is_empty(&space_outliner->tree)) {
/* Skip. */
if (space_outliner->runtime->tree_view) {
/* Skip if there's a tree view that's responsible for adding all elements. */
}
/* options */
else if (space_outliner->outlinevis == SO_LIBRARIES) {
Library *lib;
/* current file first - mainvar provides tselem with unique pointer - not used */
ten = outliner_add_library_contents(mainvar, space_outliner, &space_outliner->tree, NULL);
if (ten) {
tselem = TREESTORE(ten);
if (!tselem->used) {
tselem->flag &= ~TSE_CLOSED;
}
}
for (lib = mainvar->libraries.first; lib; lib = lib->id.next) {
ten = outliner_add_library_contents(mainvar, space_outliner, &space_outliner->tree, lib);
/* NULL-check matters, due to filtering there may not be a new element. */
if (ten) {
lib->id.newid = (ID *)ten;
}
}
/* make hierarchy */
ten = space_outliner->tree.first;
if (ten != NULL) {
ten = ten->next; /* first one is main */
while (ten) {
TreeElement *nten = ten->next, *par;
tselem = TREESTORE(ten);
lib = (Library *)tselem->id;
if (lib && lib->parent) {
par = (TreeElement *)lib->parent->id.newid;
if (tselem->id->tag & LIB_TAG_INDIRECT) {
/* Only remove from 'first level' if lib is not also directly used. */
BLI_remlink(&space_outliner->tree, ten);
BLI_addtail(&par->subtree, ten);
ten->parent = par;
}
else {
/* Else, make a new copy of the libtree for our parent. */
TreeElement *dupten = outliner_add_library_contents(
mainvar, space_outliner, &par->subtree, lib);
if (dupten) {
dupten->parent = par;
}
}
}
ten = nten;
}
}
/* restore newid pointers */
for (lib = mainvar->libraries.first; lib; lib = lib->id.next) {
lib->id.newid = NULL;
}
/* Ported to new tree-view, should be built there already. */
BLI_assert(false);
}
else if (space_outliner->outlinevis == SO_SCENES) {
Scene *sce;

View File

@ -0,0 +1,41 @@
/*
* 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.
*/
/** \file
* \ingroup spoutliner
*
* Functions and helpers shared between tree-view types or other tree related code.
*/
#include "BKE_idtype.h"
#include "RNA_access.h"
#include "tree_view.hh"
/* -------------------------------------------------------------------- */
/** \name ID Helpers.
*
* \{ */
const char *outliner_idcode_to_plural(short idcode)
{
const char *propname = BKE_idtype_idcode_to_name_plural(idcode);
PropertyRNA *prop = RNA_struct_type_find_property(&RNA_BlendData, propname);
return (prop) ? RNA_property_ui_name(prop) : "UNKNOWN";
}
/** \} */

View File

@ -34,7 +34,10 @@ TreeView *outliner_tree_view_create(eSpaceOutliner_Mode mode, SpaceOutliner *spa
switch (mode) {
case SO_SCENES:
break;
case SO_LIBRARIES:
tree_view = new outliner::TreeViewLibraries(*space_outliner);
break;
case SO_SEQUENCE:
case SO_DATA_API:
case SO_ID_ORPHANS:

View File

@ -36,8 +36,6 @@ namespace blender {
namespace ed {
namespace outliner {
using Tree = ListBase;
/* -------------------------------------------------------------------- */
/* Tree-View Interface */
@ -60,7 +58,7 @@ class AbstractTreeView {
* Build a tree for this view with the Blender context data given in \a source_data and the view
* settings in \a space_outliner.
*/
virtual Tree buildTree(const TreeSourceData &source_data) = 0;
virtual ListBase buildTree(const TreeSourceData &source_data) = 0;
protected:
/** All derived classes will need a handle to this, so storing it in the base for convenience. */
@ -80,7 +78,7 @@ class TreeViewViewLayer final : public AbstractTreeView {
public:
TreeViewViewLayer(SpaceOutliner &space_outliner);
Tree buildTree(const TreeSourceData &source_data) override;
ListBase buildTree(const TreeSourceData &source_data) override;
private:
void add_view_layer(ListBase &, TreeElement &);
@ -89,6 +87,21 @@ class TreeViewViewLayer final : public AbstractTreeView {
void add_layer_collection_objects_children(TreeElement &);
};
/* -------------------------------------------------------------------- */
/* Library Tree-View */
/**
* \brief Tree-View for the Libraries display mode.
*/
class TreeViewLibraries final : public AbstractTreeView {
public:
TreeViewLibraries(SpaceOutliner &space_outliner);
ListBase buildTree(const TreeSourceData &source_data) override;
private:
};
} // namespace outliner
} // namespace ed
} // namespace blender
@ -126,6 +139,8 @@ struct TreeElement *outliner_add_element(struct SpaceOutliner *space_outliner,
short index);
void outliner_make_object_parent_hierarchy(ListBase *lb);
const char *outliner_idcode_to_plural(short idcode);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,204 @@
/*
* 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.
*/
/** \file
* \ingroup spoutliner
*/
#include "BLI_listbase.h"
#include "BLI_listbase_wrapper.hh"
#include "BKE_collection.h"
#include "BKE_main.h"
#include "BLT_translation.h"
#include "../outliner_intern.h"
#include "tree_view.hh"
namespace blender {
namespace ed {
namespace outliner {
/* Convenience/readability. */
template<typename T> using List = ListBaseWrapper<T>;
TreeViewLibraries::TreeViewLibraries(SpaceOutliner &space_outliner)
: AbstractTreeView(space_outliner)
{
}
static bool outliner_library_id_show(Library *lib, ID *id, short filter_id_type)
{
if (id->lib != lib) {
return false;
}
if (filter_id_type == ID_GR) {
/* Don't show child collections of non-scene master collection,
* they are already shown as children. */
Collection *collection = (Collection *)id;
bool has_non_scene_parent = false;
for (CollectionParent *cparent : List<CollectionParent>(collection->parents)) {
if (!(cparent->collection->flag & COLLECTION_IS_MASTER)) {
has_non_scene_parent = true;
}
}
if (has_non_scene_parent) {
return false;
}
}
return true;
}
static TreeElement *outliner_add_library_contents(Main *mainvar,
SpaceOutliner *space_outliner,
ListBase *lb,
Library *lib)
{
TreeElement *ten, *tenlib = nullptr;
ListBase *lbarray[MAX_LIBARRAY];
int a, tot;
short filter_id_type = (space_outliner->filter & SO_FILTER_ID_TYPE) ?
space_outliner->filter_id_type :
0;
if (filter_id_type) {
lbarray[0] = which_libbase(mainvar, space_outliner->filter_id_type);
tot = 1;
}
else {
tot = set_listbasepointers(mainvar, lbarray);
}
for (a = 0; a < tot; a++) {
if (lbarray[a] && lbarray[a]->first) {
ID *id = static_cast<ID *>(lbarray[a]->first);
const bool is_library = (GS(id->name) == ID_LI) && (lib != nullptr);
/* check if there's data in current lib */
for (ID *id_iter : List<ID>(lbarray[a])) {
if (id_iter->lib == lib) {
id = id_iter;
break;
}
}
/* We always want to create an entry for libraries, even if/when we have no more IDs from
* them. This invalid state is important to show to user as well.*/
if (id != nullptr || is_library) {
if (!tenlib) {
/* Create library tree element on demand, depending if there are any data-blocks. */
if (lib) {
tenlib = outliner_add_element(space_outliner, lb, lib, nullptr, 0, 0);
}
else {
tenlib = outliner_add_element(space_outliner, lb, mainvar, nullptr, TSE_ID_BASE, 0);
tenlib->name = IFACE_("Current File");
}
}
/* Create data-block list parent element on demand. */
if (id != nullptr) {
if (filter_id_type) {
ten = tenlib;
}
else {
ten = outliner_add_element(
space_outliner, &tenlib->subtree, lbarray[a], nullptr, TSE_ID_BASE, 0);
ten->directdata = lbarray[a];
ten->name = outliner_idcode_to_plural(GS(id->name));
}
for (ID *id : List<ID>(lbarray[a])) {
if (outliner_library_id_show(lib, id, filter_id_type)) {
outliner_add_element(space_outliner, &ten->subtree, id, ten, 0, 0);
}
}
}
}
}
}
return tenlib;
}
ListBase TreeViewLibraries::buildTree(const TreeSourceData &source_data)
{
ListBase tree = {nullptr};
/* current file first - mainvar provides tselem with unique pointer - not used */
TreeElement *ten = outliner_add_library_contents(
source_data.bmain, &_space_outliner, &tree, nullptr);
TreeStoreElem *tselem;
if (ten) {
tselem = TREESTORE(ten);
if (!tselem->used) {
tselem->flag &= ~TSE_CLOSED;
}
}
for (ID *id : List<ID>(source_data.bmain->libraries)) {
Library *lib = reinterpret_cast<Library *>(id);
ten = outliner_add_library_contents(source_data.bmain, &_space_outliner, &tree, lib);
/* NULL-check matters, due to filtering there may not be a new element. */
if (ten) {
lib->id.newid = (ID *)ten;
}
}
/* make hierarchy */
ten = static_cast<TreeElement *>(tree.first);
if (ten != nullptr) {
ten = ten->next; /* first one is main */
while (ten) {
TreeElement *nten = ten->next, *par;
tselem = TREESTORE(ten);
Library *lib = (Library *)tselem->id;
if (lib && lib->parent) {
par = (TreeElement *)lib->parent->id.newid;
if (tselem->id->tag & LIB_TAG_INDIRECT) {
/* Only remove from 'first level' if lib is not also directly used. */
BLI_remlink(&tree, ten);
BLI_addtail(&par->subtree, ten);
ten->parent = par;
}
else {
/* Else, make a new copy of the libtree for our parent. */
TreeElement *dupten = outliner_add_library_contents(
source_data.bmain, &_space_outliner, &par->subtree, lib);
if (dupten) {
dupten->parent = par;
}
}
}
ten = nten;
}
}
/* restore newid pointers */
for (ID *library_id : List<ID>(source_data.bmain->libraries)) {
library_id->newid = nullptr;
}
return tree;
}
} // namespace outliner
} // namespace ed
} // namespace blender

View File

@ -69,9 +69,9 @@ TreeViewViewLayer::TreeViewViewLayer(SpaceOutliner &space_outliner)
{
}
Tree TreeViewViewLayer::buildTree(const TreeSourceData &source_data)
ListBase TreeViewViewLayer::buildTree(const TreeSourceData &source_data)
{
Tree tree = {nullptr};
ListBase tree = {nullptr};
_view_layer = source_data.view_layer;
_show_objects = !(_space_outliner.filter & SO_FILTER_NO_OBJECT);