fixed bug when using Shift+click (IntelliDraw) in editor using empty space
[rocksndiamonds.git] / src / editor.c
index 6a0e6be2afede472e3697a134d64e16370d5ecd6..12562f7671feb4c9f27e3e949e33845bf17e389f 100644 (file)
 #define INFOTEXT_UNKNOWN_ELEMENT       "unknown"
 
 
-/*
-  -----------------------------------------------------------------------------
-  screen and artwork graphic pixel position definitions
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// screen and artwork graphic pixel position definitions
+// ----------------------------------------------------------------------------
 
 // values for the control window
 #define ED_CTRL1_BUTTONS_HORIZ         4       // toolbox
 #define ED_ELEMENTLIST_YPOS            (editor.palette.y)
 #define ED_ELEMENTLIST_XSIZE           (graphic_info[IMG_EDITOR_PALETTE_BUTTON].width)
 #define ED_ELEMENTLIST_YSIZE           (graphic_info[IMG_EDITOR_PALETTE_BUTTON].height)
-#define ED_ELEMENTLIST_BUTTONS_HORIZ   (editor.palette.cols)
-#define ED_ELEMENTLIST_BUTTONS_VERT    (editor.palette.rows)
+#define ED_ELEMENTLIST_COLS            MAX(1, editor.palette.cols)
+#define ED_ELEMENTLIST_ROWS            MAX(1, editor.palette.rows)
+#define ED_ELEMENTLIST_BUTTONS_HORIZ   (ED_ELEMENTLIST_COLS)
+#define ED_ELEMENTLIST_BUTTONS_VERT    (ED_ELEMENTLIST_ROWS)
 #define ED_NUM_ELEMENTLIST_BUTTONS     (ED_ELEMENTLIST_BUTTONS_HORIZ * \
                                         ED_ELEMENTLIST_BUTTONS_VERT)
 
                                      SYSIZE - INFOTEXT_YSIZE)
 
 
-/*
-  -----------------------------------------------------------------------------
-  editor gadget definitions
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// editor gadget definitions
+// ----------------------------------------------------------------------------
 
 enum
 {
@@ -646,6 +644,8 @@ enum
   GADGET_ID_RANDOM_BALL_CONTENT,
   GADGET_ID_INITIAL_BALL_STATE,
   GADGET_ID_GROW_INTO_DIGGABLE,
+  GADGET_ID_SB_FIELDS_NEEDED,
+  GADGET_ID_SB_OBJECTS_NEEDED,
   GADGET_ID_AUTO_EXIT_SOKOBAN,
   GADGET_ID_SOLVED_BY_ONE_PLAYER,
   GADGET_ID_CONTINUOUS_SNAPPING,
@@ -949,6 +949,8 @@ enum
   ED_CHECKBUTTON_ID_RANDOM_BALL_CONTENT,
   ED_CHECKBUTTON_ID_INITIAL_BALL_STATE,
   ED_CHECKBUTTON_ID_GROW_INTO_DIGGABLE,
+  ED_CHECKBUTTON_ID_SB_FIELDS_NEEDED,
+  ED_CHECKBUTTON_ID_SB_OBJECTS_NEEDED,
   ED_CHECKBUTTON_ID_AUTO_EXIT_SOKOBAN,
   ED_CHECKBUTTON_ID_SOLVED_BY_ONE_PLAYER,
   ED_CHECKBUTTON_ID_CONTINUOUS_SNAPPING,
@@ -1079,11 +1081,9 @@ enum
 #define ED_DRAWING_ID_EDITOR_LAST      ED_DRAWING_ID_RANDOM_BACKGROUND
 
 
-/*
-  -----------------------------------------------------------------------------
-  some internally used definitions
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// some internally used definitions
+// ----------------------------------------------------------------------------
 
 // values for CopyLevelToUndoBuffer()
 #define UNDO_IMMEDIATE                 0
@@ -1138,11 +1138,9 @@ enum
 #define DEFAULT_EDITOR_TILESIZE_MM     TILESIZE
 
 
-/*
-  -----------------------------------------------------------------------------
-  some internally used data structure definitions
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// some internally used data structure definitions
+// ----------------------------------------------------------------------------
 
 static struct
 {
@@ -1154,8 +1152,8 @@ static struct
   char shortcut;
 } controlbutton_info[ED_NUM_CTRL_BUTTONS] =
 {
-  /* note: some additional characters are already reserved for "cheat mode"
-     shortcuts (":XYZ" style) -- for details, see "events.c" */
+  // note: some additional characters are already reserved for "cheat mode"
+  // shortcuts (":XYZ" style) -- for details, see "events.c"
 
   // ---------- toolbox control buttons ---------------------------------------
 
