From b2a8f46e980ebb11cb4daf2c5d99993855ba9c29 Mon Sep 17 00:00:00 2001
From: Nicolas "Pixel" Noble <pixel@nobis-crew.org>
Date: Thu, 9 Sep 2010 10:17:40 +0200
Subject: [PATCH] Trying to improve the addformattag behavior in order to avoid scrolling physically and destrying the selection.
 Adds the concept of BULK formattags.

---
 iup/src/gtk/iupgtk_text.c |  206 ++++++++++++++++++++++++++-------------------
 iup/src/iup_text.c        |   31 +++++++-
 iup/src/iup_text.h        |    4 +-
 iup/src/iup_user.c        |    2 +-
 iup/src/mot/iupmot_text.c |   14 +++-
 iup/src/win/iupwin_text.c |   53 ++++++++++--
 6 files changed, 212 insertions(+), 98 deletions(-)

diff --git a/iup/src/gtk/iupgtk_text.c b/iup/src/gtk/iupgtk_text.c
index d0909d7..c5961de 100755
--- a/iup/src/gtk/iupgtk_text.c
+++ b/iup/src/gtk/iupgtk_text.c
@@ -406,69 +406,79 @@ static void gtkTextScrollToVisible(Ihandle* ih)
 /*******************************************************************************************/
 
 
+static int gtkTextSelectionAttribToIter(Ihandle* ih, const char* value, GtkTextIter* start_iter, GtkTextIter* end_iter)
+{
+  int lin_start=1, col_start=1, lin_end=1, col_end=1;
+  GtkTextBuffer *buffer;
+
+  if (!value || !ih->data->is_multiline)
+    return 0;
+
+  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
+
+  if (!value || iupStrEqualNoCase(value, "NONE"))
+  {
+    gtk_text_buffer_get_start_iter(buffer, start_iter);
+    gtk_text_buffer_get_start_iter(buffer, end_iter);
+    return 1;
+  }
+
+  if (iupStrEqualNoCase(value, "ALL"))
+  {
+    gtk_text_buffer_get_start_iter(buffer, start_iter);
+    gtk_text_buffer_get_end_iter(buffer, end_iter);
+    return 1;
+  }
+
+  if (sscanf(value, "%d,%d:%d,%d", &lin_start, &col_start, &lin_end, &col_end)!=4) return 0;
+  if (lin_start<1 || col_start<1 || lin_end<1 || col_end<1) return 0;
+
+  gtkTextMoveIterToLinCol(buffer, start_iter, lin_start, col_start);
+  gtkTextMoveIterToLinCol(buffer, end_iter, lin_end, col_end);
+
+  return 1;
+}
+
 static int gtkTextSetSelectionAttrib(Ihandle* ih, const char* value)
 {
+  int start=1, end=1;
+
   if (!value)
     return 0;
 
-  if (!value || iupStrEqualNoCase(value, "NONE"))
+  if (ih->data->is_multiline)
   {
-    if (ih->data->is_multiline)
+    GtkTextIter start_iter, end_iter;
+    if (gtkTextSelectionAttribToIter(ih, value, &start_iter, &end_iter))
     {
-      GtkTextIter start_iter;
       GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
-      gtk_text_buffer_get_start_iter(buffer, &start_iter);
       gtk_text_buffer_select_range(buffer, &start_iter, &start_iter);
     }
-    else
-      gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, 0);
     return 0;
   }
 
-  if (iupStrEqualNoCase(value, "ALL"))
+  if (iupStrEqualNoCase(value, "NONE"))
   {
-    if (ih->data->is_multiline)
-    {
-      GtkTextIter start_iter;
-      GtkTextIter end_iter;
-      GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
-      gtk_text_buffer_get_start_iter(buffer, &start_iter);
-      gtk_text_buffer_get_end_iter(buffer, &end_iter);
-      gtk_text_buffer_select_range(buffer, &start_iter, &end_iter);
-    }
-    else
-      gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, -1);
+    gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, 0);
     return 0;
   }
 
