commit d18993acbcb4e36f4bd004b994e663aa16be0539
Author: Leszek Koltunski <leszek@koltunski.pl>
Date:   Thu Mar 26 14:37:20 2020 +0000

    Progress with Pretty Patterns.

diff --git a/src/main/java/org/distorted/dialog/RubikDialogPattern.java b/src/main/java/org/distorted/dialog/RubikDialogPattern.java
new file mode 100644
index 00000000..ab45ee9c
--- /dev/null
+++ b/src/main/java/org/distorted/dialog/RubikDialogPattern.java
@@ -0,0 +1,105 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2019 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.dialog;
+
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatDialogFragment;
+import android.support.design.widget.TabLayout;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.distorted.magic.R;
+import org.distorted.patterns.RubikPattern;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikDialogPattern extends AppCompatDialogFragment
+  {
+  RubikDialogPatternPagerAdapter mPagerAdapter;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void onStart()
+    {
+    super.onStart();
+
+    Window window = getDialog().getWindow();
+    window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+    window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @NonNull
+  @Override
+  public Dialog onCreateDialog(Bundle savedInstanceState)
+    {
+    FragmentActivity act = getActivity();
+    AlertDialog.Builder builder = new AlertDialog.Builder(act);
+
+    LayoutInflater layoutInflater = act.getLayoutInflater();
+    TextView tv = (TextView) layoutInflater.inflate(R.layout.dialog_title, null);
+    tv.setText(R.string.patterns);
+    builder.setCustomTitle(tv);
+
+    builder.setCancelable(true);
+    builder.setPositiveButton( R.string.ok, new DialogInterface.OnClickListener()
+      {
+      @Override
+      public void onClick(DialogInterface dialog, int which)
+        {
+
+        }
+      });
+
+    LayoutInflater inflater = act.getLayoutInflater();
+    final View view = inflater.inflate(R.layout.dialog_tabbed, null);
+    builder.setView(view);
+
+    ViewPager viewPager = view.findViewById(R.id.viewpager);
+    TabLayout tabLayout = view.findViewById(R.id.sliding_tabs);
+    mPagerAdapter = new RubikDialogPatternPagerAdapter(act, viewPager);
+    tabLayout.setupWithViewPager(viewPager);
+
+    int[] iconID = { R.drawable.cube2, R.drawable.cube3, R.drawable.cube4, R.drawable.cube5 };
+
+    for(int i=0; i< RubikPattern.NUM_CUBES; i++)
+      {
+      ImageView imageView = new ImageView(act);
+      imageView.setImageResource(iconID[i]);
+      TabLayout.Tab tab = tabLayout.getTabAt(i);
+      if(tab!=null) tab.setCustomView(imageView);
+      }
+
+    return builder.create();
+    }
+  }
diff --git a/src/main/java/org/distorted/dialog/RubikDialogPatternPagerAdapter.java b/src/main/java/org/distorted/dialog/RubikDialogPatternPagerAdapter.java
new file mode 100644
index 00000000..ba01a1b0
--- /dev/null
+++ b/src/main/java/org/distorted/dialog/RubikDialogPatternPagerAdapter.java
@@ -0,0 +1,114 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.dialog;
+
+import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import org.distorted.patterns.RubikPattern;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+class RubikDialogPatternPagerAdapter extends PagerAdapter
+  {
+  private FragmentActivity mAct;
+  private RubikDialogPatternView[] mViews;
+  private int mNumTabs;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// TODO: temporary mockup
+
+  private String[] createCategories(int pos)
+    {
+    String[] ret = new String[8];
+
+    for(int i=0; i<8; i++)
+      {
+      ret[i] = "CATEGORY "+pos+" "+i;
+      }
+
+    return ret;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  RubikDialogPatternPagerAdapter(FragmentActivity act, ViewPager viewPager)
+    {
+    mAct = act;
+    mNumTabs = RubikPattern.NUM_CUBES;
+    mViews = new RubikDialogPatternView[mNumTabs];
+
+    viewPager.setAdapter(this);
+    viewPager.setOffscreenPageLimit(mNumTabs-1);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  @NonNull
+  public Object instantiateItem(@NonNull ViewGroup collection, final int position)
+    {
+    mViews[position] = new RubikDialogPatternView(mAct);
+    collection.addView(mViews[position]);
+
+    String[] categories = createCategories(position);
+    final LinearLayout section = mViews[position].createSection(mAct, categories);
+
+    mAct.runOnUiThread(new Runnable()
+        {
+        @Override
+        public void run()
+          {
+          mViews[position].addSection(section);
+          }
+        });
+
+    return mViews[position];
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public void destroyItem(ViewGroup collection, int position, @NonNull Object view)
+    {
+    collection.removeView((View) view);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public int getCount()
+    {
+    return mNumTabs;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  @Override
+  public boolean isViewFromObject(@NonNull View view, @NonNull Object object)
+    {
+    return view == object;
+    }
+  }
diff --git a/src/main/java/org/distorted/dialog/RubikDialogPatternView.java b/src/main/java/org/distorted/dialog/RubikDialogPatternView.java
new file mode 100644
index 00000000..f6ff1a08
--- /dev/null
+++ b/src/main/java/org/distorted/dialog/RubikDialogPatternView.java
@@ -0,0 +1,97 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.dialog;
+
+import android.content.Context;
+import android.support.v4.app.FragmentActivity;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import org.distorted.magic.R;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikDialogPatternView extends FrameLayout
+  {
+  LinearLayout mLayout;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikDialogPatternView(Context context)
+    {
+    super(context);
+
+    View tab = inflate( context, R.layout.dialog_tab, null);
+    mLayout = tab.findViewById(R.id.tabLayout);
+    addView(tab);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  LinearLayout createSection(FragmentActivity act, final String[] categories)
+    {
+    LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+    LinearLayout layout = new LinearLayout(act);
+    layout.setLayoutParams(params);
+    layout.setOrientation(LinearLayout.VERTICAL);
+
+    DisplayMetrics metrics = act.getResources().getDisplayMetrics();
+    final float scale = metrics.density;
+    int len = categories.length;
+    int margin = (int)(3*scale + 0.5f);
+    LinearLayout.LayoutParams bParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.WRAP_CONTENT);
+    bParams.setMargins(margin, margin, margin, margin);
+
+    for(int i=0; i<len; i++)
+      {
+      android.util.Log.e("view", "adding button "+i +" to layout!");
+
+
+      final int fi = i;
+      Button button = new Button(act);
+      button.setLayoutParams(bParams);
+      button.setText(categories[i]);
+
+      button.setOnClickListener( new View.OnClickListener()
+        {
+        @Override
+        public void onClick(View view)
+          {
+          android.util.Log.e("view", "category "+categories[fi]+" clicked");
+          }
+        });
+      }
+
+    return layout;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// needs to run on UI thread
+
+  void addSection(LinearLayout section)
+    {
+    android.util.Log.e("view", "adding section to tab!");
+
+    mLayout.addView(section);
+    }
+  }
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScores.java b/src/main/java/org/distorted/dialog/RubikDialogScores.java
index 9c5d161e..0d9fb816 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScores.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScores.java
@@ -97,7 +97,7 @@ public class RubikDialogScores extends AppCompatDialogFragment
       }
 
     LayoutInflater inflater = act.getLayoutInflater();
-    final View view = inflater.inflate(R.layout.dialog_scores, null);
+    final View view = inflater.inflate(R.layout.dialog_tabbed, null);
     builder.setView(view);
 
     ViewPager viewPager = view.findViewById(R.id.viewpager);
diff --git a/src/main/java/org/distorted/dialog/RubikDialogScoresView.java b/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
index 22cf5169..506a1f6c 100644
--- a/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
+++ b/src/main/java/org/distorted/dialog/RubikDialogScoresView.java
@@ -157,7 +157,7 @@ public class RubikDialogScoresView extends FrameLayout
     {
     removeAllViews();
 
-    View tab = inflate(act, R.layout.dialog_scores_tab, null);
+    View tab = inflate(act, R.layout.dialog_tab, null);
     mLayout = tab.findViewById(R.id.tabLayout);
     addView(tab);
     }
diff --git a/src/main/java/org/distorted/magic/RubikActivity.java b/src/main/java/org/distorted/magic/RubikActivity.java
index e5c44599..905716ac 100644
--- a/src/main/java/org/distorted/magic/RubikActivity.java
+++ b/src/main/java/org/distorted/magic/RubikActivity.java
@@ -26,6 +26,7 @@ import android.support.v7.app.AppCompatActivity;
 import android.view.View;
 
 import org.distorted.dialog.RubikDialogAbout;
+import org.distorted.dialog.RubikDialogPattern;
 import org.distorted.dialog.RubikDialogScores;
 import org.distorted.dialog.RubikDialogEffects;
 import org.distorted.effect.BaseEffect;
@@ -227,7 +228,8 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
 
     public void Patterns(View v)
       {
-      android.util.Log.e("act", "Not implemented yet");
+      RubikDialogPattern diag = new RubikDialogPattern();
+      diag.show(getSupportFragmentManager(), null);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -241,8 +243,8 @@ public class RubikActivity extends AppCompatActivity implements View.OnClickList
 
     public void About(View v)
       {
-      RubikDialogAbout about = new RubikDialogAbout();
-      about.show(getSupportFragmentManager(), null);
+      RubikDialogAbout diag = new RubikDialogAbout();
+      diag.show(getSupportFragmentManager(), null);
       }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/main/java/org/distorted/patterns/RubikPattern.java b/src/main/java/org/distorted/patterns/RubikPattern.java
new file mode 100644
index 00000000..adcecdff
--- /dev/null
+++ b/src/main/java/org/distorted/patterns/RubikPattern.java
@@ -0,0 +1,399 @@
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// Copyright 2020 Leszek Koltunski                                                               //
+//                                                                                               //
+// This file is part of Magic Cube.                                                              //
+//                                                                                               //
+// Magic Cube 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.                                                           //
+//                                                                                               //
+// Magic Cube 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 Magic Cube.  If not, see <http://www.gnu.org/licenses/>.                           //
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+package org.distorted.patterns;
+
+import java.util.Vector;
+import android.graphics.Paint;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+public class RubikPattern
+{
+  private static final int MIN_CUBE  = 2;
+  private static final int MAX_CUBE  = 5;
+  public  static final int NUM_CUBES = MAX_CUBE-MIN_CUBE+1;
+
+  public int[] numCategories = new int[NUM_CUBES];
+  private static String buffer="";
+  private static Paint mPaint = new Paint();
+  private static int width;
+
+  private Vector<Category>[] mCategories;
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private class Category
+    {
+    private String name;
+    private int numpatterns;
+    private Vector<Pattern> patterns;
+    private int curPattern;
+
+    public Category(String n)
+      {
+      name=n;
+      curPattern=0;
+      numpatterns=0;
+      patterns = new Vector<>();
+      }
+    public void addPattern(Pattern p)
+      {
+      patterns.addElement(p);
+      numpatterns++;
+      }
+    public int getNumPatterns()
+    {
+    return numpatterns;
+    };
+    public String getName()
+    {
+    return name;
+    }
+    public void next()
+      {
+      curPattern++;
+      if( curPattern>=numpatterns )
+        curPattern=0;
+      }
+    public void prev()
+      {
+      curPattern--;
+      if( curPattern<0 )
+        curPattern=numpatterns-1;
+      }
+    public String getPatternName()
+      {
+      Pattern p = patterns.elementAt(curPattern);
+      return  p!=null ? p.getName(curPattern+1,numpatterns):null;
+      }
+    public int getPatternCurMove()
+      {
+      Pattern p = patterns.elementAt(curPattern);
+      return  p!=null ? p.getCurMove():-1;
+      }
+    public int getPatternNumMove()
+      {
+      Pattern p = patterns.elementAt(curPattern);
+      return  p!=null ? p.getNumMove():-1;
+      }
+    public void initializeCube()
+      {
+      Pattern p = patterns.elementAt(curPattern);
+      if( p!=null ) p.initializeCube();
+      }
+    public void makeNextMove()
+      {
+      Pattern p = patterns.elementAt(curPattern);
+      if( p!=null ) p.makeNextMove();
+      }
+    public void makePrevMove()
+      {
+      Pattern p = patterns.elementAt(curPattern);
+      if( p!=null ) p.makePrevMove();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private class Pattern
+    {
+    private String name;
+    private String shortname=null;
+    private String moves;
+    private int curmove;
+    private int nummove;
+
+    public Pattern(String n, String m)
+      {
+      name=n;
+      moves=m;
+      nummove = moves.length()/4;
+      curmove=nummove;
+      }
+    public String getName(int cur,int num)
+      {
+      if( shortname==null ) shortname=cutPatternName(name);
+      return shortname;
+      }
+    public int getNumMove()
+    {
+    return nummove;
+    }
+    public int getCurMove()
+    {
+    return curmove;
+    }
+    public void makeNextMove()
+      {
+      curmove++;
+      if( curmove>nummove)
+        {
+        curmove= 0;
+        initializeCube();
+        }
+      else
+        {
+
+// TODO
+//        RubikCube cube = world.getCube();
+//        if( cube!=null && !cube.makeMove(moves.substring(4*curmove-4,4*curmove)) )
+          curmove--;
+        }
+      }
+    public void makePrevMove()
+      {
+      curmove--;
+      if( curmove<0)
+        {
+        curmove=nummove;
+        initializeCube();
+        }
+      else
+        {
+// TODO
+//        RubikCube cube = world.getCube();
+//        if( cube!=null && !cube.backMove(moves.substring(4*curmove,4*curmove+4)) )
+          curmove++;
+        }
+      }
+    public void initializeCube()
+      {
+// TODO
+//	    RubikCube cube = world.getCube();
+//      if( cube!=null ) cube.setupPosition(moves.substring(0,4*curmove));
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public RubikPattern(int wi, int h)
+    {
+    width = wi;
+    mCategories = new Vector[NUM_CUBES];
+    mPaint.setTextSize(h);
+  
+    initializeCategories(0, RubikPatternData2.patterns);
+    initializeCategories(1, RubikPatternData3.patterns);
+    initializeCategories(2, RubikPatternData4.patterns);
+    initializeCategories(3, RubikPatternData5.patterns);
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private void initializeCategories(int num, String[] pat)
+    {
+    int colon;
+    mCategories[num] = new Vector<>();
+    Category cat=null;
+    String name, pattern;
+    Pattern patt;
+
+    numCategories[num]=0;
+
+    for(String p: pat)
+      {
+      colon = p.indexOf(':');
+
+      if( colon==-1 )
+        {
+        cat = new Category(p);
+        mCategories[num].addElement(cat);
+        numCategories[num]++;
+        }
+      else
+        {
+        pattern = p.substring(colon+1);
+        name    = p.substring(0,colon);
+        patt = new Pattern(name,pattern);
+        cat.addPattern(patt);
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  private static String cutPatternName(String n)
+    {
+    int len1 = (int)mPaint.measureText(n);
+  
+    if( len1>width )
+      {
+      int l = n.length();
+
+      while( l>=2 && len1>width )
+        {
+        l--;
+        buffer = n.substring(0,l);
+        len1 = (int)mPaint.measureText(buffer);
+        }
+
+      return buffer+"...";
+      }
+
+    return n;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getCategoryName(int size,int num)
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      return c!=null ? c.getName() : null;
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public String getPatternName(int size,int num)
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      return c!=null ? c.getPatternName() : null;
+      }
+
+    return null;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumPatterns(int size, int num )
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      return c!=null ? c.getNumPatterns() : 0;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void nextPattern(int size, int num )
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+
+      if( c!=null )
+        {
+// TODO
+//        RubikMenu.texDirty[0] = true;
+        c.next();
+        c.initializeCube();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void prevPattern(int size, int num )
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+
+      if( c!=null )
+        {
+// TODO
+//        RubikMenu.texDirty[0] = true;
+        c.prev();
+        c.initializeCube();
+        }
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCurrPattern(int size, int num )
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      return c!=null ? c.curPattern:0;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getCurMove(int size, int num )
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      return c!=null ? c.getPatternCurMove():0;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public int getNumMove(int size, int num )
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      return c!=null ? c.getPatternNumMove():0;
+      }
+
+    return 0;
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void makeNextMove(int size, int num)
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      if( c!=null ) c.makeNextMove();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void makePrevMove(int size, int num)
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      if( c!=null ) c.makePrevMove();
+      }
+    }
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+  public void initializeCube(int size,int num)
+    {
+    if( size>=MIN_CUBE && size<=MAX_CUBE && num>=0 && num< numCategories[size-MIN_CUBE] )
+      {
+      Category c = mCategories[size-MIN_CUBE].elementAt(num);
+      if( c!=null ) c.initializeCube();
+      }
+    }
+}
diff --git a/src/main/java/org/distorted/uistate/RubikState.java b/src/main/java/org/distorted/uistate/RubikState.java
index b08e0894..9daf594e 100644
--- a/src/main/java/org/distorted/uistate/RubikState.java
+++ b/src/main/java/org/distorted/uistate/RubikState.java
@@ -36,7 +36,7 @@ public enum RubikState
   private final RubikStateAbstract mClass;
   private static final RubikState[] sizes;
 
-  private static RubikState mCurrentState;
+  private static RubikState mCurrState, mPrevState;
 
   static
     {
@@ -61,36 +61,40 @@ public enum RubikState
 
   public static RubikState getCurrentState()
     {
-    return mCurrentState;
+    return mCurrState;
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static void savePreferences(SharedPreferences.Editor editor)
     {
-    editor.putInt("state", mCurrentState.ordinal() );
+    editor.putInt("curr_state", mCurrState.ordinal() );
+    editor.putInt("prev_state", mPrevState.ordinal() );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static void restorePreferences(SharedPreferences preferences)
     {
-    int stateOrdinal = preferences.getInt("state", RubikState.MAIN.ordinal() );
-    mCurrentState = getState(stateOrdinal);
+    int currState = preferences.getInt("curr_state", RubikState.MAIN.ordinal() );
+    int prevState = preferences.getInt("prev_state", RubikState.MAIN.ordinal() );
+
+    mCurrState = getState(currState);
+    mPrevState = getState(prevState);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static void goBack(RubikActivity act)
     {
-    switchState(act, mCurrentState.mBack );
+    switchState(act, mCurrState.mBack );
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
   public static void setState(RubikActivity act)
     {
-    mCurrentState.enterState(act);
+    mCurrState.enterState(act,mPrevState);
     }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -99,9 +103,10 @@ public enum RubikState
     {
     if( state!=null )
       {
-      if( mCurrentState!=null ) mCurrentState.leaveState(act);
-      state.enterState(act);
-      mCurrentState = state;
+      if( mCurrState!=null ) mCurrState.leaveState(act);
+      state.enterState(act,mCurrState);
+      mPrevState = mCurrState;
+      mCurrState = state;
       }
     else
       {
@@ -133,8 +138,8 @@ public enum RubikState
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  public void enterState(RubikActivity act)
+  public void enterState(RubikActivity act, RubikState prevState)
     {
-    mClass.enterState(act);
+    mClass.enterState(act,prevState);
     }
   }
\ No newline at end of file
diff --git a/src/main/java/org/distorted/uistate/RubikStateAbstract.java b/src/main/java/org/distorted/uistate/RubikStateAbstract.java
index 37b7d2f4..1d5912ef 100644
--- a/src/main/java/org/distorted/uistate/RubikStateAbstract.java
+++ b/src/main/java/org/distorted/uistate/RubikStateAbstract.java
@@ -28,7 +28,7 @@ public abstract class RubikStateAbstract
   {
   public static final int BUTTON_ID_BACK  = 1023;
 
-  abstract void enterState(RubikActivity act);
+  abstract void enterState(RubikActivity act, RubikState prev);
   abstract void leaveState(RubikActivity act);
   public abstract void savePreferences(SharedPreferences.Editor editor);
   public abstract void restorePreferences(SharedPreferences preferences);
diff --git a/src/main/java/org/distorted/uistate/RubikStateMain.java b/src/main/java/org/distorted/uistate/RubikStateMain.java
index 973686e4..7016f798 100644
--- a/src/main/java/org/distorted/uistate/RubikStateMain.java
+++ b/src/main/java/org/distorted/uistate/RubikStateMain.java
@@ -52,7 +52,7 @@ public class RubikStateMain extends RubikStateAbstract
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void enterState(RubikActivity act)
+  void enterState(RubikActivity act, RubikState prev)
     {
     FragmentManager mana = act.getSupportFragmentManager();
     RubikDialogMain diag = (RubikDialogMain) mana.findFragmentByTag(RubikDialogMain.getDialogTag());
diff --git a/src/main/java/org/distorted/uistate/RubikStatePlay.java b/src/main/java/org/distorted/uistate/RubikStatePlay.java
index 0fe2dad0..613fc0fc 100644
--- a/src/main/java/org/distorted/uistate/RubikStatePlay.java
+++ b/src/main/java/org/distorted/uistate/RubikStatePlay.java
@@ -65,7 +65,7 @@ public class RubikStatePlay extends RubikStateAbstract
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void enterState(final RubikActivity act)
+  void enterState(final RubikActivity act, RubikState prev)
     {
     LayoutInflater inflater = act.getLayoutInflater();
 
diff --git a/src/main/java/org/distorted/uistate/RubikStateSolving.java b/src/main/java/org/distorted/uistate/RubikStateSolving.java
index 70e1eff3..641bab52 100644
--- a/src/main/java/org/distorted/uistate/RubikStateSolving.java
+++ b/src/main/java/org/distorted/uistate/RubikStateSolving.java
@@ -60,7 +60,7 @@ public class RubikStateSolving extends RubikStateAbstract
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
-  void enterState(RubikActivity act)
+  void enterState(RubikActivity act, RubikState prev)
     {
     LayoutInflater inflater = act.getLayoutInflater();
 
diff --git a/src/main/res/layout/dialog_scores.xml b/src/main/res/layout/dialog_scores.xml
deleted file mode 100644
index d70ded5e..00000000
--- a/src/main/res/layout/dialog_scores.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_weight="1"
-    android:orientation="vertical" >
-
-    <android.support.design.widget.TabLayout
-        android:id="@+id/sliding_tabs"
-        android:layout_width="match_parent"
-        android:layout_height="32dp"
-        android:theme="@style/Theme.AppCompat.NoActionBar">
-    </android.support.design.widget.TabLayout>
-
-    <android.support.v4.view.ViewPager
-        android:id="@+id/viewpager"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:background="@android:color/black" />
-
-</LinearLayout>
diff --git a/src/main/res/layout/dialog_scores_tab.xml b/src/main/res/layout/dialog_scores_tab.xml
deleted file mode 100644
index 74f63cd8..00000000
--- a/src/main/res/layout/dialog_scores_tab.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/tabScrollView"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-
-    <LinearLayout
-        android:id="@+id/tabLayout"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical" >
-    </LinearLayout>
-
-</ScrollView>
-
diff --git a/src/main/res/layout/dialog_tab.xml b/src/main/res/layout/dialog_tab.xml
new file mode 100644
index 00000000..74f63cd8
--- /dev/null
+++ b/src/main/res/layout/dialog_tab.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tabScrollView"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:id="@+id/tabLayout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+    </LinearLayout>
+
+</ScrollView>
+
diff --git a/src/main/res/layout/dialog_tabbed.xml b/src/main/res/layout/dialog_tabbed.xml
new file mode 100644
index 00000000..d70ded5e
--- /dev/null
+++ b/src/main/res/layout/dialog_tabbed.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_weight="1"
+    android:orientation="vertical" >
+
+    <android.support.design.widget.TabLayout
+        android:id="@+id/sliding_tabs"
+        android:layout_width="match_parent"
+        android:layout_height="32dp"
+        android:theme="@style/Theme.AppCompat.NoActionBar">
+    </android.support.design.widget.TabLayout>
+
+    <android.support.v4.view.ViewPager
+        android:id="@+id/viewpager"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:background="@android:color/black" />
+
+</LinearLayout>
