commit 48e61813c5eb5ffde4bddd33bf14042254f41f3f
Author: leszek <leszek@koltunski.pl>
Date:   Sun Jan 21 12:53:00 2024 +0100

    progress

diff --git a/src/main/java/org/distorted/helpers/ObjectGridCreator.java b/src/main/java/org/distorted/helpers/ObjectGridCreator.java
index 286a6259..ff1e6f9a 100644
--- a/src/main/java/org/distorted/helpers/ObjectGridCreator.java
+++ b/src/main/java/org/distorted/helpers/ObjectGridCreator.java
@@ -20,6 +20,7 @@ import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.FrameLayout;
 import android.widget.GridLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
@@ -40,6 +41,7 @@ public class ObjectGridCreator
   private static final float TITLE_SIZE     = 0.8f;
   private static final float TEXT_SIZE      = 0.5f;
   private static final float TITLE_PADDING  = 0.15f;
+  private static final float TITLE_MARGIN   = 0.15f;
 
   private GridLayout mGrid;
   private GridLayout[] mCategoryGrids;
@@ -134,7 +136,7 @@ public class ObjectGridCreator
 
       int height = (int)(TITLE_SIZE*mCubeSize);
 
-      mROC = new RubikObjectCategories(sortMode);
+      mROC = RubikObjectCategories.create(sortMode);
       if( mROC.hasIcons() ) constructIconBasedGrid(act,layout,mROC,height);
       else                  constructIconlessGrid(act,layout,mROC,height);
       }
@@ -181,8 +183,12 @@ public class ObjectGridCreator
   private View constructTitle(Activity act, int iconID, int height)
     {
     ImageView view = new ImageView(act);
-    ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height);
+    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height);
+    int m = (int)(TITLE_MARGIN*height);
+    params.leftMargin = m;
+    params.rightMargin = m;
     view.setLayoutParams(params);
+
     view.setBackgroundResource(R.color.dark_grey);
 
     int p = (int)(TITLE_PADDING*height);
@@ -197,7 +203,10 @@ public class ObjectGridCreator
   private View constructTitle(Activity act, String title, int height)
     {
     TextView view = new TextView(act);
-    ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height);
+    FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,height);
+    int m = (int)(TITLE_MARGIN*height);
+    params.leftMargin = m;
+    params.rightMargin = m;
     view.setLayoutParams(params);
     view.setBackgroundResource(R.color.dark_grey);
     view.setGravity(Gravity.CENTER);
diff --git a/src/main/java/org/distorted/objects/RubikObjectCategories.java b/src/main/java/org/distorted/objects/RubikObjectCategories.java
index 6a4f0314..839d5687 100644
--- a/src/main/java/org/distorted/objects/RubikObjectCategories.java
+++ b/src/main/java/org/distorted/objects/RubikObjectCategories.java
@@ -13,11 +13,9 @@ import static org.distorted.main.MainSettingsPopup.*;
 
 import org.distorted.main.R;
 