-  if (ih->data->is_multiline)
+  if (iupStrEqualNoCase(value, "ALL"))
   {
-    int lin_start=1, col_start=1, lin_end=1, col_end=1;
-    GtkTextIter start_iter, end_iter;
-    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
-
-    if (sscanf(value, "%d,%d:%d,%d", &lin_start, &col_start, &lin_end, &col_end)!=4) return 0;
-    if (lin_start<1 || col_start<1 || lin_end<1 || col_end<1) return 0;
-
-    gtkTextMoveIterToLinCol(buffer, &start_iter, lin_start, col_start);
-    gtkTextMoveIterToLinCol(buffer, &end_iter, lin_end, col_end);
-
-    gtk_text_buffer_select_range(buffer, &start_iter, &end_iter);
+    gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, -1);
+    return 0;
   }
-  else
-  {
-    int start=1, end=1;
-    if (iupStrToIntInt(value, &start, &end, ':')!=2) 
-      return 0;
 
-    if(start<1 || end<1) 
-      return 0;
+  if (iupStrToIntInt(value, &start, &end, ':')!=2) 
+    return 0;
 
-    start--; /* IUP starts at 1 */
-    end--;
+  if(start<1 || end<1) 
+    return 0;
 
-    gtk_editable_select_region(GTK_EDITABLE(ih->handle), start, end);
-  }
+  start--; /* IUP starts at 1 */
+  end--;
+
+  gtk_editable_select_region(GTK_EDITABLE(ih->handle), start, end);
 
   return 0;
 }
@@ -509,6 +519,41 @@ static char* gtkTextGetSelectionAttrib(Ihandle* ih)
   return NULL;
 }
 
+static int gtkTextSelectionPosAttribToIter(Ihandle* ih, const char* value, GtkTextIter* start_iter, GtkTextIter* end_iter)
+{
+  int start=0, end=0;
+  GtkTextBuffer *buffer;
+
+  if (!value || !ih->data->is_multiline)
+    return 0;
+
+  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
+  if (!value || iupStrEqualNoCase(value, "NONE"))
+  {
+    gtk_text_buffer_get_start_iter(buffer, start_iter);
+    gtk_text_buffer_get_start_iter(buffer, end_iter);
+    return 1;
+  }
+
+  if (iupStrEqualNoCase(value, "ALL"))
+  {
+    gtk_text_buffer_get_start_iter(buffer, start_iter);
+    gtk_text_buffer_get_end_iter(buffer, end_iter);
+    return 1;
+  }
+
+  if (iupStrToIntInt(value, &start, &end, ':')!=2) 
+    return 0;
+
+  if(start<0 || end<0) 
+    return 0;
+
+  gtk_text_buffer_get_iter_at_offset(buffer, start_iter, start);
+  gtk_text_buffer_get_iter_at_offset(buffer, end_iter, end);
+
+  return 1;
+}
+
 static int gtkTextSetSelectionPosAttrib(Ihandle* ih, const char* value)
 {
   int start=0, end=0;
@@ -516,33 +561,26 @@ static int gtkTextSetSelectionPosAttrib(Ihandle* ih, const char* value)
   if (!value)
     return 0;
 
-  if (!value || iupStrEqualNoCase(value, "NONE"))
+  if (ih->data->is_multiline)
   {
-    if (ih->data->is_multiline)
+    GtkTextIter start_iter, end_iter;
+    if (gtkTextSelectionPosAttribToIter(ih, value, &start_iter, &end_iter))
     {
-      GtkTextIter start_iter;
       GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
-      gtk_text_buffer_get_start_iter(buffer, &start_iter);
       gtk_text_buffer_select_range(buffer, &start_iter, &start_iter);
     }
-    else
-      gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, 0);
+    return 0;
+  }
+
+  if (iupStrEqualNoCase(value, "NONE"))
+  {
+    gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, 0);
     return 0;
   }
 
   if (iupStrEqualNoCase(value, "ALL"))
   {
-    if (ih->data->is_multiline)
-    {
-      GtkTextIter start_iter;
-      GtkTextIter end_iter;
-      GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
-      gtk_text_buffer_get_start_iter(buffer, &start_iter);
-      gtk_text_buffer_get_end_iter(buffer, &end_iter);
-      gtk_text_buffer_select_range(buffer, &start_iter, &end_iter);
-    }
-    else
-      gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, -1);
+    gtk_editable_select_region(GTK_EDITABLE(ih->handle), 0, -1);
     return 0;
   }
 
@@ -552,18 +590,7 @@ static int gtkTextSetSelectionPosAttrib(Ihandle* ih, const char* value)
   if(start<0 || end<0) 
     return 0;
 
