Fix the keyboard focus issue for nested table view
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / table-view / table-view-impl.cpp
index d2e46fd..b683987 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,7 +22,7 @@
 #include <sstream>
 #include <dali/public-api/object/ref-object.h>
 #include <dali/public-api/object/type-registry.h>
-#include <dali/devel-api/object/type-registry-helper.h>
+#include <dali/public-api/object/type-registry-helper.h>
 #include <dali/devel-api/scripting/scripting.h>
 #include <dali/public-api/size-negotiation/relayout-container.h>
 #include <dali/integration-api/debug.h>
@@ -31,20 +31,6 @@ using namespace Dali;
 
 namespace
 {
-/*
- * Custom properties for where to put the actor.
- *
- * When an actor is add to the tableView through Actor::Add() instead of TableView::AddChild,
- * the following custom properties of the actor are checked to decide the actor position inside the table
- *
- * These non-animatable properties should be registered to the child which would be added to the table
- */
-const char * const CELL_INDEX_PROPERTY_NAME("cellIndex");
-const char * const ROW_SPAN_PROPERTY_NAME("rowSpan");
-const char * const COLUMN_SPAN_PROPERTY_NAME("columnSpan");
-const char * const CELL_HORIZONTAL_ALIGNMENT_PROPERTY_NAME("cellHorizontalAlignment");
-const char * const CELL_VERTICAL_ALIGNMENT_PROPERTY_NAME("cellVerticalAlignment");
-
 /**
  * @brief Should the tableview fit around the given actor
  *
@@ -147,6 +133,11 @@ DALI_PROPERTY_REGISTRATION( Toolkit, TableView, "columns",        INTEGER, COLUM
 DALI_PROPERTY_REGISTRATION( Toolkit, TableView, "cellPadding",    VECTOR2, CELL_PADDING   )
 DALI_PROPERTY_REGISTRATION( Toolkit, TableView, "layoutRows",     MAP,     LAYOUT_ROWS    )
 DALI_PROPERTY_REGISTRATION( Toolkit, TableView, "layoutColumns",  MAP,     LAYOUT_COLUMNS )
+DALI_CHILD_PROPERTY_REGISTRATION( Toolkit, TableView, "cellIndex",                VECTOR2, CELL_INDEX                )
+DALI_CHILD_PROPERTY_REGISTRATION( Toolkit, TableView, "rowSpan",                  FLOAT,   ROW_SPAN                  )
+DALI_CHILD_PROPERTY_REGISTRATION( Toolkit, TableView, "columnSpan",               FLOAT,   COLUMN_SPAN               )
+DALI_CHILD_PROPERTY_REGISTRATION( Toolkit, TableView, "cellHorizontalAlignment",  STRING,  CELL_HORIZONTAL_ALIGNMENT )
+DALI_CHILD_PROPERTY_REGISTRATION( Toolkit, TableView, "cellVerticalAlignment",    STRING,  CELL_VERTICAL_ALIGNMENT   )
 
 DALI_TYPE_REGISTRATION_END()
 
@@ -287,7 +278,7 @@ Actor TableView::RemoveChildAt( const Toolkit::TableView::CellPosition& position
   if( child )
   {
     RelayoutingLock lock( *this );
-    // Remove the child, this will trigger a call to OnControlChildRemove
+    // Remove the child, this will trigger a call to OnChildRemove
     Self().Remove( child );
 
     // relayout the table only if instances were found
@@ -412,7 +403,7 @@ void TableView::DeleteRow( unsigned int rowIndex, std::vector<Actor>& removed )
       else if( row >= rowIndex )    // If below of or at the inserted row, decrease row index
       {
         // Decrement index
-        if( position.rowIndex > 1 )
+        if( position.rowIndex > 0 )
         {
           position.rowIndex--;
         }
@@ -804,6 +795,8 @@ void TableView::OnSizeSet( const Vector3& size )
   // rows and columns must be recalculated or the new size will not take effect.
   mRowDirty = mColumnDirty = true;
   RelayoutRequest();
+
+  Control::OnSizeSet( size );
 }
 
 void TableView::OnRelayout( const Vector2& size, RelayoutContainer& container )
@@ -968,100 +961,106 @@ Property::Value TableView::GetProperty( BaseObject* object, Property::Index inde
   return value;
 }
 
-void TableView::OnControlChildAdd( Actor& child )
+void TableView::OnChildAdd( Actor& child )
 {
-  if( mLayoutingChild )
+  if( mLayoutingChild )
   {
-    // we're in the middle of laying out children so no point doing anything here
-    return;
-  }
-
-  // Test properties on actor
-  HorizontalAlignment::Type horizontalAlignment = HorizontalAlignment::LEFT;
-  VerticalAlignment::Type verticalAlignment = VerticalAlignment::TOP;
-  if( child.GetPropertyIndex( CELL_HORIZONTAL_ALIGNMENT_PROPERTY_NAME ) != Property::INVALID_INDEX )
-  {
-    std::string value = child.GetProperty( child.GetPropertyIndex(CELL_HORIZONTAL_ALIGNMENT_PROPERTY_NAME) ).Get<std::string >();
-    Scripting::GetEnumeration< HorizontalAlignment::Type >( value.c_str(),
-                                                            HORIZONTAL_ALIGNMENT_STRING_TABLE,
-                                                            HORIZONTAL_ALIGNMENT_STRING_TABLE_COUNT,
-                                                            horizontalAlignment );
-  }
-  if( child.GetPropertyIndex( CELL_VERTICAL_ALIGNMENT_PROPERTY_NAME ) != Property::INVALID_INDEX )
-  {
-    std::string value = child.GetProperty( child.GetPropertyIndex(CELL_VERTICAL_ALIGNMENT_PROPERTY_NAME) ).Get<std::string >();
-    Scripting::GetEnumeration< VerticalAlignment::Type >( value.c_str(),
-                                                          VERTICAL_ALIGNMENT_STRING_TABLE,
-                                                          VERTICAL_ALIGNMENT_STRING_TABLE_COUNT,
-                                                          verticalAlignment );
-  }
+    // Ensure we're not in the middle of laying out children
 
+    // Check child properties on actor to decide its position inside the table
+    HorizontalAlignment::Type horizontalAlignment = HorizontalAlignment::LEFT;
+    VerticalAlignment::Type verticalAlignment = VerticalAlignment::TOP;
 
-  Toolkit::TableView::CellPosition cellPosition;
-  if( child.GetPropertyIndex(ROW_SPAN_PROPERTY_NAME) != Property::INVALID_INDEX )
-  {
-    cellPosition.rowSpan = static_cast<unsigned int>( child.GetProperty( child.GetPropertyIndex(ROW_SPAN_PROPERTY_NAME) ).Get<float>() );
-  }
+    if( child.GetPropertyType( Toolkit::TableView::ChildProperty::CELL_HORIZONTAL_ALIGNMENT ) != Property::NONE )
+    {
+      std::string value = child.GetProperty( Toolkit::TableView::ChildProperty::CELL_HORIZONTAL_ALIGNMENT ).Get<std::string >();
+      Scripting::GetEnumeration< HorizontalAlignment::Type >( value.c_str(),
+                                                              HORIZONTAL_ALIGNMENT_STRING_TABLE,
+                                                              HORIZONTAL_ALIGNMENT_STRING_TABLE_COUNT,
+                                                              horizontalAlignment );
+    }
 
-  if( child.GetPropertyIndex(COLUMN_SPAN_PROPERTY_NAME) != Property::INVALID_INDEX )
-  {
-    cellPosition.columnSpan = static_cast<unsigned int>( child.GetProperty( child.GetPropertyIndex(COLUMN_SPAN_PROPERTY_NAME) ).Get<float>() );
-  }
+    if( child.GetPropertyType( Toolkit::TableView::ChildProperty::CELL_VERTICAL_ALIGNMENT ) != Property::NONE )
+    {
+      std::string value = child.GetProperty( Toolkit::TableView::ChildProperty::CELL_VERTICAL_ALIGNMENT ).Get<std::string >();
+      Scripting::GetEnumeration< VerticalAlignment::Type >( value.c_str(),
+                                                            VERTICAL_ALIGNMENT_STRING_TABLE,
+                                                            VERTICAL_ALIGNMENT_STRING_TABLE_COUNT,
+                                                            verticalAlignment );
+    }
 
-  if( child.GetPropertyIndex(CELL_INDEX_PROPERTY_NAME) != Property::INVALID_INDEX )
-  {
-    Vector2 indices = child.GetProperty( child.GetPropertyIndex(CELL_INDEX_PROPERTY_NAME) ).Get<Vector2 >();
-    cellPosition.rowIndex = static_cast<unsigned int>( indices.x );
-    cellPosition.columnIndex = static_cast<unsigned int>( indices.y );
+    Toolkit::TableView::CellPosition cellPosition;
+    if( child.GetPropertyType( Toolkit::TableView::ChildProperty::ROW_SPAN ) != Property::NONE )
+    {
+      cellPosition.rowSpan = static_cast<unsigned int>( child.GetProperty( Toolkit::TableView::ChildProperty::ROW_SPAN ).Get<float>() );
+    }
 
-    AddChild( child, cellPosition );
-    SetCellAlignment(cellPosition, horizontalAlignment, verticalAlignment);
+    if( child.GetPropertyType( Toolkit::TableView::ChildProperty::COLUMN_SPAN ) != Property::NONE )
+    {
+      cellPosition.columnSpan = static_cast<unsigned int>( child.GetProperty( Toolkit::TableView::ChildProperty::COLUMN_SPAN ).Get<float>() );
+    }
 
-    // Do not continue
-    return;
-  }
+    if( child.GetPropertyType( Toolkit::TableView::ChildProperty::CELL_INDEX ) != Property::NONE )
+    {
+      Vector2 indices = child.GetProperty( Toolkit::TableView::ChildProperty::CELL_INDEX ).Get<Vector2 >();
+      cellPosition.rowIndex = static_cast<unsigned int>( indices.x );
+      cellPosition.columnIndex = static_cast<unsigned int>( indices.y );
 
-  // Find the first available cell to store the actor in
-  const unsigned int rowCount = mCellData.GetRows();
-  const unsigned int columnCount = mCellData.GetColumns();
-  for( unsigned int row = 0; row < rowCount; ++row )
-  {
-    for( unsigned int column = 0; column < columnCount; ++column )
+      AddChild( child, cellPosition );
+      SetCellAlignment(cellPosition, horizontalAlignment, verticalAlignment);
+    }
+    else
     {
-      if( !(mCellData[ row ][ column ].actor) )
+      bool availableCellFound = false;
+
+      // Find the first available cell to store the actor in
+      const unsigned int rowCount = mCellData.GetRows();
+      const unsigned int columnCount = mCellData.GetColumns();
+      for( unsigned int row = 0; row < rowCount && !availableCellFound; ++row )
       {
-        // Put the actor in the cell
+        for( unsigned int column = 0; column < columnCount && !availableCellFound; ++column )
+        {
+          if( !(mCellData[ row ][ column ].actor) )
+          {
+            // Put the actor in the cell
+            CellData data;
+            data.actor = child;
+            data.position.columnIndex = column;
+            data.position.rowIndex = row;
+            data.horizontalAlignment = horizontalAlignment;
+            data.verticalAlignment = verticalAlignment;
+            mCellData[ row ][ column ] = data;
+
+            availableCellFound = true;
+            break;
+          }
+        }
+      }
+
+      if( ! availableCellFound )
+      {
+        // No empty cells, so increase size of the table
+        unsigned int newColumnCount = ( columnCount > 0 ) ? columnCount : 1;
+        ResizeContainers( rowCount + 1, newColumnCount );
+
+        // Put the actor in the first cell of the new row
         CellData data;
         data.actor = child;
-        data.position.columnIndex = column;
-        data.position.rowIndex = row;
+        data.position.rowIndex = rowCount;
+        data.position.columnIndex = 0;
         data.horizontalAlignment = horizontalAlignment;
         data.verticalAlignment = verticalAlignment;
-        mCellData[ row ][ column ] = data;
-
-        // Don't continue
-        RelayoutRequest();
-        return;
+        mCellData[ rowCount ][ 0 ] = data;
       }
+
+      RelayoutRequest();
     }
   }
 
-  // No empty cells, so increase size of the table
-  unsigned int newColumnCount = ( columnCount > 0 ) ? columnCount : 1;
-  ResizeContainers( rowCount + 1, newColumnCount );
-
-  // Put the actor in the first cell of the new row
-  CellData data;
-  data.actor = child;
-  data.position.rowIndex = rowCount;
-  data.position.columnIndex = 0;
-  data.horizontalAlignment = horizontalAlignment;
-  data.verticalAlignment = verticalAlignment;
-  mCellData[ rowCount ][ 0 ] = data;
-  RelayoutRequest();
+  Control::OnChildAdd( child );
 }
 
-void TableView::OnControlChildRemove( Actor& child )
+void TableView::OnChildRemove( Actor& child )
 {
   // dont process if we're in the middle of bigger operation like delete row, column or resize
   if( !mLayoutingChild )
@@ -1072,11 +1071,14 @@ void TableView::OnControlChildRemove( Actor& child )
       RelayoutRequest();
     }
   }
+
+  Control::OnChildRemove( child );
 }
 
 TableView::TableView( unsigned int initialRows, unsigned int initialColumns )
-: Control( ControlBehaviour( REQUIRES_STYLE_CHANGE_SIGNALS ) ),
+: Control( ControlBehaviour( CONTROL_BEHAVIOUR_DEFAULT ) ),
   mCellData( initialRows, initialColumns ),
+  mPreviousFocusedActor(),
   mLayoutingChild( false ),
   mRowDirty( true ),     // Force recalculation first time
   mColumnDirty( true )
@@ -1197,8 +1199,8 @@ void TableView::SetHeightOrWidthProperty(TableView& tableViewImpl,
       if( childMap )
       {
         Property::Value* policy = childMap->Find( "policy" );
-        Property::Value* value = childMap->Find( "value" );
-        if( policy && value )
+        Property::Value* childMapValue = childMap->Find( "value" );
+        if( policy && childMapValue )
         {
           std::string policyValue;
           policy->Get( policyValue );
@@ -1210,11 +1212,11 @@ void TableView::SetHeightOrWidthProperty(TableView& tableViewImpl,
           {
             if( policy == Toolkit::TableView::FIXED  )
             {
-              (tableViewImpl.*funcFixed)( index, value->Get<float>() );
+              (tableViewImpl.*funcFixed)( index, childMapValue->Get<float>() );
             }
             else if( policy == Toolkit::TableView::RELATIVE )
             {
-              (tableViewImpl.*funcRelative)( index, value->Get<float>() );
+              (tableViewImpl.*funcRelative)( index, childMapValue->Get<float>() );
             }
             else if( policy == Toolkit::TableView::FIT )
             {
@@ -1323,51 +1325,78 @@ Actor TableView::GetNextKeyboardFocusableActor(Actor currentFocusedActor, Toolki
       int numberOfColumns = GetColumns();
       int numberOfRows = GetRows();
 
+      bool lastCell = false;
+      Actor nextValidActor;
+
       switch ( direction )
       {
         case Toolkit::Control::KeyboardFocus::LEFT:
         {
-          if(--currentColumn < 0)
+          do
           {
-            currentColumn = numberOfColumns - 1;
-            if(--currentRow < 0)
+            if(--currentColumn < 0)
             {
-              currentRow = loopEnabled ? numberOfRows - 1 : 0;
-              focusLost = (currentRow == 0);
+              currentColumn = numberOfColumns - 1;
+              if(--currentRow < 0)
+              {
+                lastCell = true;
+                currentRow = loopEnabled ? numberOfRows - 1 : 0;
+                focusLost = (currentRow == 0);
+              }
             }
-          }
+            nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+          } while ( !nextValidActor && !lastCell );
           break;
         }
         case Toolkit::Control::KeyboardFocus::RIGHT:
         {
-          if(++currentColumn > numberOfColumns - 1)
+          do
           {
-            currentColumn = 0;
-            if(++currentRow > numberOfRows - 1)
+            if(++currentColumn > numberOfColumns - 1)
             {
-              currentRow = loopEnabled ? 0 : numberOfRows - 1;
-              focusLost = (currentRow == numberOfRows - 1);
+              currentColumn = 0;
+              if(++currentRow > numberOfRows - 1)
+              {
+                lastCell = true;
+                currentRow = loopEnabled ? 0 : numberOfRows - 1;
+                focusLost = (currentRow == numberOfRows - 1);
+              }
             }
-          }
+            nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+          } while ( !nextValidActor && !lastCell );
           break;
         }
         case Toolkit::Control::KeyboardFocus::UP:
         {
-          if(--currentRow < 0)
+          do
           {
-            currentRow = loopEnabled ? numberOfRows - 1 : 0;
-            focusLost = (currentRow == 0);
-          }
+            if(--currentRow < 0)
+            {
+              lastCell = true;
+              currentRow = loopEnabled ? numberOfRows - 1 : 0;
+              focusLost = (currentRow == 0);
+            }
+            nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+          } while ( !nextValidActor && !lastCell );
           break;
         }
         case Toolkit::Control::KeyboardFocus::DOWN:
 
         {
-          if(++currentRow > numberOfRows - 1)
+          do
           {
-            currentRow = loopEnabled ? 0 : numberOfRows - 1;
-            focusLost = (currentRow == numberOfRows - 1);
-          }
+            if(++currentRow > numberOfRows - 1)
+            {
+              lastCell = true;
+              currentRow = loopEnabled ? 0 : numberOfRows - 1;
+              focusLost = (currentRow == numberOfRows - 1);
+            }
+            nextValidActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+          } while ( !nextValidActor && !lastCell );
+          break;
+        }
+        default:
+        {
           break;
         }
       }
@@ -1376,12 +1405,49 @@ Actor TableView::GetNextKeyboardFocusableActor(Actor currentFocusedActor, Toolki
       if(!focusLost)
       {
         nextFocusableActor = GetChildAt(Toolkit::TableView::CellPosition(currentRow, currentColumn));
+
+        // Save the focused actor in the TableView.
+        mPreviousFocusedActor = nextFocusableActor;
       }
     }
     else
     {
-      // The current focused actor is not within table view, so the child in the first cell should be focused.
-      nextFocusableActor = GetChildAt(Toolkit::TableView::CellPosition(0, 0));
+      // The current focused actor is not within this TableView.
+
+      unsigned int numberOfColumns = GetColumns();
+      unsigned int numberOfRows = GetRows();
+
+      // Check whether the previous focused actor is a focus group (i.e. a layout container)
+      bool wasFocusedOnLayoutContainer = false;
+      Actor previousFocusedActor = mPreviousFocusedActor.GetHandle();
+      if( previousFocusedActor )
+      {
+        Toolkit::Control control = Toolkit::Control::DownCast( previousFocusedActor );
+        if( control )
+        {
+          Internal::Control& controlImpl = static_cast<Internal::Control&>(control.GetImplementation());
+          wasFocusedOnLayoutContainer = controlImpl.IsKeyboardFocusGroup();
+        }
+      }
+
+      // Check whether the previous focused actor is a layout container and also a child of this TableView
+      Toolkit::TableView::CellPosition position;
+      if( wasFocusedOnLayoutContainer && FindChildPosition( previousFocusedActor, position ) )
+      {
+        nextFocusableActor = GetNextKeyboardFocusableActor(previousFocusedActor, direction, loopEnabled);
+      }
+      else
+      {
+        // Otherwise, move the focus to either the first or the last cell according to the given direction.
+        if(direction == Toolkit::Control::KeyboardFocus::LEFT || direction == Toolkit::Control::KeyboardFocus::UP)
+        {
+          nextFocusableActor = GetChildAt(Toolkit::TableView::CellPosition(numberOfRows - 1, numberOfColumns - 1));
+        }
+        else
+        {
+          nextFocusableActor = GetChildAt(Toolkit::TableView::CellPosition(0, 0));
+        }
+      }
     }
   }