-import java.util.Arrays;
-
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-public class RubikObjectCategories
+public abstract class RubikObjectCategories
 {
   public static final int[] DIFF_IDS =
     {
@@ -46,293 +44,39 @@ public class RubikObjectCategories
     R.drawable.difficulty5,
     };
 
-  private final int mNumCategories;
-  private int[][] mObjectIndices;
-  private int[] mIconIDs;
-  private String[] mTitles;
+  protected final int mNumCategories;
+  protected int[] mIconIDs;
+  protected String[] mTitles;
+  protected int[][] mObjectIndices;
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public RubikObjectCategories(int sortMode)
+  public RubikObjectCategories()
     {
-    switch(sortMode)
-      {
-      case SORT_CATEGORY  : buildSortCategory(); break;
-      case SORT_DIFFICULTY: buildSortDifficulty(); break;
-      case SORT_AUTHOR    : buildSortAuthor(); break;
-      case SORT_YEAR      : buildSortYear(); break;
-      }
-
+    buildIndices();
     mNumCategories = mObjectIndices.length;
-
-    switch(sortMode)
-      {
-      case SORT_CATEGORY  : buildIconsCategory(); break;
-      case SORT_DIFFICULTY: buildIconsDifficulty(); break;
-      case SORT_AUTHOR    : buildTitleAuthor(); break;
-      case SORT_YEAR      : buildTitleYear(); break;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSortStr(String[] data)
-    {
-    int numObjects = data.length;
-    String[] sorted = new String[numObjects];
-    System.arraycopy(data, 0, sorted, 0, numObjects);
-
-    Arrays.sort(sorted);
-
-    String[] categories = new String[numObjects];
-    int numCategories = 0;
-
-    for(int o=0; o<numObjects; o++)
-      if( o==0 || !sorted[o-1].equals(sorted[o]) )
-        {
-        categories[numCategories] = sorted[o];
-        numCategories++;
-        }
-
-    int lastChange = -1;
-    int curr = 0;
-    int[] numInCategory = new int[numCategories];
-
-    for(int o=0; o<numObjects; o++)
-      if( o==numObjects-1 || !sorted[o].equals(sorted[o+1]) )
-        {
-        numInCategory[curr] = o-lastChange;
-        curr++;
-        lastChange = o;
-        }
-
-    mObjectIndices = new int[numCategories][];
-
-    for(int c=0; c<numCategories; c++)
-      {
-      mObjectIndices[c] = new int[numInCategory[c]];
-
-      String cat = categories[c];
-      int index = 0;
-
-      for(int o=0; o<numObjects; o++)
-        if( data[o].equals(cat) ) mObjectIndices[c][index++] = o;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSortInt(int[] data)
-    {
-    int numObjects = data.length;
-    int[] sorted = new int[numObjects];
-    System.arraycopy(data, 0, sorted, 0, numObjects);
-
-    Arrays.sort(sorted);
-
-    int[] categories = new int[numObjects];
-    int numCategories = 0;
-
-    for(int o=0; o<numObjects; o++)
-      if( o==0 || sorted[o-1]!=sorted[o] )
-        {
-        categories[numCategories] = sorted[o];
-        numCategories++;
-        }
-
-    int lastChange = -1;
-    int curr = 0;
-    int[] numInCategory = new int[numCategories];
-
-    for(int o=0; o<numObjects; o++)
-      if( o==numObjects-1 || sorted[o]!=sorted[o+1] )
-        {
-        numInCategory[curr] = o-lastChange;
-        curr++;
-        lastChange = o;
-        }
-
-    mObjectIndices = new int[numCategories][];
-
-    for(int c=0; c<numCategories; c++)
-      {
-      mObjectIndices[c] = new int[numInCategory[c]];
-
-      int cat = categories[c];
-      int index = 0;
-
-      for(int o=0; o<numObjects; o++)
-        if( data[o]==cat ) mObjectIndices[c][index++] = o;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSortFloat(float[] data)
-    {
-    int numObjects = data.length;
-    float[] sorted = new float[numObjects];
-    System.arraycopy(data, 0, sorted, 0, numObjects);
-
-    Arrays.sort(sorted);
-
-    float[] categories = new float[numObjects];
-    int numCategories = 0;
-
-    for(int o=0; o<numObjects; o++)
-      if( o==0 || sorted[o-1]!=sorted[o] )
-        {
-        categories[numCategories] = sorted[o];
-        numCategories++;
-        }
-
-    int lastChange = -1;
-    int curr = 0;
-    int[] numInCategory = new int[numCategories];
-
-    for(int o=0; o<numObjects; o++)
-      if( o==numObjects-1 || sorted[o]!=sorted[o+1] )
-        {
-        numInCategory[curr] = o-lastChange;
-        curr++;
-        lastChange = o;
-        }
-
-    mObjectIndices = new int[numCategories][];
-
-    for(int c=0; c<numCategories; c++)
-      {
-      mObjectIndices[c] = new int[numInCategory[c]];
-
-      float cat = categories[c];
-      int index = 0;
-
-      for(int o=0; o<numObjects; o++)
-        if( data[o]==cat ) mObjectIndices[c][index++] = o;
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSortCategory()
-    {
-    int numObjects = RubikObjectList.getNumObjects();
-    int[] cats = new int[numObjects];
-
-    for(int o=0; o<numObjects; o++)
-      {
-      RubikObject obj = RubikObjectList.getObject(o);
-      cats[o] = obj==null ? 0 : obj.getCategory();
-      }
-
-    buildSortInt(cats);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSortDifficulty()
-    {
-    int numObjects = RubikObjectList.getNumObjects();
-    float[] diffs = new float[numObjects];
-
-    for(int o=0; o<numObjects; o++)
-      {
-      RubikObject obj = RubikObjectList.getObject(o);
-      diffs[o] = obj==null ? 0 : obj.getDifficulty();
-      }
-
-    buildSortFloat(diffs);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSortAuthor()
-    {
-    int numObjects = RubikObjectList.getNumObjects();
-    String[] auths = new String[numObjects];
-
-    for(int o=0; o<numObjects; o++)
-      {
-      RubikObject obj = RubikObjectList.getObject(o);
-      auths[o] = obj==null ? "" : obj.getAuthor();
-      }
-
-    buildSortStr(auths);
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildSortYear()
-    {
-    int numObjects = RubikObjectList.getNumObjects();
-    int[] years = new int[numObjects];
-
-    for(int o=0; o<numObjects; o++)
-      {
-      RubikObject obj = RubikObjectList.getObject(o);
-      years[o] = obj==null ? 0 : obj.getYearOfInvention();
-      }
-
-    buildSortInt(years);
+    buildTitle();
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void buildIconsCategory()
-    {
-    mIconIDs = new int[mNumCategories];
-
-    for(int t=0; t<mNumCategories; t++)
-      {
-      int obj = mObjectIndices[t][0];
-      RubikObject object = RubikObjectList.getObject(obj);
-      int category = object==null ? 0 : object.getCategory();
-      mIconIDs[t] = CATEGORY_IMAGES[category%5];  //TODO
-      }
-    }
+  abstract void buildIndices();
+  abstract void buildTitle();
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  private void buildIconsDifficulty()
+  public static RubikObjectCategories create(int sortMode)
     {
-    mIconIDs = new int[mNumCategories];
-
-    for(int t=0; t<mNumCategories; t++)
-      {
-      int obj = mObjectIndices[t][0];
-      RubikObject object = RubikObjectList.getObject(obj);
-      int difficulty = object==null ? 0 : (int)object.getDifficulty();
-      mIconIDs[t] = DIFF_IMAGES[difficulty];
-      }
-    }
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildTitleAuthor()
-    {
-    mTitles = new String[mNumCategories];
-
-    for(int t=0; t<mNumCategories; t++)
+    switch(sortMode)
       {
-      int obj = mObjectIndices[t][0];
-      RubikObject object = RubikObjectList.getObject(obj);
-      mTitles[t] = object==null ? "" : object.getAuthor();
+      case SORT_CATEGORY  : return new RubikObjectCategoriesCat();
+      case SORT_DIFFICULTY: return new RubikObjectCategoriesDiff();
+      case SORT_AUTHOR    : return new RubikObjectCategoriesAuthor();
+      case SORT_YEAR      : return new RubikObjectCategoriesYear();
       }
-    }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-  private void buildTitleYear()
-    {
-    mTitles = new String[mNumCategories];
-
-    for(int t=0; t<mNumCategories; t++)
-      {
-      int obj = mObjectIndices[t][0];
-      RubikObject object = RubikObjectList.getObject(obj);
-      int year = object==null ? 0 : object.getYearOfInvention();
-      mTitles[t] = year==0 ? "Unknown" : String.valueOf(year);
-      }
+    android.util.Log.e("D", "sortMode = "+sortMode+" not implemented!");
+    return null;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -394,6 +138,8 @@ public class RubikObjectCategories
       for(int object : mObjectIndices[c])
         if( object==objectIndex ) return c;
 
+    android.util.Log.e("D", "returning category -1: index: "+objectIndex);
+
     return -1;
     }
 
diff --git a/src/main/java/org/distorted/objects/RubikObjectCategoriesAuthor.java b/src/main/java/org/distorted/objects/RubikObjectCategoriesAuthor.java
new file mode 100644
index 00000000..3078b3b8
--- /dev/null
+++ b/src/main/java/org/distorted/objects/RubikObjectCategoriesAuthor.java
@@ -0,0 +1,91 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2024 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objects;
+
+import java.util.Arrays;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikObjectCategoriesAuthor extends RubikObjectCategories
+{
+  private void buildSort(String[] data)
+    {
+    int numObjects = data.length;
+    String[] sorted = new String[numObjects];
+    System.arraycopy(data, 0, sorted, 0, numObjects);
+
+    Arrays.sort(sorted);
+
+    String[] categories = new String[numObjects];
+    int numCategories = 0;
+
+    for(int o=0; o<numObjects; o++)
+      if( o==0 || !sorted[o-1].equals(sorted[o]) )
+        {
+        categories[numCategories] = sorted[o];
+        numCategories++;
+        }
+
+    int lastChange = -1;
+    int curr = 0;
+    int[] numInCategory = new int[numCategories];
+
+    for(int o=0; o<numObjects; o++)
+      if( o==numObjects-1 || !sorted[o].equals(sorted[o+1]) )
+        {
+        numInCategory[curr] = o-lastChange;
+        curr++;
+        lastChange = o;
+        }
+
+    mObjectIndices = new int[numCategories][];
+
+    for(int c=0; c<numCategories; c++)
+      {
+      mObjectIndices[c] = new int[numInCategory[c]];
+
+      String cat = categories[c];
+      int index = 0;
+
+      for(int o=0; o<numObjects; o++)
+        if( data[o].equals(cat) ) mObjectIndices[c][index++] = o;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildIndices()
+    {
+    int numObjects = RubikObjectList.getNumObjects();
+    String[] auths = new String[numObjects];
+
+    for(int o=0; o<numObjects; o++)
+      {
+      RubikObject obj = RubikObjectList.getObject(o);
+      auths[o] = obj==null ? "" : obj.getAuthor();
+      }
+
+    buildSort(auths);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildTitle()
+    {
+    mTitles = new String[mNumCategories];
+
+    for(int t=0; t<mNumCategories; t++)
+      {
+      int obj = mObjectIndices[t][0];
+      RubikObject object = RubikObjectList.getObject(obj);
+      mTitles[t] = object==null ? "" : object.getAuthor();
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/objects/RubikObjectCategoriesCat.java b/src/main/java/org/distorted/objects/RubikObjectCategoriesCat.java
new file mode 100644
index 00000000..e2c7be7e
--- /dev/null
+++ b/src/main/java/org/distorted/objects/RubikObjectCategoriesCat.java
@@ -0,0 +1,92 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2024 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objects;
+
+import java.util.Arrays;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikObjectCategoriesCat extends RubikObjectCategories
+{
+  private void buildSort(int[] data)
+    {
+    int numObjects = data.length;
+    int[] sorted = new int[numObjects];
+    System.arraycopy(data, 0, sorted, 0, numObjects);
+
+    Arrays.sort(sorted);
+
+    int[] categories = new int[numObjects];
+    int numCategories = 0;
+
+    for(int o=0; o<numObjects; o++)
+      if( o==0 || sorted[o-1]!=sorted[o] )
+        {
+        categories[numCategories] = sorted[o];
+        numCategories++;
+        }
+
+    int lastChange = -1;
+    int curr = 0;
+    int[] numInCategory = new int[numCategories];
+
+    for(int o=0; o<numObjects; o++)
+      if( o==numObjects-1 || sorted[o]!=sorted[o+1] )
+        {
+        numInCategory[curr] = o-lastChange;
+        curr++;
+        lastChange = o;
+        }
+
+    mObjectIndices = new int[numCategories][];
+
+    for(int c=0; c<numCategories; c++)
+      {
+      mObjectIndices[c] = new int[numInCategory[c]];
+
+      int cat = categories[c];
+      int index = 0;
+
+      for(int o=0; o<numObjects; o++)
+        if( data[o]==cat ) mObjectIndices[c][index++] = o;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildIndices()
+    {
+    int numObjects = RubikObjectList.getNumObjects();
+    int[] cats = new int[numObjects];
+
+    for(int o=0; o<numObjects; o++)
+      {
+      RubikObject obj = RubikObjectList.getObject(o);
+      cats[o] = obj==null ? 0 : obj.getCategory();
+      }
+
+    buildSort(cats);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildTitle()
+    {
+    mIconIDs = new int[mNumCategories];
+
+    for(int t=0; t<mNumCategories; t++)
+      {
+      int obj = mObjectIndices[t][0];
+      RubikObject object = RubikObjectList.getObject(obj);
+      int category = object==null ? 0 : object.getCategory();
+      mIconIDs[t] = CATEGORY_IMAGES[category%5];  //TODO
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/objects/RubikObjectCategoriesDiff.java b/src/main/java/org/distorted/objects/RubikObjectCategoriesDiff.java
new file mode 100644
index 00000000..c8fa5b6f
--- /dev/null
+++ b/src/main/java/org/distorted/objects/RubikObjectCategoriesDiff.java
@@ -0,0 +1,102 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2024 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objects;
+
+import java.util.Arrays;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikObjectCategoriesDiff extends RubikObjectCategories
+{
+  private void buildSort(float[] data)
+    {
+    int numObjects = data.length;
+    float[] sorted = new float[numObjects];
+    System.arraycopy(data, 0, sorted, 0, numObjects);
+
+    Arrays.sort(sorted);
+
+    int[] categories = new int[numObjects];
+    int numCategories = 0;
+
+    for(int o=0; o<numObjects; o++)
+      if( o==0 || (int)sorted[o-1] != (int)sorted[o] )
+        {
+        categories[numCategories] = (int)sorted[o];
+        numCategories++;
+        }
+
+    int lastChange = -1;
+    int curr = 0;
+    int[] numInCategory = new int[numCategories];
+
+    for(int o=0; o<numObjects; o++)
+      if( o==numObjects-1 || (int)sorted[o] != (int)sorted[o+1] )
+        {
+        numInCategory[curr] = o-lastChange;
+        curr++;
+        lastChange = o;
+        }
+
+    mObjectIndices = new int[numCategories][];
+
+    boolean[] taken = new boolean[numObjects];
+    for(int i=0; i<numObjects; i++) taken[i] = false;
+
+    for(int c=0; c<numCategories; c++)
+      {
+      mObjectIndices[c] = new int[numInCategory[c]];
+
+      int cat = categories[c];
+      int index = 0;
+
+      for(int o=0; o<numObjects; o++)
+        if( (int)sorted[o]==cat )
+          for(int n=0; n<numObjects; n++)
+            if( data[n]==sorted[o] && !taken[n] )
+              {
+              taken[n]=true;
+              mObjectIndices[c][index++] = n;
+              break;
+              }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildIndices()
+    {
+    int numObjects = RubikObjectList.getNumObjects();
+    float[] diffs = new float[numObjects];
+
+    for(int o=0; o<numObjects; o++)
+      {
+      RubikObject obj = RubikObjectList.getObject(o);
+      diffs[o] = obj==null ? 0 : obj.getDifficulty();
+      }
+
+    buildSort(diffs);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildTitle()
+    {
+    mIconIDs = new int[mNumCategories];
+
+    for(int t=0; t<mNumCategories; t++)
+      {
+      int obj = mObjectIndices[t][0];
+      RubikObject object = RubikObjectList.getObject(obj);
+      int difficulty = object==null ? 0 : (int)object.getDifficulty();
+      mIconIDs[t] = DIFF_IMAGES[difficulty];
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/objects/RubikObjectCategoriesYear.java b/src/main/java/org/distorted/objects/RubikObjectCategoriesYear.java
new file mode 100644
index 00000000..4ec24d02
--- /dev/null
+++ b/src/main/java/org/distorted/objects/RubikObjectCategoriesYear.java
@@ -0,0 +1,92 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2024 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube is proprietary software licensed under an EULA which you should have received      //
+// along with the code. If not, check https://distorted.org/magic/License-Magic-Cube.html        //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.objects;
+
+import java.util.Arrays;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikObjectCategoriesYear extends RubikObjectCategories
+{
+  private void buildSort(int[] data)
+    {
+    int numObjects = data.length;
+    int[] sorted = new int[numObjects];
+    System.arraycopy(data, 0, sorted, 0, numObjects);
+
+    Arrays.sort(sorted);
+
+    int[] categories = new int[numObjects];
+    int numCategories = 0;
+
+    for(int o=0; o<numObjects; o++)
+      if( o==0 || sorted[o-1]!=sorted[o] )
+        {
+        categories[numCategories] = sorted[o];
+        numCategories++;
+        }
+
+    int lastChange = -1;
+    int curr = 0;
+    int[] numInCategory = new int[numCategories];
+
+    for(int o=0; o<numObjects; o++)
+      if( o==numObjects-1 || sorted[o]!=sorted[o+1] )
+        {
+        numInCategory[curr] = o-lastChange;
+        curr++;
+        lastChange = o;
+        }
+
+    mObjectIndices = new int[numCategories][];
+
+    for(int c=0; c<numCategories; c++)
+      {
+      mObjectIndices[c] = new int[numInCategory[c]];
+
+      int cat = categories[c];
+      int index = 0;
+
+      for(int o=0; o<numObjects; o++)
+        if( data[o]==cat ) mObjectIndices[c][index++] = o;
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildIndices()
+    {
+    int numObjects = RubikObjectList.getNumObjects();
+    int[] years = new int[numObjects];
+
+    for(int o=0; o<numObjects; o++)
+      {
+      RubikObject obj = RubikObjectList.getObject(o);
+      years[o] = obj==null ? 0 : obj.getYearOfInvention();
+      }
+
+    buildSort(years);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  void buildTitle()
+    {
+    mTitles = new String[mNumCategories];
+
+    for(int t=0; t<mNumCategories; t++)
+      {
+      int obj = mObjectIndices[t][0];
+      RubikObject object = RubikObjectList.getObject(obj);
+      int year = object==null ? 0 : object.getYearOfInvention();
+      mTitles[t] = year==0 ? "Unknown" : String.valueOf(year);
+      }
+    }
+}