-  if (ih->data->is_multiline)
-  {
-    GtkTextIter start_iter, end_iter;
-    GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
-
-    gtk_text_buffer_get_iter_at_offset(buffer, &start_iter, start);
-    gtk_text_buffer_get_iter_at_offset(buffer, &end_iter, end);
-
-    gtk_text_buffer_select_range(buffer, &start_iter, &end_iter);
-  }
-  else
-    gtk_editable_select_region(GTK_EDITABLE(ih->handle), start, end);
+  gtk_editable_select_region(GTK_EDITABLE(ih->handle), start, end);
 
   return 0;
 }
@@ -1117,8 +1144,21 @@ static char* gtkTextGetOverwriteAttrib(Ihandle* ih)
     return "NO";
 }
 
-void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag)
+void* iupdrvTextAddFormatTagStartBulk(Ihandle* ih)
 {
+  (void)ih;
+}
+
+void iupdrvTextAddFormatTagStopBulk(Ihandle* ih, void* state)
+{
+  (void)ih;
+  (void)state;
+}
+
+void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag, int bulk)
+{
+  (void)bulk;
+
   GtkTextBuffer *buffer;
   GtkTextIter start_iter, end_iter;
   GtkTextTag* tag;
@@ -1130,36 +1170,28 @@ void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag)
   selection = iupAttribGet(formattag, "SELECTION");
   if (selection)
   {
-    /* simulate Windows behavior and change the current selection */
-    gtkTextSetSelectionAttrib(ih, selection);
-    iupAttribSetStr(ih, "SELECTION", NULL);
+    if (!gtkTextSelectionAttribToIter(ih, selection, &start_iter, &end_iter))
+      return;
   }
   else
   {
     char* selectionpos = iupAttribGet(formattag, "SELECTIONPOS");
     if (selectionpos)
     {
-      /* simulate Windows behavior and change the current selection */
-      gtkTextSetSelectionPosAttrib(ih, selectionpos);
-      iupAttribSetStr(ih, "SELECTIONPOS", NULL);
+      if (!gtkTextSelectionPosAttribToIter(ih, selectionpos, &start_iter, &end_iter))
+        return;
+    } else {
+      GtkTextMark* mark = gtk_text_buffer_get_insert(buffer);
+      gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
+      gtk_text_buffer_get_iter_at_mark(buffer, &end_iter, mark);
     }
   }
 
   buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ih->handle));
-  if (!gtk_text_buffer_get_selection_bounds(buffer, &start_iter, &end_iter))
-  {
-    GtkTextMark* mark = gtk_text_buffer_get_insert(buffer);
-    gtk_text_buffer_get_iter_at_mark(buffer, &start_iter, mark);
-    gtk_text_buffer_get_iter_at_mark(buffer, &end_iter, mark);
-  }
-
   tag = gtk_text_buffer_create_tag(buffer, NULL, NULL);
   gtkTextParseParagraphFormat(formattag, tag);
   gtkTextParseCharacterFormat(formattag, tag);
   gtk_text_buffer_apply_tag(buffer, tag, &start_iter, &end_iter);
-
-  /* reset the selection */
-  gtkTextSetSelectionAttrib(ih, NULL);
 }
 
 static int gtkTextSetRemoveFormattingAttrib(Ihandle* ih, const char* value)
diff --git a/iup/src/iup_text.c b/iup/src/iup_text.c
index d2e2e36..8be24ae 100755
--- a/iup/src/iup_text.c
+++ b/iup/src/iup_text.c
@@ -83,7 +83,17 @@ void iupTextUpdateFormatTags(Ihandle* ih)
 
   for (i = 0; i < count; i++)
   {
-    iupdrvTextAddFormatTag(ih, tag_array[i]);
+    char* bulk = iupAttribGet(tag_array[i], "BULK");
+    if (bulk && iupStrBoolean(bulk))
+    {
+      Ihandle* child = NULL;
+      void* state = iupdrvTextAddFormatTagStartBulk(ih);
+      for (child = IupGetNextChild(tag_array[i], NULL); child; child = IupGetNextChild(tag_array[i], child))
+        iupdrvTextAddFormatTag(ih, child, 1);
+      iupdrvTextAddFormatTagStopBulk(ih, state);
+    }
+    else
+      iupdrvTextAddFormatTag(ih, tag_array[i], 0);
     IupDestroy(tag_array[i]);
   }
   iupArrayDestroy(ih->data->formattags);
