rnd-19991005-1-src
authorHolger Schemel <info@artsoft.org>
Tue, 5 Oct 1999 00:10:36 +0000 (02:10 +0200)
committerHolger Schemel <info@artsoft.org>
Sat, 30 Aug 2014 08:34:27 +0000 (10:34 +0200)
src/files.c
src/files.h
src/main.c
src/main.h
src/misc.c
src/misc.h
src/network.c
src/screens.c

index b3d280c64b8357cc40aabeb64d3225d05935dff4..e0d444b1cd2ceba546f9227c70e6faab3ae68dff 100644 (file)
@@ -287,7 +287,7 @@ static char *getLevelFilename(int nr)
   filename = getPath3((leveldir_current->user_defined ?
                       getUserLevelDir("") :
                       options.level_directory),
-                     leveldir_current->filename,
+                     leveldir_current->fullpath,
                      basename);
 
   return filename;
@@ -1067,7 +1067,8 @@ void SaveScore(int level_nr)
 #define LEVELINFO_TOKEN_LEVELS         34
 #define LEVELINFO_TOKEN_FIRST_LEVEL    35
 #define LEVELINFO_TOKEN_SORT_PRIORITY  36
-#define LEVELINFO_TOKEN_READONLY       37
+#define LEVELINFO_TOKEN_LEVEL_GROUP    37
+#define LEVELINFO_TOKEN_READONLY       38
 
 #define FIRST_GLOBAL_SETUP_TOKEN       SETUP_TOKEN_PLAYER_NAME
 #define LAST_GLOBAL_SETUP_TOKEN                SETUP_TOKEN_TIME_LIMIT
@@ -1142,6 +1143,7 @@ static struct
   { TYPE_INTEGER, &ldi.levels,         "levels"                        },
   { TYPE_INTEGER, &ldi.first_level,    "first_level"                   },
   { TYPE_INTEGER, &ldi.sort_priority,  "sort_priority"                 },
+  { TYPE_BOOLEAN, &ldi.level_group,    "level_group"                   },
   { TYPE_BOOLEAN, &ldi.readonly,       "readonly"                      }
 };
 
@@ -1403,6 +1405,8 @@ static void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
 {
   ldi->filename = NULL;
+  ldi->fullpath = NULL;
+  ldi->basepath = NULL;
   ldi->name = getStringCopy(ANONYMOUS_NAME);
   ldi->name_short = NULL;
   ldi->name_sorting = NULL;
@@ -1412,11 +1416,19 @@ static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
   ldi->first_level = 0;
   ldi->last_level = 0;
   ldi->sort_priority = LEVELCLASS_UNDEFINED;   /* default: least priority */
-  ldi->readonly = TRUE;
+  ldi->level_group = FALSE;
+  ldi->parent_link = FALSE;
   ldi->user_defined = FALSE;
+  ldi->readonly = TRUE;
   ldi->color = 0;
   ldi->class_desc = NULL;
   ldi->handicap_level = 0;
+  ldi->cl_first = -1;
+  ldi->cl_cursor = -1;
+
+  ldi->node_parent = NULL;
+  ldi->node_group = NULL;
+  ldi->next = NULL;
 }
 
 static void setSetupInfoToDefaults(struct SetupInfo *si)
@@ -1529,31 +1541,15 @@ static void decodeSetupFileList(struct SetupFileList *setup_file_list)
   }
 }
 