@@ -3076,10 +3074,24 @@ static struct
   },
   {
     ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(0),
+    GADGET_ID_SB_FIELDS_NEEDED,                GADGET_ID_NONE,
+    &level.sb_fields_needed,
+    NULL, NULL,
+    "all fields need to be filled",    "require all SB fields to be solved"
+  },
+  {
+    ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(0),
+    GADGET_ID_SB_OBJECTS_NEEDED,       GADGET_ID_NONE,
+    &level.sb_objects_needed,
+    NULL, NULL,
+    "all objects need to be placed",   "require all SB objects to be solved"
+  },
+  {
+    ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(1),
     GADGET_ID_AUTO_EXIT_SOKOBAN,       GADGET_ID_NONE,
     &level.auto_exit_sokoban,
     NULL, NULL,
-    "exit level if all fields solved", "automatically finish Sokoban levels"
+    "exit level if all tasks solved",  "automatically finish Sokoban levels"
   },
   {
     ED_ELEMENT_SETTINGS_XPOS(0),       ED_ELEMENT_SETTINGS_YPOS(14),
@@ -3747,11 +3759,9 @@ static struct
 };
 
 
-/*
-  -----------------------------------------------------------------------------
-  some internally used variables
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// some internally used variables
+// ----------------------------------------------------------------------------
 
 // maximal size of level editor drawing area
 static int MAX_ED_FIELDX, MAX_ED_FIELDY;
@@ -3788,6 +3798,8 @@ static int use_permanent_palette = TRUE;
 #define PYSIZE         (use_permanent_palette ? DYSIZE : SYSIZE)
 
 // forward declaration for internal use
+static void CopyBrushToCursor(int, int);
+static void DeleteBrushFromCursor(void);
 static void ModifyEditorCounterValue(int, int);
 static void ModifyEditorCounterLimits(int, int, int);
 static void ModifyEditorSelectboxValue(int, int);
@@ -5419,11 +5431,9 @@ editor_elements_info[] =
 };
 
 
-/*
-  -----------------------------------------------------------------------------
-  functions
-  -----------------------------------------------------------------------------
-*/
+// ----------------------------------------------------------------------------
+// functions
+// ----------------------------------------------------------------------------
 
 static int getMaxInfoTextLength(void)
 {
@@ -5750,7 +5760,7 @@ static void ReinitializeElementList(void)
       {
        // required for correct padding of palette headline buttons
        if (*editor_elements_info[i].headline_list_size > 0)
-         num_editor_elements += editor.palette.cols;
+         num_editor_elements += ED_ELEMENTLIST_COLS;
 
        for (j = 0; j < *editor_elements_info[i].headline_list_size; j++)
        {
@@ -5767,8 +5777,8 @@ static void ReinitializeElementList(void)
       // required for correct padding of palette element buttons
       int element_list_size = *editor_elements_info[i].element_list_size;
       int element_rows =
-       (element_list_size + editor.palette.cols - 1) / editor.palette.cols;
-      int element_buttons = editor.palette.cols * element_rows;
+       (element_list_size + ED_ELEMENTLIST_COLS - 1) / ED_ELEMENTLIST_COLS;
+      int element_buttons = ED_ELEMENTLIST_COLS * element_rows;
 
       num_editor_elements += element_buttons;
     }
@@ -5800,7 +5810,7 @@ static void ReinitializeElementList(void)
       {
        // required for correct padding of palette headline buttons
        int headline_size = (*editor_elements_info[i].headline_list_size > 0 ?
-                            editor.palette.cols : 0);
+                            ED_ELEMENTLIST_COLS : 0);
 
        for (j = 0; j < headline_size; j++)
        {
@@ -5822,8 +5832,8 @@ static void ReinitializeElementList(void)
       // required for correct padding of palette element buttons
       int element_list_size = *editor_elements_info[i].element_list_size;
       int element_rows =
-       (element_list_size + editor.palette.cols - 1) / editor.palette.cols;
-      int element_buttons = editor.palette.cols * element_rows;
+       (element_list_size + ED_ELEMENTLIST_COLS - 1) / ED_ELEMENTLIST_COLS;
+      int element_buttons = ED_ELEMENTLIST_COLS * element_rows;
 
       // copy all elements from element list
       for (j = 0; j < element_list_size; j++)
@@ -6682,9 +6692,9 @@ static void CreateSelectboxGadgets(void)
 
     if (selectbox_info[i].size == -1)  // dynamically determine size
     {
-      /* (we cannot use -1 for uninitialized values if we directly compare
-        with results from strlen(), because the '<' and '>' operation will
-        implicitely cast -1 to an unsigned integer value!) */
+      // (we cannot use -1 for uninitialized values if we directly compare
+      // with results from strlen(), because the '<' and '>' operation will
+      // implicitely cast -1 to an unsigned integer value!)
       selectbox_info[i].size = 0;
 
       for (j = 0; selectbox_info[i].options[j].text != NULL; j++)
@@ -7138,7 +7148,10 @@ void CreateLevelEditorGadgets(void)
   right_gadget_border =
     checked_calloc(num_editor_gadgets * sizeof(int));
 
-  editor_el_empty = checked_calloc(ED_NUM_ELEMENTLIST_BUTTONS * sizeof(int));
+  // set number of empty (padding) element buttons to maximum number of buttons
+  num_editor_el_empty = ED_NUM_ELEMENTLIST_BUTTONS;
+
+  editor_el_empty = checked_calloc(num_editor_el_empty * sizeof(int));
   editor_el_empty_ptr = editor_el_empty;
 
   use_permanent_palette = !editor.palette.show_as_separate_screen;
@@ -8490,29 +8503,31 @@ static boolean useEditorDoorAnimation(void)
   return (door_1_viewport_unchanged && door_1_contains_toolbox);
 }
 
+static void DrawEditorDoorBackground(int graphic, int x, int y,
+                                    int width, int height)
+{
+  struct GraphicInfo *g = &graphic_info[graphic];
+
+  if (g->bitmap != NULL)
+    BlitBitmap(g->bitmap, drawto, g->src_x, g->src_y,
+              MIN(width, g->width), MIN(height, g->height), x, y);
+  else
+    ClearRectangle(drawto, x, y, width, height);
+}
+
 static void DrawEditorDoorContent(void)
 {
   // needed for gadgets drawn on background (like palette scrollbar)
   SetDoorBackgroundImage(IMG_UNDEFINED);
 
   // copy default editor door content to main double buffer
-  BlitBitmap(graphic_info[IMG_BACKGROUND_PALETTE].bitmap, drawto,
-            graphic_info[IMG_BACKGROUND_PALETTE].src_x,
-            graphic_info[IMG_BACKGROUND_PALETTE].src_y,
-            MIN(DXSIZE, graphic_info[IMG_BACKGROUND_PALETTE].width),
-            MIN(DYSIZE, graphic_info[IMG_BACKGROUND_PALETTE].height),
-            DX, DY);
+  DrawEditorDoorBackground(IMG_BACKGROUND_PALETTE, DX, DY, DXSIZE, DYSIZE);
 
   // draw bigger door
   DrawSpecialEditorDoor();
 
   // draw new control window
-  BlitBitmap(graphic_info[IMG_BACKGROUND_TOOLBOX].bitmap, drawto,
-            graphic_info[IMG_BACKGROUND_TOOLBOX].src_x,
-            graphic_info[IMG_BACKGROUND_TOOLBOX].src_y,
-            MIN(EXSIZE, graphic_info[IMG_BACKGROUND_TOOLBOX].width),
-            MIN(EYSIZE, graphic_info[IMG_BACKGROUND_TOOLBOX].height),
-            EX, EY);
+  DrawEditorDoorBackground(IMG_BACKGROUND_TOOLBOX, EX, EY, EXSIZE, EYSIZE);
 
   // draw all toolbox gadgets to editor doors
   MapControlButtons();
@@ -8531,7 +8546,7 @@ void DrawLevelEd(void)
 
   FadeSoundsAndMusic();
 
-  if (CheckIfGlobalBorderOrPlayfieldViewportHasChanged())
+  if (CheckFadeAll())
     fade_mask = REDRAW_ALL;
 
   FadeOut(fade_mask);
@@ -9737,10 +9752,43 @@ static void SetAutomaticNumberOfGemsNeeded(void)
     {
       int element = Feld[x][y];
 
-      if (IS_GEM(element) ||
-         element == EL_MM_KETTLE ||
-         element == EL_DF_CELL)
-       level.gems_needed++;
+      switch (element)
+      {
+       case EL_EMERALD:
+       case EL_EMERALD_YELLOW:
+       case EL_EMERALD_RED:
+       case EL_EMERALD_PURPLE:
+       case EL_BD_DIAMOND:
+       case EL_WALL_EMERALD:
+       case EL_WALL_EMERALD_YELLOW:
+       case EL_WALL_EMERALD_RED:
+       case EL_WALL_EMERALD_PURPLE:
+       case EL_WALL_BD_DIAMOND:
+       case EL_NUT:
+       case EL_SP_INFOTRON:
+       case EL_MM_KETTLE:
+       case EL_DF_CELL:
+         level.gems_needed++;
+         break;
+
+       case EL_DIAMOND:
+       case EL_WALL_DIAMOND:
+         level.gems_needed += 3;
+         break;
+
+       case EL_PEARL:
+       case EL_WALL_PEARL:
+         level.gems_needed += 5;
+         break;
+
+       case EL_CRYSTAL:
+       case EL_WALL_CRYSTAL:
+         level.gems_needed += 8;
+         break;
+
+       default:
+         break;
+      }
     }
   }
 
@@ -9943,10 +9991,22 @@ static void DrawPropertiesConfig(void)
     MapCheckbuttonGadget(ED_CHECKBUTTON_ID_GROW_INTO_DIGGABLE);
   }
 
+  if (properties_element == EL_SOKOBAN_FIELD_EMPTY)
+    MapCheckbuttonGadget(ED_CHECKBUTTON_ID_SB_FIELDS_NEEDED);
+
+  if (properties_element == EL_SOKOBAN_OBJECT)
+    MapCheckbuttonGadget(ED_CHECKBUTTON_ID_SB_OBJECTS_NEEDED);
+
   if (properties_element == EL_SOKOBAN_OBJECT ||
       properties_element == EL_SOKOBAN_FIELD_EMPTY ||
       properties_element == EL_SOKOBAN_FIELD_FULL)
+  {
+    checkbutton_info[ED_CHECKBUTTON_ID_AUTO_EXIT_SOKOBAN].y =
+      ED_ELEMENT_SETTINGS_XPOS(properties_element == EL_SOKOBAN_FIELD_FULL ?
+                              0 : 1);
+
     MapCheckbuttonGadget(ED_CHECKBUTTON_ID_AUTO_EXIT_SOKOBAN);
+  }
 
   if (IS_BALLOON_ELEMENT(properties_element))
     MapSelectboxGadget(ED_SELECTBOX_ID_WIND_DIRECTION);
@@ -11600,11 +11660,21 @@ static void ResetIntelliDraw(void)
 
 static boolean draw_mode_hires = FALSE;
 
+static boolean isHiresTileElement(int element)
+{
+  return (IS_MM_WALL(element)        || element == EL_EMPTY);
+}
+
+static boolean isHiresDrawElement(int element)
+{
+  return (IS_MM_WALL_EDITOR(element) || element == EL_EMPTY);
+}
+
 static void SetDrawModeHiRes(int element)
 {
   draw_mode_hires =
     (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
-     (IS_MM_WALL_EDITOR(element) || element == EL_EMPTY));
+     isHiresDrawElement(element));
 }
 
 static boolean getDrawModeHiRes(void)
@@ -11769,8 +11839,8 @@ static void DrawArcExt(int from_x, int from_y, int to_x2, int to_y2,
 
   radius = (int)(sqrt((float)(len_x * len_x + len_y * len_y)) + 0.5);
 
-  /* not optimal (some points get drawn twice) but simple,
-     and fast enough for the few points we are drawing */
+  // not optimal (some points get drawn twice) but simple,
+  // and fast enough for the few points we are drawing
 
   for (x = 0; x <= radius; x++)
   {
@@ -11877,12 +11947,23 @@ static void SelectArea(int from_x, int from_y, int to_x, int to_y,
 }
 
 // values for CopyBrushExt()
-#define CB_AREA_TO_BRUSH       0
-#define CB_BRUSH_TO_CURSOR     1
-#define CB_BRUSH_TO_LEVEL      2
-#define CB_DELETE_OLD_CURSOR   3
-#define CB_DUMP_BRUSH          4
-#define CB_DUMP_BRUSH_SMALL    5
+#define CB_AREA_TO_BRUSH               0
+#define CB_BRUSH_TO_CURSOR             1
+#define CB_BRUSH_TO_LEVEL              2
+#define CB_DELETE_OLD_CURSOR           3
+#define CB_DUMP_BRUSH                  4
+#define CB_DUMP_BRUSH_SMALL            5
+#define CB_CLIPBOARD_TO_BRUSH          6
+#define CB_BRUSH_TO_CLIPBOARD          7
+#define CB_BRUSH_TO_CLIPBOARD_SMALL    8
+#define CB_UPDATE_BRUSH_POSITION       9
+
+#define MAX_CB_PART_SIZE       10
+#define MAX_CB_LINE_SIZE       (MAX_LEV_FIELDX + 1)    // text plus newline
+#define MAX_CB_NUM_LINES       (MAX_LEV_FIELDY)
+#define MAX_CB_TEXT_SIZE       (MAX_CB_LINE_SIZE *     \
+                                MAX_CB_NUM_LINES *     \
+                                MAX_CB_PART_SIZE)
 
 static void DrawBrushElement(int sx, int sy, int element, boolean change_level)
 {
@@ -11895,26 +11976,31 @@ static void CopyBrushExt(int from_x, int from_y, int to_x, int to_y,
   static short brush_buffer[MAX_LEV_FIELDX][MAX_LEV_FIELDY];
   static int brush_width, brush_height;
   static int last_cursor_x = -1, last_cursor_y = -1;
-  static boolean delete_old_brush;
+  static boolean delete_old_brush = FALSE;
   int new_element = BUTTON_ELEMENT(button);
   int x, y;
 
   if (mode == CB_DUMP_BRUSH ||
-      mode == CB_DUMP_BRUSH_SMALL)
+      mode == CB_DUMP_BRUSH_SMALL ||
+      mode == CB_BRUSH_TO_CLIPBOARD ||
+      mode == CB_BRUSH_TO_CLIPBOARD_SMALL)
   {
-    if (!draw_with_brush)
-    {
-      Error(ERR_WARN, "no brush selected");
-
+    if (edit_mode != ED_MODE_DRAWING)
       return;
-    }
 
-    for (y = 0; y < brush_height; y++)
+    char part[MAX_CB_PART_SIZE + 1] = "";
+    char text[MAX_CB_TEXT_SIZE + 1] = "";
+    int width  = (draw_with_brush ? brush_width  : lev_fieldx);
+    int height = (draw_with_brush ? brush_height : lev_fieldy);
+
+    for (y = 0; y < height; y++)
     {
-      for (x = 0; x < brush_width; x++)
+      for (x = 0; x < width; x++)
       {
-       int element = brush_buffer[x][y];
+       int element = (draw_with_brush ? brush_buffer[x][y] : Feld[x][y]);
        int element_mapped = element;
+       char *prefix = (mode == CB_DUMP_BRUSH ||
+                       mode == CB_BRUSH_TO_CLIPBOARD ? "`" : "¸");
 
        if (IS_CUSTOM_ELEMENT(element))
          element_mapped = EL_CUSTOM_START;
@@ -11923,13 +12009,176 @@ static void CopyBrushExt(int from_x, int from_y, int to_x, int to_y,
        else if (element >= NUM_FILE_ELEMENTS)
          element_mapped = EL_UNKNOWN;
 
-       // dump brush as level sketch text for the R'n'D forum:
+       // copy brush to level sketch text buffer for the R'n'D forum:
        // - large tiles: `xxx (0x60 ASCII)
        // - small tiles: ¸xxx (0xb8 ISO-8859-1, 0xc2b8 UTF-8)
-       printf("%s%03d", (mode == CB_DUMP_BRUSH ? "`" : "¸"), element_mapped);
+       snprintf(part, MAX_CB_PART_SIZE + 1, "%s%03d", prefix, element_mapped);
+       strcat(text, part);
       }
 
-      printf("\n");
+      strcat(text, "\n");
+    }
+
+    if (mode == CB_BRUSH_TO_CLIPBOARD ||
+       mode == CB_BRUSH_TO_CLIPBOARD_SMALL)
+      SDL_SetClipboardText(text);
+    else
+      printf("%s", text);
+
+    return;
+  }
+
+  if (mode == CB_CLIPBOARD_TO_BRUSH)
+  {
+    if (edit_mode != ED_MODE_DRAWING)
+      return;
+
+    if (!SDL_HasClipboardText())
+    {
+      Request("Clipboard is empty!", REQ_CONFIRM);
+
+      return;
+    }
+
+    boolean copy_to_brush = (draw_with_brush ||
+                            drawing_function == GADGET_ID_GRAB_BRUSH);
+
+    // this will delete the old brush, if already drawing with a brush
+    if (copy_to_brush)
+      ClickOnGadget(level_editor_gadget[GADGET_ID_SINGLE_ITEMS], MB_LEFTBUTTON);
+
+    // initialization is required for "odd" (incomplete) clipboard content
+    for (x = 0; x < MAX_LEV_FIELDX; x++)
+      for (y = 0; y < MAX_LEV_FIELDY; y++)
+       brush_buffer[x][y] = EL_EMPTY;
+
+    brush_width  = 0;
+    brush_height = 0;
+    x = 0;
+    y = 0;
+
+    char *clipboard_text = SDL_GetClipboardText();
+    char *ptr = clipboard_text;
+    boolean stop = FALSE;
+
+    while (*ptr && !stop)
+    {
+      boolean prefix_found = FALSE;
+
+      // level sketch element number prefixes (may be multi-byte characters)
+      char *prefix_list[] = { "`", "¸" };
+      int i;
+
+      for (i = 0; i < ARRAY_SIZE(prefix_list); i++)
+      {
+       char *prefix = prefix_list[i];
+
+       // check if string is large enough for prefix
+       if (strlen(ptr) < strlen(prefix))
+       {
+         stop = TRUE;
+
+         break;
+       }
+
+       // check if string starts with prefix
+       if (strPrefix(ptr, prefix))
+       {
+         ptr += strlen(prefix);
+
+         prefix_found = TRUE;
+
+         break;
+       }
+      }
+
+      // continue with next character if prefix not found
+      if (!prefix_found)
+      {
+       ptr++;          // !!! FIX THIS for real UTF-8 handling !!!
+
+       continue;
+      }
+
+      // continue with next character if prefix not found
+      if (strlen(ptr) < 3)
+       break;
+
+      if (ptr[0] >= '0' && ptr[0] <= '9' &&
+         ptr[1] >= '0' && ptr[1] <= '9' &&
+         ptr[2] >= '0' && ptr[2] <= '9')
+      {
+       int element = ((ptr[0] - '0') * 100 +
+                      (ptr[1] - '0') * 10 +
+                      (ptr[2] - '0'));
+
+       ptr += 3;
+
+       if (element >= NUM_FILE_ELEMENTS)
+         element = EL_UNKNOWN;
+
+       brush_buffer[x][y] = element;
+
+       brush_width  = MAX(x + 1, brush_width);
+       brush_height = MAX(y + 1, brush_height);
+
+       x++;
+
+       if (x >= MAX_LEV_FIELDX || *ptr == '\n')
+       {
+         x = 0;
+         y++;
+
+         if (y >= MAX_LEV_FIELDY)
+           stop = TRUE;
+       }
+      }
+    }
+
+    SDL_free(clipboard_text);
+
+    if (brush_width == 0 || brush_height == 0)
+    {
+      Request("No level sketch found in clipboard!", REQ_CONFIRM);
+
+      return;
+    }
+
+    if (copy_to_brush)
+    {
+      struct GadgetInfo *gi = level_editor_gadget[GADGET_ID_DRAWING_LEVEL];
+      int mx, my;
+
+      SDL_GetMouseState(&mx, &my);
+
+      // if inside drawing area, activate and draw brush at last mouse position
+      if (mx >= gi->x && mx < gi->x + gi->width &&
+         my >= gi->y && my < gi->y + gi->height)
+       CopyBrushToCursor(last_cursor_x, last_cursor_y);
+
+      draw_with_brush = TRUE;
+    }
+    else
+    {
+      char request[100];
+
+      sprintf(request, "Replace level with %dx%d level sketch from clipboard?",
+             brush_width, brush_height);
+
+      if (!Request(request, REQ_ASK))
+       return;
+
+      for (x = 0; x < MAX_LEV_FIELDX; x++)
+       for (y = 0; y < MAX_LEV_FIELDY; y++)
+         Feld[x][y] = brush_buffer[x][y];
+
+      lev_fieldx = level.fieldx = brush_width;
+      lev_fieldy = level.fieldy = brush_height;
+
+      SetBorderElement();
+
+      DrawEditModeWindow();
+      CopyLevelToUndoBuffer(UNDO_IMMEDIATE);
     }
 
     return;
@@ -11987,6 +12236,7 @@ static void CopyBrushExt(int from_x, int from_y, int to_x, int to_y,
        !IN_LEV_FIELD(cursor_x + level_xpos, cursor_y + level_ypos))
     {
       delete_old_brush = FALSE;
+
       return;
     }
 
@@ -12024,8 +12274,15 @@ static void CopyBrushExt(int from_x, int from_y, int to_x, int to_y,
 
     last_cursor_x = cursor_x;
     last_cursor_y = cursor_y;
+
     delete_old_brush = TRUE;
   }
+
+  if (mode == CB_UPDATE_BRUSH_POSITION)
+  {
+    last_cursor_x = from_x;
+    last_cursor_y = from_y;
+  }
 }
 
 static void CopyAreaToBrush(int from_x, int from_y, int to_x, int to_y,
@@ -12044,6 +12301,11 @@ static void CopyBrushToCursor(int x, int y)
   CopyBrushExt(x, y, 0, 0, 0, CB_BRUSH_TO_CURSOR);
 }
 
+static void UpdateBrushPosition(int x, int y)
+{
+  CopyBrushExt(x, y, 0, 0, 0, CB_UPDATE_BRUSH_POSITION);
+}
+
 static void DeleteBrushFromCursor(void)
 {
   CopyBrushExt(0, 0, 0, 0, 0, CB_DELETE_OLD_CURSOR);
@@ -12059,6 +12321,21 @@ void DumpBrush_Small(void)
   CopyBrushExt(0, 0, 0, 0, 0, CB_DUMP_BRUSH_SMALL);
 }
 
+void CopyClipboardToBrush(void)
+{
+  CopyBrushExt(0, 0, 0, 0, 0, CB_CLIPBOARD_TO_BRUSH);
+}
+
+void CopyBrushToClipboard(void)
+{
+  CopyBrushExt(0, 0, 0, 0, 0, CB_BRUSH_TO_CLIPBOARD);
+}
+
+void CopyBrushToClipboard_Small(void)
+{
+  CopyBrushExt(0, 0, 0, 0, 0, CB_BRUSH_TO_CLIPBOARD_SMALL);
+}
+
 static void FloodFill(int from_x, int from_y, int fill_element)
 {
   FloodFillLevel(from_x, from_y, fill_element, Feld, lev_fieldx, lev_fieldy);
@@ -12149,6 +12426,8 @@ static int DrawLevelText(int sx, int sy, char letter, int mode)
     case TEXT_SETCURSOR:
       DrawEditorElement(last_sx, last_sy, Feld[lx][ly]);
       DrawAreaBorder(sx, sy, sx, sy);
+      StartTextInput(SX + sx * ed_tilesize, SY + sy * ed_tilesize,
+                    ed_tilesize, ed_tilesize);
       last_sx = sx;
       last_sy = sy;
       break;
@@ -12184,7 +12463,7 @@ static int DrawLevelText(int sx, int sy, char letter, int mode)
       break;
 
     case TEXT_NEWLINE:
-      if (sy + 1 < ed_fieldy - 1 && ly + 1 < lev_fieldy - 1)
+      if (sy + 1 < ed_fieldy && ly + 1 < lev_fieldy)
        DrawLevelText(start_sx, sy + 1, 0, TEXT_SETCURSOR);
       else
        DrawLevelText(0, 0, 0, TEXT_END);
@@ -12193,6 +12472,7 @@ static int DrawLevelText(int sx, int sy, char letter, int mode)
     case TEXT_END:
       CopyLevelToUndoBuffer(UNDO_IMMEDIATE);
       DrawEditorElement(sx, sy, Feld[lx][ly]);
+      StopTextInput();
       typing = FALSE;
       break;
 
@@ -12435,9 +12715,13 @@ static void HandleDrawingAreas(struct GadgetInfo *gi)
   }
   else if (!button_press_event)
   {
+    int old_element = (IN_LEV_FIELD(lx, ly) ? Feld[lx][ly] : EL_UNDEFINED);
+    boolean hires_drawing = (level.game_engine_type == GAME_ENGINE_TYPE_MM &&
+                            isHiresTileElement(old_element) &&
+                            isHiresDrawElement(new_element));
+
     // prevent handling events for every pixel position when moving mouse
-    if ((sx == last_sx && sy == last_sy &&
-        !IS_MM_WALL_EDITOR(new_element) && new_element != EL_EMPTY) ||
+    if ((sx == last_sx && sy == last_sy && !hires_drawing) ||
        (sx2 == last_sx2 && sy2 == last_sy2))
       return;
   }
@@ -13571,8 +13855,8 @@ static void HandleControlButtons(struct GadgetInfo *gi)
 
     case GADGET_ID_SAVE:
     {
-      /* saving read-only levels into personal level set modifies global vars
-        "leveldir_current" and "level_nr"; restore them after saving level */
+      // saving read-only levels into personal level set modifies global vars
+      // "leveldir_current" and "level_nr"; restore them after saving level
       LevelDirTree *leveldir_former = leveldir_current;
       int level_nr_former = level_nr;
       char *level_filename;
@@ -14113,6 +14397,9 @@ static void HandleDrawingAreaInfo(struct GadgetInfo *gi)
       else
        DeleteBrushFromCursor();
     }
+
+    if (!draw_with_brush)
+      UpdateBrushPosition(sx, sy);
   }
   else if (actual_drawing_function == GADGET_ID_PICK_ELEMENT)
   {