@@ -101,7 +111,24 @@ int iupTextSetAddFormatTagHandleAttrib(Ihandle* ih, const char* value)
     /* must update VALUE before updating the format */
     iTextUpdateValueAttrib(ih);
 
-    iupdrvTextAddFormatTag(ih, formattag);
+    char* bulk = iupAttribGet(formattag, "BULK");
+    if (bulk && iupStrBoolean(bulk))
+    {
+      Ihandle* child = NULL;
+      void* state = iupdrvTextAddFormatTagStartBulk(ih);
+      char* cleanout = iupAttribGet(formattag, "CLEANOUT");
+      if (cleanout && iupStrBoolean(cleanout))
+      {
+        IupSetAttribute(ih, "SELECTION", "ALL");
+        IupSetAttribute(ih, "REMOVEFORMATTING", NULL);
+      }
+      for (child = IupGetNextChild(formattag, NULL); child; child = IupGetNextChild(formattag, child))
+        iupdrvTextAddFormatTag(ih, child, 1);
+      iupdrvTextAddFormatTagStopBulk(ih, state);
+    }
+    else
+      iupdrvTextAddFormatTag(ih, formattag, 0);
+
     IupDestroy(formattag);
   }
   else
diff --git a/iup/src/iup_text.h b/iup/src/iup_text.h
index e018961..56a8725 100755
--- a/iup/src/iup_text.h
+++ b/iup/src/iup_text.h
@@ -15,7 +15,9 @@ extern "C" {
 void iupdrvTextInitClass(Iclass* ic);
 void iupdrvTextAddBorders(int *w, int *h);
 void iupdrvTextAddSpin(int *w, int h);
-void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag);
+void* iupdrvTextAddFormatTagStartBulk(Ihandle* ih);
+void iupdrvTextAddFormatTagStopBulk(Ihandle* ih, void* state);
+void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag, int bulk);
 void iupdrvTextConvertLinColToPos(Ihandle* ih, int lin, int col, int *pos);
 void iupdrvTextConvertPosToLinCol(Ihandle* ih, int pos, int *lin, int *col);
 
diff --git a/iup/src/iup_user.c b/iup/src/iup_user.c
index 842f436..90a9fb7 100755
--- a/iup/src/iup_user.c
+++ b/iup/src/iup_user.c
@@ -32,7 +32,7 @@ Iclass* iupUserGetClass(void)
   ic->name = "user";
   ic->format = NULL;  /* no parameters */
   ic->nativetype = IUP_TYPEVOID;
-  ic->childtype = IUP_CHILDNONE;
+  ic->childtype = IUP_CHILDMANY;
   ic->is_interactive = 0;
 
   iupClassRegisterAttribute(ic, "CLEARATTRIBUTES", NULL, iUserSetClearAttributesAttrib, NULL, NULL, IUPAF_NOT_MAPPED|IUPAF_NO_INHERIT);
diff --git a/iup/src/mot/iupmot_text.c b/iup/src/mot/iupmot_text.c
index d9d2c74..5af1328 100755
--- a/iup/src/mot/iupmot_text.c
+++ b/iup/src/mot/iupmot_text.c
@@ -43,10 +43,22 @@
 #define XmNwrap "Nwrap"
 #endif
 
-void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag)
+void* iupdrvTextAddFormatTagStartBulk(Ihandle* ih)
+{
+  (void)ih;
+}
+
+void iupdrvTextAddFormatTagStopBulk(Ihandle* ih, void* state)
+{
+  (void)ih;
+  (void)state;
+}
+
+void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag, int bulk)
 {
   (void)ih;
   (void)formattag;
+  (void)bulk;
 }
 
 void iupdrvTextAddSpin(int *w, int h)
diff --git a/iup/src/win/iupwin_text.c b/iup/src/win/iupwin_text.c
index c0e7115..7f3ea63 100755
--- a/iup/src/win/iupwin_text.c
+++ b/iup/src/win/iupwin_text.c
@@ -1171,12 +1171,44 @@ static int winTextSetStandardFontAttrib(Ihandle* ih, const char* value)
   return iupdrvSetStandardFontAttrib(ih, value);
 }
 