-struct LevelDirInfo *getLevelDirInfoFromLevelDirName(char *level_dir_name)
-{
-  struct LevelDirInfo *leveldir_node = leveldir_first;
-
-  if (level_dir_name == NULL)
-    return NULL;
-
-  while (leveldir_node)
-  {
-    if (strcmp(level_dir_name, leveldir_node->name) == 0)
-      return leveldir_node;            /* return success value */
-
-    leveldir_node = leveldir_node->next;
-  }
-
-  return NULL;
-}
-
 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
 {
   const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
   const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
   int compare_result;
 
-  if (entry1->sort_priority == entry2->sort_priority)
+  if (entry1->parent_link || entry2->parent_link)
+    compare_result = (entry1->parent_link ? -1 : +1);
+  else if (entry1->sort_priority == entry2->sort_priority)
   {
     char *name1 = getStringToLower(entry1->name_sorting);
     char *name2 = getStringToLower(entry2->name_sorting);
@@ -1571,7 +1567,31 @@ static int compareLevelDirInfoEntries(const void *object1, const void *object2)
   return compare_result;
 }
 
-static void LoadLevelInfoFromLevelDir(char *level_directory)
+static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
+{
+  struct LevelDirInfo *leveldir_new = newLevelDirInfo();
+
+  setLevelDirInfoToDefaults(leveldir_new);
+
+  leveldir_new->node_parent = node_parent;
+  leveldir_new->parent_link = TRUE;
+
+  leveldir_new->name = ".. (parent directory)";
+  leveldir_new->name_short = getStringCopy(leveldir_new->name);
+  leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
+
+  leveldir_new->filename = "..";
+  leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
+
+  leveldir_new->sort_priority = node_parent->sort_priority;
+  leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
+
+  pushLevelDirInfo(&node_parent->node_group, leveldir_new);
+}
+
+static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
+                                     struct LevelDirInfo *node_parent,
+                                     char *level_directory)
 {
   DIR *dir;
   struct dirent *dir_entry;
@@ -1585,26 +1605,29 @@ static void LoadLevelInfoFromLevelDir(char *level_directory)
 
   while ((dir_entry = readdir(dir)) != NULL)   /* loop until last dir entry */
   {
+    struct SetupFileList *setup_file_list = NULL;
     struct stat file_status;
-    char *directory = NULL;
+    char *directory_name = dir_entry->d_name;
+    char *directory_path = getPath2(level_directory, directory_name);
     char *filename = NULL;
-    struct SetupFileList *setup_file_list = NULL;
 
     /* skip entries for current and parent directory */
-    if (strcmp(dir_entry->d_name, ".")  == 0 ||
-       strcmp(dir_entry->d_name, "..") == 0)
+    if (strcmp(directory_name, ".")  == 0 ||
+       strcmp(directory_name, "..") == 0)
+    {
+      free(directory_path);
       continue;
+    }
 
     /* find out if directory entry is itself a directory */
-    directory = getPath2(level_directory, dir_entry->d_name);
-    if (stat(directory, &file_status) != 0 ||          /* cannot stat file */
+    if (stat(directory_path, &file_status) != 0 ||     /* cannot stat file */
        (file_status.st_mode & S_IFMT) != S_IFDIR)      /* not a directory */
     {
-      free(directory);
+      free(directory_path);
       continue;
     }
 
-    filename = getPath2(directory, LEVELINFO_FILENAME);
+    filename = getPath2(directory_path, LEVELINFO_FILENAME);
     setup_file_list = loadSetupFileList(filename);
 
     if (setup_file_list)
@@ -1615,6 +1638,9 @@ static void LoadLevelInfoFromLevelDir(char *level_directory)
       checkSetupFileListIdentifier(setup_file_list, LEVELINFO_COOKIE);
       setLevelDirInfoToDefaults(leveldir_new);
 
+      leveldir_new->node_parent = node_parent;
+
+      /* set all structure fields according to the token/value pairs */
       ldi = *leveldir_new;
       for (i=FIRST_LEVELINFO_TOKEN; i<=LAST_LEVELINFO_TOKEN; i++)
        setSetupInfo(i, getTokenValue(setup_file_list, token_info[i].text));
@@ -1628,7 +1654,19 @@ static void LoadLevelInfoFromLevelDir(char *level_directory)
       if (leveldir_new->name_sorting == NULL)
        leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
 
-      leveldir_new->filename = getStringCopy(dir_entry->d_name);
+      leveldir_new->filename = getStringCopy(directory_name);
+
+      if (node_parent == NULL)         /* top level group */
+      {
+       leveldir_new->basepath = level_directory;
+       leveldir_new->fullpath = leveldir_new->filename;
+      }
+      else                             /* sub level group */
+      {
+       leveldir_new->basepath = node_parent->basepath;
+       leveldir_new->fullpath = getPath2(node_parent->fullpath,
+                                         directory_name);
+      }
 
       if (leveldir_new->levels < 1)
        leveldir_new->levels = 1;
@@ -1637,7 +1675,8 @@ static void LoadLevelInfoFromLevelDir(char *level_directory)
        leveldir_new->first_level + leveldir_new->levels - 1;
 
       leveldir_new->user_defined =
-       (level_directory == options.level_directory ? FALSE : TRUE);
+       (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
+
       leveldir_new->color = LEVELCOLOR(leveldir_new);
       leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
 
@@ -1646,15 +1685,25 @@ static void LoadLevelInfoFromLevelDir(char *level_directory)
         leveldir_new->last_level :
         leveldir_new->first_level);
 
-      pushLevelDirInfo(leveldir_new);  /* add new LevelDirInfo to list */
+      pushLevelDirInfo(node_first, leveldir_new);
 
       freeSetupFileList(setup_file_list);
       valid_entry_found = TRUE;
+
+      if (leveldir_new->level_group)
+      {
+       /* create node to link back to current level directory */
+       createParentLevelDirNode(leveldir_new);
+
+       /* step into sub-directory and look for more level series */
+       LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
+                                 leveldir_new, directory_path);
+      }
     }
     else
-      Error(ERR_WARN, "ignoring level directory '%s'", directory);
+      Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
 
-    free(directory);
+    free(directory_path);
     free(filename);
   }
 
@@ -1671,22 +1720,18 @@ void LoadLevelInfo()
 
   DrawInitText("Loading level series:", 120, FC_GREEN);
 
-  LoadLevelInfoFromLevelDir(options.level_directory);
-  LoadLevelInfoFromLevelDir(getUserLevelDir(""));
+  LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
+  LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
 
-  num_leveldirs = numLevelDirInfo(leveldir_first);
-  leveldir_current = leveldir_first;
+  leveldir_current = getFirstValidLevelSeries(leveldir_first);
 
-  if (num_leveldirs == 0)
+  if (leveldir_first == NULL)
     Error(ERR_EXIT, "cannot find any valid level series in any directory");
 
+  sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
+
 #if 0
-  if (num_leveldirs > 1)
-    qsort(leveldir, num_leveldirs, sizeof(struct LevelDirInfo),
-         compareLevelDirInfoEntries);
-#else
-  if (num_leveldirs > 1)
-    sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
+  dumpLevelDirInfo(leveldir_first, 0);
 #endif
 }
 
@@ -1902,7 +1947,7 @@ void LoadLevelSetup_LastSeries()
     char *last_level_series =
       getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
 