-void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag)
+typedef struct
+{
+  int eventMask;
+  DWORD line;
+  CHARRANGE oldRange;
+} formatTagBulkState;
+
+void* iupdrvTextAddFormatTagStartBulk(Ihandle* ih)
+{
+  formatTagBulkState* state = (formatTagBulkState*) malloc(sizeof(formatTagBulkState));
+  state->line = SendMessage(ih->handle, EM_GETFIRSTVISIBLELINE, 0, 0);
+  SendMessage(ih->handle, EM_EXGETSEL, 0, (LPARAM)&state->oldRange);
+  state->eventMask = SendMessage(ih->handle, EM_SETEVENTMASK, 0, 0);
+  SendMessage(ih->handle, WM_SETREDRAW, FALSE, 0);
+  return state;
+}
+
+void iupdrvTextAddFormatTagStopBulk(Ihandle* ih, void* stateOpaque)
+{
+  formatTagBulkState* state = (formatTagBulkState*) stateOpaque;
+  SendMessage(ih->handle, EM_EXSETSEL, 0, (LPARAM)&state->oldRange);
+  DWORD line = SendMessage(ih->handle, EM_GETFIRSTVISIBLELINE, 0, 0);
+  SendMessage(ih->handle, EM_LINESCROLL, 0, state->line - line);
+  SendMessage(ih->handle, WM_SETREDRAW, TRUE, 0);
+  SendMessage(ih->handle, EM_SETEVENTMASK, 0, state->eventMask);
+  free(state);
+  iupdrvRedrawNow(ih);
+}
+
+void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag, int bulk)
 {
   int convert2twips, pixel2twips;
   char *selection, *units;
   PARAFORMAT2 paraformat;
   CHARFORMAT2 charformat;
+  CHARRANGE oldRange;
+  DWORD line0, line1;
+  int oldEventMask;
 
   /* one twip is 1/1440 inch */
   /* twip = (pixel*1440)/(pixel/inch) */
@@ -1199,7 +1231,6 @@ void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag)
   {
     /* In Windows, the format message use the current selection */
     winTextSetSelectionAttrib(ih, selection);
-    iupAttribSetStr(ih, "SELECTION", NULL);
   }
   else
   {
@@ -1208,10 +1239,15 @@ void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag)
     {
       /* In Windows, the format message use the current selection */
       winTextSetSelectionPosAttrib(ih, selectionpos);
-      iupAttribSetStr(ih, "SELECTIONPOS", NULL);
     }
   }
 
+  if (!bulk) {
+    line0 = SendMessage(ih->handle, EM_GETFIRSTVISIBLELINE, 0, 0);
+    SendMessage(ih->handle, EM_EXGETSEL, 0, (LPARAM)&oldRange);
+    oldEventMask = SendMessage(ih->handle, EM_SETEVENTMASK, 0, 0);
+    SendMessage(ih->handle, WM_SETREDRAW, FALSE, 0);
+  }
   if (iupAttribGet(formattag, "FONTSCALE") && !iupAttribGet(formattag, "FONTSIZE"))
     iupAttribSetStr(formattag, "FONTSIZE", iupGetFontSizeAttrib(ih));
 
@@ -1223,9 +1259,14 @@ void iupdrvTextAddFormatTag(Ihandle* ih, Ihandle* formattag)
   if (charformat.dwMask != 0)
     SendMessage(ih->handle, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&charformat);
 
-  /* reset the selection, if changed here */
-  if (selection)
-    winTextSetSelectionAttrib(ih, NULL);
+  if (!bulk) {
+    SendMessage(ih->handle, EM_EXSETSEL, 0, (LPARAM)&oldRange);
+    line1 = SendMessage(ih->handle, EM_GETFIRSTVISIBLELINE, 0, 0);
+    SendMessage(ih->handle, EM_LINESCROLL, 0, line0 - line1);
+    SendMessage(ih->handle, WM_SETREDRAW, TRUE, 0);
+    SendMessage(ih->handle, EM_SETEVENTMASK, 0, oldEventMask);
+    iupdrvRedrawNow(ih);
+  }
 }
 
 static int winTextSetRemoveFormattingAttrib(Ihandle* ih, const char* value)
-- 
1.7.2.2