-    leveldir_current = getLevelDirInfoFromLevelDirName(last_level_series);
+    leveldir_current = getLevelDirInfoFromFilename(last_level_series);
     if (leveldir_current == NULL)
       leveldir_current = leveldir_first;
 
index cc09445886ae17236e45121e84d56598d9c94ec3..35fa3f10f7e3672b6043a5c5d019f3b9b6ef3c04 100644 (file)
@@ -25,8 +25,6 @@ void SaveTape(int);
 void LoadScore(int);
 void SaveScore(int);
 
-struct LevelDirInfo *getLevelDirInfoFromLevelDirName(char *);
-
 void LoadLevelInfo(void);
 void LoadSetup(void);
 void SaveSetup(void);
index 3182b47dd9d4cae4b3248d2e98cb5728fbc57ed0..78ce1be2a446e5e0d283171c3bf0d24e46fb98d2 100644 (file)
@@ -84,7 +84,7 @@ short         AmoebaCnt[MAX_NUM_AMOEBA], AmoebaCnt2[MAX_NUM_AMOEBA];
 unsigned long  Elementeigenschaften1[MAX_ELEMENTS];
 unsigned long  Elementeigenschaften2[MAX_ELEMENTS];
 
-int            level_nr, num_leveldirs;
+int            level_nr;
 int            lev_fieldx,lev_fieldy, scroll_x,scroll_y;
 
 int            FX = SX, FY = SY, ScrollStepSize;
index 0ec85dcbc3778cc25db81cfd31ecfaba0f863e73..9a58655c938dcde2be92647ef6ca913e5c1c7244 100644 (file)
@@ -384,7 +384,9 @@ struct LevelInfo
 
 struct LevelDirInfo
 {
-  char *filename;      /* level series sub-directory inside level directory */
+  char *filename;      /* level series single directory name */
+  char *fullpath;      /* complete path relative to level directory */
+  char *basepath;      /* absolute base path of level directory */
   char *name;          /* level series name, as displayed on main screen */
   char *name_short;    /* optional short name for level selection screen */
   char *name_sorting;  /* optional sorting name for correct level sorting */
@@ -394,13 +396,19 @@ struct LevelDirInfo
   int first_level;     /* first level number (to allow start with 0 or 1) */
   int last_level;      /* last level number (automatically calculated) */
   int sort_priority;   /* sort levels by 'sort_priority' and then by name */
+  boolean level_group; /* directory contains more level series directories */
+  boolean parent_link; /* entry links back to parent directory */
   boolean user_defined;        /* user defined levels are stored in home directory */
   boolean readonly;    /* readonly levels can not be changed with editor */
   int color;           /* color to use on selection screen for this level */
   char *class_desc;    /* description of level series class */
   int handicap_level;  /* number of the lowest unsolved level */
+  int cl_first;                /* internal control field for "choose level" screen */
+  int cl_cursor;       /* internal control field for "choose level" screen */
 
-  struct LevelDirInfo *next;
+  struct LevelDirInfo *node_parent;    /* parent level directory info */
+  struct LevelDirInfo *node_group;     /* level group sub-directory info */
+  struct LevelDirInfo *next;           /* next level series structure node */
 };
 
 struct TapeInfo
@@ -496,7 +504,7 @@ extern short                AmoebaCnt[MAX_NUM_AMOEBA], AmoebaCnt2[MAX_NUM_AMOEBA];
 extern unsigned long   Elementeigenschaften1[MAX_ELEMENTS];
 extern unsigned long   Elementeigenschaften2[MAX_ELEMENTS];
 
-extern int             level_nr, num_leveldirs;
+extern int             level_nr;
 extern int             lev_fieldx,lev_fieldy, scroll_x,scroll_y;
 
 extern int             FX,FY, ScrollStepSize;
index e60067ca3b6f293cd1ac60992655b3f76be67c3a..40bfad4bf83cea07b73f21b6fd9d25e5f0301fb1 100644 (file)
@@ -1076,10 +1076,11 @@ struct LevelDirInfo *newLevelDirInfo()
   return checked_calloc(sizeof(struct LevelDirInfo));
 }
 
-void pushLevelDirInfo(struct LevelDirInfo *node)
+void pushLevelDirInfo(struct LevelDirInfo **node_first,
+                     struct LevelDirInfo *node_new)
 {
-  node->next = leveldir_first;
-  leveldir_first = node;
+  node_new->next = *node_first;
+  *node_first = node_new;
 }
 
 int numLevelDirInfo(struct LevelDirInfo *node)
@@ -1095,9 +1096,47 @@ int numLevelDirInfo(struct LevelDirInfo *node)
   return num;
 }
 
+boolean validLevelSeries(struct LevelDirInfo *node)
+{
+  return (node != NULL && !node->node_group && !node->parent_link);
+}
+
+struct LevelDirInfo *getFirstValidLevelSeries(struct LevelDirInfo *node)
+{
+  if (node == NULL)            /* start with first level directory entry */
+    return getFirstValidLevelSeries(leveldir_first);
+  else if (node->node_group)   /* enter level group (step down into tree) */
+    return getFirstValidLevelSeries(node->node_group);
+  else if (node->parent_link)  /* skip start entry of level group */
+  {
+    if (node->next)            /* get first real level series entry */
+      return getFirstValidLevelSeries(node->next);
+    else                       /* leave empty level group and go on */
+      return getFirstValidLevelSeries(node->node_parent->next);
+  }
+  else                         /* this seems to be a regular level series */
+    return node;
+}
+
+struct LevelDirInfo *getLevelDirInfoFirstGroupEntry(struct LevelDirInfo *node)
+{
+  if (node == NULL)
+    return NULL;
+
+  if (node->node_parent == NULL)               /* top level group */
+    return leveldir_first;
+  else                                         /* sub level group */
+    return node->node_parent->node_group;
+}
+
+int numLevelDirInfoInGroup(struct LevelDirInfo *node)
+{
+  return numLevelDirInfo(getLevelDirInfoFirstGroupEntry(node));
+}
+
 int posLevelDirInfo(struct LevelDirInfo *node)
 {
-  struct LevelDirInfo *node_cmp = leveldir_first;
+  struct LevelDirInfo *node_cmp = getLevelDirInfoFirstGroupEntry(node);
   int pos = 0;
 
   while (node_cmp)
@@ -1129,6 +1168,58 @@ struct LevelDirInfo *getLevelDirInfoFromPos(struct LevelDirInfo *node, int pos)
   return node_default;
 }
 
+struct LevelDirInfo *getLevelDirInfoFromFilenameExt(struct LevelDirInfo *node,
+                                                   char *filename)
+{
+  if (filename == NULL)
+    return NULL;
+
+  while (node)
+  {
+    if (node->node_group)
+    {
+      struct LevelDirInfo *node_group;
+
+      node_group = getLevelDirInfoFromFilenameExt(node->node_group, filename);
+
+      if (node_group)
+       return node_group;
+    }
+    else if (!node->parent_link)
+    {
+      if (strcmp(filename, node->filename) == 0)
+       return node;
+    }
+
+    node = node->next;
+  }
+
+  return NULL;
+}
+
+struct LevelDirInfo *getLevelDirInfoFromFilename(char *filename)
+{
+  return getLevelDirInfoFromFilenameExt(leveldir_first, filename);
+}
+
+void dumpLevelDirInfo(struct LevelDirInfo *node, int depth)
+{
+  int i;
+
+  while (node)
+  {
+    for (i=0; i<depth * 3; i++)
+      printf(" ");
+
+    printf("filename == '%s'\n", node->filename);
+
+    if (node->node_group != NULL)
+      dumpLevelDirInfo(node->node_group, depth + 1);
+
+    node = node->next;
+  }
+}
+
 void sortLevelDirInfo(struct LevelDirInfo **node_first,
                      int (*compare_function)(const void *, const void *))
 {
@@ -1137,7 +1228,7 @@ void sortLevelDirInfo(struct LevelDirInfo **node_first,
   struct LevelDirInfo *node = *node_first;
   int i = 0;
 
-  if (num_nodes < 2)   /* a list with only one element is always sorted... */
+  if (num_nodes == 0)
     return;
 
   /* allocate array for sorting structure pointers */
@@ -1165,6 +1256,16 @@ void sortLevelDirInfo(struct LevelDirInfo **node_first,
   *node_first = sort_array[0];
 
   free(sort_array);
+
+  /* now recursively sort the level group structures */
+  node = *node_first;
+  while (node)
+  {
+    if (node->node_group != NULL)
+      sortLevelDirInfo(&node->node_group, compare_function);
+
+    node = node->next;
+  }
 }
 
 
index 591ca06b0f12820aae6881e4551b963eadafeb3a..d274922504752aff628b8b3c77cc48abb00e834d 100644 (file)
@@ -79,10 +79,16 @@ int getJoySymbolFromJoyName(char *);
 int getJoystickNrFromDeviceName(char *);
 
 struct LevelDirInfo *newLevelDirInfo();
-void pushLevelDirInfo(struct LevelDirInfo *);
+void pushLevelDirInfo(struct LevelDirInfo **, struct LevelDirInfo *);
 int numLevelDirInfo(struct LevelDirInfo *);
+boolean validLevelSeries(struct LevelDirInfo *);
+struct LevelDirInfo *getFirstValidLevelSeries(struct LevelDirInfo *);
+struct LevelDirInfo *getLevelDirInfoFirstGroupEntry(struct LevelDirInfo *);
+int numLevelDirInfoInGroup(struct LevelDirInfo *);
 int posLevelDirInfo(struct LevelDirInfo *);
 struct LevelDirInfo *getLevelDirInfoFromPos(struct LevelDirInfo *, int);
+struct LevelDirInfo *getLevelDirInfoFromFilename(char *);
+void dumpLevelDirInfo(struct LevelDirInfo *, int);
 void sortLevelDirInfo(struct LevelDirInfo **,
                      int (*compare_function)(const void *, const void *));
 
index b3b8eca226857d3457b9aebb35dacd70f716f06d..1c593270d2e1462fe15287c01dc581c21a02ed5c 100644 (file)
@@ -424,7 +424,7 @@ static void Handle_OP_START_PLAYING()
     (buffer[6] << 24) | (buffer[7] << 16) | (buffer[8] << 8) | (buffer[9]);
   new_leveldir_name = (char *)&buffer[10];
 
-  new_leveldir = getLevelDirInfoFromLevelDirName(new_leveldir_name);
+  new_leveldir = getLevelDirInfoFromFilename(new_leveldir_name);
   if (new_leveldir == NULL)
   {
     Error(ERR_WARN, "no such level directory: '%s'", new_leveldir_name);
index 4839ab3927153319d3009f12a7f81807d95f7536..abe7925a5c255c6841e7347296d47f1ea94b5cc3 100644 (file)
@@ -69,6 +69,7 @@ void DrawHeadline()
 
 void DrawMainMenu()
 {
+  static struct LevelDirInfo *leveldir_last_valid = NULL;
   int i;
   char *name_text = (!options.network && setup.team_mode ? "Team:" : "Name:");
 
@@ -90,7 +91,14 @@ void DrawMainMenu()
   /* map gadgets for main menu screen */
   MapTapeButtons();
 
-  /* level_nr may have set to value over handicap with level editor */
+  /* leveldir_current may be invalid (level group, parent link) */
+  if (!validLevelSeries(leveldir_current))
+    leveldir_current = getFirstValidLevelSeries(leveldir_last_valid);
+
+  /* store valid level series information */
+  leveldir_last_valid = leveldir_current;
+
+  /* level_nr may have been set to value over handicap with level editor */
   if (setup.handicap && level_nr > leveldir_current->handicap_level)
     level_nr = leveldir_current->handicap_level;
 
@@ -157,6 +165,35 @@ void DrawMainMenu()
 
 }
 
+static void gotoTopLevelDir()
+{
+  /* move upwards to top level directory */
+  while (leveldir_current->node_parent)
+  {
+    /* write a "path" into level tree for easy navigation to last level */
+    if (leveldir_current->node_parent->node_group->cl_first == -1)
+    {
+      int num_leveldirs = numLevelDirInfoInGroup(leveldir_current);
+      int leveldir_pos = posLevelDirInfo(leveldir_current);
+      int num_page_entries;
+      int cl_first, cl_cursor;
+
+      if (num_leveldirs <= MAX_LEVEL_SERIES_ON_SCREEN)
+       num_page_entries = num_leveldirs;
+      else
+       num_page_entries = MAX_LEVEL_SERIES_ON_SCREEN - 1;
+
+      cl_first = MAX(0, leveldir_pos - num_page_entries + 1);
+      cl_cursor = leveldir_pos - cl_first + 3;
+
+      leveldir_current->node_parent->node_group->cl_first = cl_first;
+      leveldir_current->node_parent->node_group->cl_cursor = cl_cursor;
+    }
+
+    leveldir_current = leveldir_current->node_parent;
+  }
+}
+
 void HandleMainMenu(int mx, int my, int dx, int dy, int button)
 {
   static int choice = 3;
@@ -260,11 +297,14 @@ void HandleMainMenu(int mx, int my, int dx, int dy, int button)
       }
       else if (y == 4)
       {
-       if (num_leveldirs)
+       if (leveldir_first)
        {
          game_status = CHOOSELEVEL;
          SaveLevelSetup_LastSeries();
          SaveLevelSetup_SeriesInfo();
+
+         gotoTopLevelDir();
+
          DrawChooseLevel();
        }
       }
@@ -824,6 +864,33 @@ void HandleTypeName(int newxpos, KeySym key)
   BackToFront();
 }
 
+static void drawCursorExt(int ypos, int color, int graphic)
+{
+  static int cursor_array[SCR_FIELDY];
+
+  if (graphic)
+    cursor_array[ypos] = graphic;
+
+  graphic = cursor_array[ypos];
+
+  if (color == FC_RED)
+    graphic = (graphic == GFX_ARROW_BLUE_LEFT  ? GFX_ARROW_RED_LEFT  :
+              graphic == GFX_ARROW_BLUE_RIGHT ? GFX_ARROW_RED_RIGHT :
+              GFX_KUGEL_ROT);
+
+  DrawGraphic(0, ypos, graphic);
+}
+
+static void initCursor(int ypos, int graphic)
+{
+  drawCursorExt(ypos, FC_BLUE, graphic);
+}
+
+static void drawCursor(int ypos, int color)
+{
+  drawCursorExt(ypos, color, 0);
+}
+
 void DrawChooseLevel()
 {
   UnmapAllGadgets();
@@ -842,7 +909,7 @@ static void AdjustChooseLevelScrollbar(int id, int first_entry)
   struct GadgetInfo *gi = screen_gadget[id];
   int items_max, items_visible, item_position;
 
-  items_max = num_leveldirs;
+  items_max = numLevelDirInfoInGroup(leveldir_current);
   items_visible = MAX_LEVEL_SERIES_ON_SCREEN - 1;
   item_position = first_entry;
 
@@ -858,6 +925,7 @@ static void drawChooseLevelList(int first_entry, int num_page_entries)
   int i;
   char buffer[SCR_FIELDX * 2];
   int max_buffer_len = (SCR_FIELDX - 2) * 2;
+  int num_leveldirs = numLevelDirInfoInGroup(leveldir_current);
 
   XFillRectangle(display, backbuffer, gc, SX, SY, SXSIZE - 32, SYSIZE);
   redraw_mask |= REDRAW_FIELD;
@@ -866,16 +934,24 @@ static void drawChooseLevelList(int first_entry, int num_page_entries)
 
   for(i=0; i<num_page_entries; i++)
   {
-    struct LevelDirInfo *leveldir_node;
+    struct LevelDirInfo *node, *node_first;
     int leveldir_pos = first_entry + i;
+    int ypos = i + 2;
 
-    leveldir_node = getLevelDirInfoFromPos(leveldir_first, leveldir_pos);
-    strncpy(buffer, leveldir_node->name , max_buffer_len);
+    node_first = getLevelDirInfoFirstGroupEntry(leveldir_current);
+    node = getLevelDirInfoFromPos(node_first, leveldir_pos);
+
+    strncpy(buffer, node->name , max_buffer_len);
     buffer[max_buffer_len] = '\0';
 
-    DrawText(SX + 32, SY + (i + 2) * 32, buffer, FS_MEDIUM,
-            leveldir_node->color);
-    DrawGraphic(0, i + 2, GFX_KUGEL_BLAU);
+    DrawText(SX + 32, SY + ypos * 32, buffer, FS_MEDIUM, node->color);
+
+    if (node->parent_link)
+      initCursor(ypos, GFX_ARROW_BLUE_LEFT);
+    else if (node->level_group)
+      initCursor(ypos, GFX_ARROW_BLUE_RIGHT);
+    else
+      initCursor(ypos, GFX_KUGEL_BLAU);
   }
 
   if (first_entry > 0)
@@ -887,16 +963,21 @@ static void drawChooseLevelList(int first_entry, int num_page_entries)
 
 static void drawChooseLevelInfo(int leveldir_pos)
 {
-  struct LevelDirInfo *leveldir_node;
+  struct LevelDirInfo *node, *node_first;
   int x, last_redraw_mask = redraw_mask;
 
-  leveldir_node = getLevelDirInfoFromPos(leveldir_first, leveldir_pos);
+  node_first = getLevelDirInfoFirstGroupEntry(leveldir_current);
+  node = getLevelDirInfoFromPos(node_first, leveldir_pos);
 
   XFillRectangle(display, drawto, gc, SX + 32, SY + 32, SXSIZE - 64, 32);
 
-  DrawTextFCentered(40, FC_RED, "%3d levels (%s)",
-                   leveldir_node->levels,
-                   leveldir_node->class_desc);
+  if (node->parent_link)
+    DrawTextFCentered(40, FC_RED, "leave group \"%s\"", node->class_desc);
+  else if (node->level_group)
+    DrawTextFCentered(40, FC_RED, "enter group \"%s\"", node->class_desc);
+  else
+    DrawTextFCentered(40, FC_RED, "%3d levels (%s)",
+                     node->levels, node->class_desc);
 
   /* let BackToFront() redraw only what is needed */
   redraw_mask = last_redraw_mask | REDRAW_TILES;
@@ -906,12 +987,11 @@ static void drawChooseLevelInfo(int leveldir_pos)
 
 void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
 {
-  static int choice = 3;
-  static int first_entry = -1;
   static unsigned long choose_delay = 0;
   static int redraw = TRUE;
   int x = (mx + 32 - SX) / 32, y = (my + 32 - SY) / 32;
   int step = (button == 1 ? 1 : button == 2 ? 5 : 10);
+  int num_leveldirs = numLevelDirInfoInGroup(leveldir_current);
   int num_page_entries;
 
   if (num_leveldirs <= MAX_LEVEL_SERIES_ON_SCREEN)
@@ -923,24 +1003,27 @@ void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
   {
     int leveldir_pos = posLevelDirInfo(leveldir_current);
 
-    if (first_entry == -1)
+    if (leveldir_current->cl_first == -1)
     {
-      first_entry = MAX(0, leveldir_pos - num_page_entries + 1);
-      choice = leveldir_pos - first_entry + 3;
-      AdjustChooseLevelScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, first_entry);
+      leveldir_current->cl_first = MAX(0, leveldir_pos - num_page_entries + 1);
+      leveldir_current->cl_cursor =
+       leveldir_pos - leveldir_current->cl_first + 3;
     }
 
-    if (dx == 1)       /* 'first_entry' is set by scrollbar position */
-      first_entry = dy;
+    if (dx == 999)     /* first entry is set by scrollbar position */
+      leveldir_current->cl_first = dy;
+    else
+      AdjustChooseLevelScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
+                                leveldir_current->cl_first);
 
-    drawChooseLevelList(first_entry, num_page_entries);
+    drawChooseLevelList(leveldir_current->cl_first, num_page_entries);
     drawChooseLevelInfo(leveldir_pos);
     redraw = TRUE;
   }
 
   if (redraw)
   {
-    DrawGraphic(0, choice - 1, GFX_KUGEL_ROT);
+    drawCursor(leveldir_current->cl_cursor - 1, FC_RED);
     redraw = FALSE;
   }
 
@@ -952,7 +1035,7 @@ void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
     if (dy)
     {
       x = 1;
-      y = choice + dy;
+      y = leveldir_current->cl_cursor + dy;
     }
     else
       x = y = 0;       /* no action */
@@ -968,33 +1051,37 @@ void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
 
   if (x == 1 && y == 2)
   {
-    if (first_entry > 0 &&
+    if (leveldir_current->cl_first > 0 &&
        (dy || DelayReached(&choose_delay, GADGET_FRAME_DELAY)))
     {
-      first_entry -= step;
-      if (first_entry < 0)
-       first_entry = 0;
-
-      drawChooseLevelList(first_entry, num_page_entries);
-      drawChooseLevelInfo(first_entry + choice - 3);
-      AdjustChooseLevelScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, first_entry);
-      DrawGraphic(0, choice - 1, GFX_KUGEL_ROT);
+      leveldir_current->cl_first -= step;
+      if (leveldir_current->cl_first < 0)
+       leveldir_current->cl_first = 0;
+
+      drawChooseLevelList(leveldir_current->cl_first, num_page_entries);
+      drawChooseLevelInfo(leveldir_current->cl_first +
+                         leveldir_current->cl_cursor - 3);
+      drawCursor(leveldir_current->cl_cursor - 1, FC_RED);
+      AdjustChooseLevelScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
+                                leveldir_current->cl_first);
       return;
     }
   }
   else if (x == 1 && y > num_page_entries + 2)
   {
-    if (first_entry + num_page_entries < num_leveldirs &&
+    if (leveldir_current->cl_first + num_page_entries < num_leveldirs &&
        (dy || DelayReached(&choose_delay, GADGET_FRAME_DELAY)))
     {
-      first_entry += step;
-      if (first_entry + num_page_entries > num_leveldirs)
-       first_entry = MAX(0, num_leveldirs - num_page_entries);
-
-      drawChooseLevelList(first_entry, num_page_entries);
-      drawChooseLevelInfo(first_entry + choice - 3);
-      AdjustChooseLevelScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL, first_entry);
-      DrawGraphic(0, choice - 1, GFX_KUGEL_ROT);
+      leveldir_current->cl_first += step;
+      if (leveldir_current->cl_first + num_page_entries > num_leveldirs)
+       leveldir_current->cl_first = MAX(0, num_leveldirs - num_page_entries);
+
+      drawChooseLevelList(leveldir_current->cl_first, num_page_entries);
+      drawChooseLevelInfo(leveldir_current->cl_first +
+                         leveldir_current->cl_cursor - 3);
+      drawCursor(leveldir_current->cl_cursor - 1, FC_RED);
+      AdjustChooseLevelScrollbar(SCREEN_CTRL_ID_SCROLL_VERTICAL,
+                                leveldir_current->cl_first);
       return;
     }
   }
@@ -1002,43 +1089,77 @@ void HandleChooseLevel(int mx, int my, int dx, int dy, int button)
   if (!mx && !my && !dx && !dy)
   {
     x = 1;
-    y = choice;
+    y = leveldir_current->cl_cursor;
+  }
+
+  if (dx == 1)
+  {
+    struct LevelDirInfo *node_first, *node_cursor;
+    int leveldir_pos =
+      leveldir_current->cl_first + leveldir_current->cl_cursor - 3;
+
+    node_first = getLevelDirInfoFirstGroupEntry(leveldir_current);
+    node_cursor = getLevelDirInfoFromPos(node_first, leveldir_pos);
+
+    if (node_cursor->node_group)
+    {
+      node_cursor->cl_first = leveldir_current->cl_first;
+      node_cursor->cl_cursor = leveldir_current->cl_cursor;
+      leveldir_current = node_cursor->node_group;
+      DrawChooseLevel();
+    }
+  }
+  else if (dx == -1 && leveldir_current->node_parent)
+  {
+    leveldir_current = leveldir_current->node_parent;
+    DrawChooseLevel();
   }
 
   if (x == 1 && y >= 3 && y <= num_page_entries + 2)
   {
     if (button)
     {
-      if (y != choice)
+      if (y != leveldir_current->cl_cursor)
       {
-        DrawGraphic(0, y - 1, GFX_KUGEL_ROT);
-        DrawGraphic(0, choice - 1, GFX_KUGEL_BLAU);
-       drawChooseLevelInfo(first_entry + y - 3);
-       choice = y;
+       drawCursor(y - 1, FC_RED);
+       drawCursor(leveldir_current->cl_cursor - 1, FC_BLUE);
+       drawChooseLevelInfo(leveldir_current->cl_first + y - 3);
+       leveldir_current->cl_cursor = y;
       }
     }
     else
     {
-      int leveldir_pos = first_entry + y - 3;
-
-      leveldir_current = getLevelDirInfoFromPos(leveldir_first, leveldir_pos);
+      struct LevelDirInfo *node_first, *node_cursor;
+      int leveldir_pos = leveldir_current->cl_first + y - 3;
 
-      LoadLevelSetup_SeriesInfo();
-
-      SaveLevelSetup_LastSeries();
-      SaveLevelSetup_SeriesInfo();
-      TapeErase();
+      node_first = getLevelDirInfoFirstGroupEntry(leveldir_current);
+      node_cursor = getLevelDirInfoFromPos(node_first, leveldir_pos);
 
+      if (node_cursor->node_group)
+      {
+       node_cursor->cl_first = leveldir_current->cl_first;
+       node_cursor->cl_cursor = leveldir_current->cl_cursor;
+       leveldir_current = node_cursor->node_group;
+       DrawChooseLevel();
+      }
+      else if (node_cursor->parent_link)
+      {
+       leveldir_current = node_cursor->node_parent;
+       DrawChooseLevel();
+      }
+      else
+      {
+       leveldir_current = node_cursor;
 
-      printf("first_level == %d, last_level == %d, levels == %d\n",
-            leveldir_current->first_level,
-            leveldir_current->last_level,
-            leveldir_current->levels);
+       LoadLevelSetup_SeriesInfo();
 
+       SaveLevelSetup_LastSeries();
+       SaveLevelSetup_SeriesInfo();
+       TapeErase();
 
-      game_status = MAINMENU;
-      DrawMainMenu();
-      redraw = TRUE;
+       game_status = MAINMENU;
+       DrawMainMenu();
+      }
     }
   }
 
@@ -1190,8 +1311,12 @@ void DrawSetupScreen()
 
     if (!(i >= SETUP_SCREEN_POS_EMPTY1 && i <= SETUP_SCREEN_POS_EMPTY2))
     {
-      DrawGraphic(0,i,GFX_KUGEL_BLAU);
       DrawText(SX+32,SY+i*32, setup_info[base].text, FS_BIG,FC_GREEN);
+
+      if (strcmp(setup_info[base].text, "Input Devices") == 0)
+       initCursor(i, GFX_ARROW_BLUE_RIGHT);
+      else
+       initCursor(i, GFX_KUGEL_BLAU);
     }
 
     if (setup_info[base].value)
@@ -1223,7 +1348,7 @@ void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
 
   if (redraw)
   {
-    DrawGraphic(0,choice-1,GFX_KUGEL_ROT);
+    drawCursor(choice - 1, FC_RED);
     redraw = FALSE;
   }
 
@@ -1255,6 +1380,13 @@ void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
     y = choice;
   }
 
+  if (dx == 1 && choice == 14)
+  {
+    game_status = SETUPINPUT;
+    DrawSetupInputScreen();
+    redraw = TRUE;
+  }
+
   if (x==1 && y >= pos_start && y <= pos_end &&
       !(y >= pos_empty1 && y <= pos_empty2))
   {
@@ -1262,8 +1394,8 @@ void HandleSetupScreen(int mx, int my, int dx, int dy, int button)
     {
       if (y!=choice)
       {
-       DrawGraphic(0,y-1,GFX_KUGEL_ROT);
-       DrawGraphic(0,choice-1,GFX_KUGEL_BLAU);
+       drawCursor(y - 1, FC_RED);
+       drawCursor(choice - 1, FC_BLUE);
       }
       choice = y;
     }
@@ -1440,10 +1572,11 @@ void DrawSetupInputScreen()
   ClearWindow();
   DrawText(SX+16, SY+16, "SETUP INPUT", FS_BIG, FC_YELLOW);
 
-  DrawGraphic(0, 2, GFX_KUGEL_BLAU);
-  DrawGraphic(0, 3, GFX_KUGEL_BLAU);
-  DrawGraphic(0, 4, GFX_KUGEL_BLAU);
-  DrawGraphic(0, 15, GFX_KUGEL_BLAU);
+  initCursor(2, GFX_KUGEL_BLAU);
+  initCursor(3, GFX_KUGEL_BLAU);
+  initCursor(4, GFX_ARROW_BLUE_RIGHT);
+  initCursor(15, GFX_KUGEL_BLAU);
+
   DrawGraphic(10, 2, GFX_ARROW_BLUE_LEFT);
   DrawGraphic(12, 2, GFX_ARROW_BLUE_RIGHT);
 
@@ -1569,7 +1702,7 @@ void HandleSetupInputScreen(int mx, int my, int dx, int dy, int button)
 
   if (redraw)
   {
-    DrawGraphic(0,choice-1,GFX_KUGEL_ROT);
+    drawCursor(choice - 1, FC_RED);
     redraw = FALSE;
   }
 
@@ -1630,8 +1763,8 @@ void HandleSetupInputScreen(int mx, int my, int dx, int dy, int button)
     {
       if (y != choice)
       {
-       DrawGraphic(0, y-1, GFX_KUGEL_ROT);
-       DrawGraphic(0, choice-1, GFX_KUGEL_BLAU);
+       drawCursor(y - 1, FC_RED);
+       drawCursor(choice - 1, FC_BLUE);
       }
       choice = y;
     }
@@ -2226,8 +2359,9 @@ static void CreateScreenScrollbars()
     struct GadgetInfo *gi;
     int items_max, items_visible, item_position;
     unsigned long event_mask;
-    int num_page_entries;
+    int num_page_entries = MAX_LEVEL_SERIES_ON_SCREEN - 1;
 
+#if 0
     if (num_leveldirs <= MAX_LEVEL_SERIES_ON_SCREEN)
       num_page_entries = num_leveldirs;
     else
@@ -2236,6 +2370,11 @@ static void CreateScreenScrollbars()
     items_max = MAX(num_leveldirs, num_page_entries);
     items_visible = num_page_entries;
     item_position = 0;
+#else
+    items_max = num_page_entries;
+    items_visible = num_page_entries;
+    item_position = 0;
+#endif
 
     event_mask = GD_EVENT_MOVING | GD_EVENT_OFF_BORDERS;
 
@@ -2278,6 +2417,7 @@ void CreateScreenGadgets()
 
 void MapChooseLevelGadgets()
 {
+  int num_leveldirs = numLevelDirInfoInGroup(leveldir_current);
   int i;
 
   if (num_leveldirs <= MAX_LEVEL_SERIES_ON_SCREEN)
@@ -2313,7 +2453,7 @@ static void HandleScreenGadgets(struct GadgetInfo *gi)
       break;
 
     case SCREEN_CTRL_ID_SCROLL_VERTICAL:
-      HandleChooseLevel(0,0, 1,gi->event.item_position, MB_MENU_INITIALIZE);
+      HandleChooseLevel(0,0, 999,gi->event.item_position, MB_MENU_INITIALIZE);
       break;
 
     default: