texmacs-dev
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Texmacs-dev] Patches: copy/paste and selection handling.


From: Norbert Nemec
Subject: [Texmacs-dev] Patches: copy/paste and selection handling.
Date: Fri, 23 Jan 2009 23:07:00 +0000
User-agent: Thunderbird 2.0.0.19 (X11/20090105)

Hi there,

following the bugfix two days ago, I have now finally sat down and tried to clean out the annoyingly non-standard behavior of TeXmacs in terms of selection handling and cut-and-paste. Resulting are the three attached patches that handle mostly independent issues. Each patch has an explanation included in the header.

To begin with: I am used to the Windows/KDE/Gnome standard behavior which is very different from that of Emacs. I believe the patches in their current form will work in Emacs mode as well, but I would ask some of the true Emacs users to try these patches.

The first patch should be pretty much invisible if you don't use X11-style mouse-select-and-middle-button-paste. If you do use this, you should find the new behavior identical to that in other common editors. If you have xclipboard, klipper or any similar clipboard manager running, please check how it might interfere with the inter-program cut-and-paste.

The second patch makes selections generally non-persistent (i.e. text is unselected as soon as cursor is moved). This is the standard set by Windows and adopted by freedesktop.org (i.e. KDE, Gnome, etc.). If anybody has a good reason for supporting persistent selections, please bring it forward.

The third patch does away with the annoying habit of TeXmacs overwriting the clipboard when you press <delete> on a selection. I know that some Emacs users prefer to have their <delete> key bound to perform a <cut> operation. If anybody out there thinks this kind of behavior should be configurable (or even default in Emacs mode), could you please contact me, so we could try to figure out at which place I should put a switch.

Please, test these patches thoroughly and give your feedback. This is something that will affect every user every day, so it should be polished.

Greeting,
Norbert
Make copy-and-paste compliant with freedesktop standards

From:  <>

According to the freedesktop standard, there are two separate buffers to do 
cut-and-paste:
XA_CLIPBOARD: Windows like via Ctrl-C and Ctrl-V (XA_CLIPBOARD)
XA_PRIMARY: mouse-shortcut by marking with mouse and pressing middle button in 
some other place

Formerly, both methods were mapped to TeXmacs "primary" buffer, causing some 
strange behavior.

Now, the mouse selection is copied to a new "mouse" buffer and middle-click 
pastes from
this special buffer. The regular "primary" buffer is unaffected by mouse 
selection. Of
course the mouse-selection can also be copied to the "primary" buffer as well.

The interaction with the world outside depends on the GUI:

X11:
    The naming is a bit confusing to avoid changing too much code
    * The "primary" buffer is mapped to XA_CLIPBOARD
    * The "mouse" buffer is mapped to XA_PRIMARY

Qt:
    The handling of the "primary" is encapsulated by QClipboard. The "mouse"
    cut-and-paste should work TeXmacs internally. No idea how to get it to work
    properly with other applications.

All other GUIs should work as before with the "primary" selection and should 
work
following the freedesktop standard within TeXmacs. Whether a inter-application
mechanism exists and can be used for mouse copy-paste depends on the system.

============

The changes in detail:

* added function 'bool contains(list<T>,T)' to list.hpp

* created symbols XA_CLIPBOARD and XA_TARGETS in x_gui initialized at startup 
to standard XAtom values.

* changed edit_interface_rep::mouse_select to select into "mouse" instead of 
"primary" buffer

* changed edit_interface_rep::mouse_paste to
    a) go_to position of mouse click
    b) paste from "mouse" instead of "primary" buffer

* removed x_gui_rep::selection variable - do conversion from selection_s on the 
fly in
  x_loop instead

* changed x_gui_rep::set_selection to handle "mouse" (pointing to XA_PRIMARY) 
and "primary" (pointing to XA_CLIPBOARD)

* completely restructured x_gui_rep::get_selection to achieve the same here

* handle SelectionRequestEvent and SelectionClearEvent for either XA_PRIMARY or 
XA_CLIPBOARD
---

 src/src/Edit/Interface/edit_mouse.cpp |   10 +++--
 src/src/Edit/Replace/edit_select.cpp  |    2 +
 src/src/Kernel/Containers/list.cpp    |    5 +++
 src/src/Kernel/Containers/list.hpp    |    1 +
 src/src/Plugins/X11/x_drawable.hpp    |    2 +
 src/src/Plugins/X11/x_gui.cpp         |   62 ++++++++++++++++++++-------------
 src/src/Plugins/X11/x_gui.hpp         |    4 ++
 src/src/Plugins/X11/x_init.cpp        |    9 +++--
 src/src/Plugins/X11/x_loop.cpp        |   32 +++++++++++------
 src/src/Plugins/X11/x_window.hpp      |    2 +
 10 files changed, 80 insertions(+), 49 deletions(-)


diff --git a/src/src/Edit/Interface/edit_mouse.cpp 
b/src/src/Edit/Interface/edit_mouse.cpp
index bf6eebc..3af1018 100644
--- a/src/src/Edit/Interface/edit_mouse.cpp
+++ b/src/src/Edit/Interface/edit_mouse.cpp
@@ -56,7 +56,7 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int 
mods, time_t t) {
       dragging= true;
     }
     else if (dragging && (type == "move"))
-      type2= "dragging"; 
+      type2= "dragging";
     if (dragging && (type == "release-left"))
       type2= "end-drag";
 
@@ -66,7 +66,7 @@ edit_interface_rep::mouse_any (string type, SI x, SI y, int 
mods, time_t t) {
       right_dragging= true;
     }
     else if (right_dragging && (type == "move"))
-      type2= "right-dragging"; 
+      type2= "right-dragging";
     if (right_dragging && (type == "release-right"))
       type2= "end-right-drag";
 
@@ -180,14 +180,14 @@ edit_interface_rep::mouse_select (SI x, SI y, int mods) {
     eval ("(graphics-reset-context 'exit)");
   }
   if (selection_active_any ())
-    selection_set ("primary", selection_get (), true);
+    selection_set ("mouse", selection_get (), true);
 }
 
 void
 edit_interface_rep::mouse_paste (SI x, SI y) { (void) x; (void) y;
   if (eb->action ("paste", x, y, 0) != "") return;
-  selection_copy ();
-  selection_paste ();
+  go_to (x, y);
+  selection_paste ("mouse");
 }
 
 void
diff --git a/src/src/Edit/Replace/edit_select.cpp 
b/src/src/Edit/Replace/edit_select.cpp
index 7eb32ee..9b0fd6f 100644
--- a/src/src/Edit/Replace/edit_select.cpp
+++ b/src/src/Edit/Replace/edit_select.cpp
@@ -557,7 +557,7 @@ edit_select_rep::selection_set (string key, tree t, bool 
persistant) {
     nicely copying graphics into text, text into graphics, etc.
  */
   string s;
-  if (key == "primary") {
+  if (key == "primary" || key == "mouse") {
     if (selection_export == "verbatim") t= exec_texmacs (t, tp);
     if (selection_export == "html") t= exec_html (t, tp);
     if (selection_export == "latex") t= exec_latex (t, tp);
diff --git a/src/src/Kernel/Containers/list.cpp 
b/src/src/Kernel/Containers/list.cpp
index 4efd8c4..faa1bcf 100644
--- a/src/src/Kernel/Containers/list.cpp
+++ b/src/src/Kernel/Containers/list.cpp
@@ -194,4 +194,9 @@ remove (list<T> l, T what) {
   else return list<T> (l->item, remove (l->next, what));
 }
 
+template<class T> bool
+contains (list<T> l, T what) {
+  return (!is_nil(l) && (l->item == what || contains(l->next, what)));
+}
+
 #endif // defined LIST_CC
diff --git a/src/src/Kernel/Containers/list.hpp 
b/src/src/Kernel/Containers/list.hpp
index 2a82d01..1df1dff 100644
--- a/src/src/Kernel/Containers/list.hpp
+++ b/src/src/Kernel/Containers/list.hpp
@@ -70,6 +70,7 @@ TMPL T&       access_last (list<T>& l);
 TMPL list<T>& suppress_last (list<T>& l);
 TMPL list<T>  reverse (list<T> l);
 TMPL list<T>  remove (list<T> l, T what);
+TMPL bool     contains (list<T> l, T what);
 
 TMPL ostream& operator << (ostream& out, list<T> l);
 TMPL list<T>& operator << (list<T>& l, T item);
diff --git a/src/src/Plugins/X11/x_drawable.hpp 
b/src/src/Plugins/X11/x_drawable.hpp
index a877a6f..088a3b5 100644
--- a/src/src/Plugins/X11/x_drawable.hpp
+++ b/src/src/Plugins/X11/x_drawable.hpp
@@ -81,7 +81,7 @@ public:
 
   friend class x_gui_rep;
   friend class x_window_rep;
-  friend Bool my_predicate (Display* dpy, XEvent* ev, XPointer arg);
+  friend Bool my_selnotify_predicate (Display* dpy, XEvent* ev, XPointer arg);
 };
 
 #endif // defined X_DRAWABLE_H
diff --git a/src/src/Plugins/X11/x_gui.cpp b/src/src/Plugins/X11/x_gui.cpp
index e99254f..13ceb7a 100644
--- a/src/src/Plugins/X11/x_gui.cpp
+++ b/src/src/Plugins/X11/x_gui.cpp
@@ -188,7 +188,7 @@ x_gui_rep::has_mouse_grab (widget w) {
 ******************************************************************************/
 
 Bool
-my_predicate (Display* dpy, XEvent* ev, XPointer arg) { (void) dpy;
+my_selnotify_predicate (Display* dpy, XEvent* ev, XPointer arg) { (void) dpy;
   x_window win= (x_window) arg;
   return (win->win==ev->xany.window) && (ev->type==SelectionNotify);
 }
@@ -212,29 +212,41 @@ bool
 x_gui_rep::get_selection (string key, tree& t, string& s) {
   t= "none";
   s= "";
+  bool res=false;
+
   if (selection_t->contains (key)) {
     t= copy (selection_t [key]);
     s= copy (selection_s [key]);
-    return true;
+    res=true;
   }
-  if (key != "primary") return false;
-  if (XGetSelectionOwner (dpy, XA_PRIMARY) == None) return false;
   
-  if (is_nil (windows_l)) return false;
+  Atom xsel;
+  if(key == "primary")
+    xsel = XA_CLIPBOARD;
+  else if (key == "mouse")
+    xsel = XA_PRIMARY;
+  else
+    return res;
+
+  Window selown = XGetSelectionOwner(dpy, xsel);
+  if (selown == None
+  || is_nil (windows_l)
+  || contains(windows_l,selown)) return res;
+
   Window win= windows_l->item;
   x_window x_win= (x_window) Window_to_window[win];
-  Atom data= XInternAtom (dpy, "MY_STRING_SELECTION", false);
-  XConvertSelection (dpy, XA_PRIMARY, XA_STRING, data, win, CurrentTime);
+  Atom prop= XInternAtom (dpy, "MY_STRING_SELECTION", false);
+  XConvertSelection (dpy, xsel, XA_STRING, prop, win, CurrentTime);
 
   int i;
   XEvent ev;
   for (i=0; i<1000000; i++)
-    if (XCheckIfEvent (dpy, &ev, my_predicate, (XPointer) x_win))
+    if (XCheckIfEvent (dpy, &ev, my_selnotify_predicate, (XPointer) x_win))
       break;
-  if (i==1000000) return false;
+  if (i==1000000) return res;
   XSelectionEvent& sel= ev.xselection;
 
-  if (sel.property!=None) {
+  if (sel.property==prop) {
     long offset=0;
     Atom type;
     int fm;
@@ -249,23 +261,27 @@ x_gui_rep::get_selection (string key, tree& t, string& s) 
{
       offset += (n >> 2);
       XFree (ret);
     } while (remains>0);
-  }
-  t= tuple ("extern", s);
-  return true;
+    t= tuple ("extern", s);
+    return true;
+  } else
+    return res;
 }
 
 bool
 x_gui_rep::set_selection (string key, tree t, string s) {
   selection_t (key)= copy (t);
   selection_s (key)= copy (s);
-  if (key == "primary") {
-    if (is_nil (windows_l)) return false;
-    Window win= windows_l->item;
-    if (selection!=NULL) tm_delete_array (selection);
-    XSetSelectionOwner (dpy, XA_PRIMARY, win, CurrentTime);
-    if (XGetSelectionOwner(dpy, XA_PRIMARY)==None) return false;
-    selection= as_charp (s);
-  }
+  Atom xsel;
+  if(key == "primary") {
+    xsel = XA_CLIPBOARD;
+  } else if (key == "mouse") {
+    xsel = XA_PRIMARY;
+  } else
+    return true;
+  if (is_nil (windows_l)) return false;
+  Window win= windows_l->item;
+  XSetSelectionOwner (dpy, xsel, win, CurrentTime);
+  if (XGetSelectionOwner(dpy, xsel)==None) return false;
   return true;
 }
 
@@ -273,10 +289,6 @@ void
 x_gui_rep::clear_selection (string key) {
   selection_t->reset (key);
   selection_s->reset (key);
-  if ((key == "primary") && (selection != NULL)) {
-    tm_delete_array (selection);
-    selection= NULL;
-  }
 }
 
 bool
diff --git a/src/src/Plugins/X11/x_gui.hpp b/src/src/Plugins/X11/x_gui.hpp
index c1abfd6..24840fa 100644
--- a/src/src/Plugins/X11/x_gui.hpp
+++ b/src/src/Plugins/X11/x_gui.hpp
@@ -125,10 +125,12 @@ public:
   hashmap<int,string>          upper_key;
 
   list<Window>                 windows_l;
-  char*                        selection;
   hashmap<string,tree>         selection_t;
   hashmap<string,string>       selection_s;
 
+  Atom XA_CLIPBOARD;
+  Atom XA_TARGETS;
+
 public:
   x_gui_rep (int argc, char** argv);
   ~x_gui_rep ();
diff --git a/src/src/Plugins/X11/x_init.cpp b/src/src/Plugins/X11/x_init.cpp
index adae260..0294337 100644
--- a/src/src/Plugins/X11/x_init.cpp
+++ b/src/src/Plugins/X11/x_init.cpp
@@ -443,9 +443,9 @@ x_gui_rep::initialize_keyboard_pointer () {
   Map (XK_Cyrillic_E,   "\xdd");
   Map (XK_Cyrillic_YU,  "\xde");
   Map (XK_Cyrillic_YA,  "\xdf");
-  
+
   //Ukrainian letters in T2A encoding
-  Map (XK_Ukrainian_i,   "i"); // Fall back! 
+  Map (XK_Ukrainian_i,   "i"); // Fall back!
   Map (XK_Ukrainian_I,   "I"); // Fall back!
   Map (XK_Ukrainian_yi,   "\xa8");
   Map (XK_Ukrainian_YI,   "\x88");
@@ -808,7 +808,7 @@ x_gui_rep::x_gui_rep (int argc2, char** argv2):
   character_bitmap (NULL), character_pixmap ((pointer) 0),
   xpm_bitmap (0), xpm_pixmap (0),
   lower_key (""), upper_key (""),
-  selection (NULL), selection_t ("none"), selection_s ("")
+  selection_t ("none"), selection_s ("")
 {
   the_gui= this;
   if ((dpy= XOpenDisplay (NULL)) == NULL)
@@ -834,6 +834,9 @@ x_gui_rep::x_gui_rep (int argc2, char** argv2):
   interrupted        = false;
   interrupt_time     = texmacs_time ();
 
+  XA_CLIPBOARD = XInternAtom (dpy, "CLIPBOARD", false);
+  XA_TARGETS = XInternAtom (dpy, "TARGETS", false);
+
   if (XMatchVisualInfo (dpy, scr, depth, TrueColor, &visual) != 0) {
     if (visual.red_mask   == (255 << 16) &&
        visual.green_mask == (255 << 8) &&
diff --git a/src/src/Plugins/X11/x_loop.cpp b/src/src/Plugins/X11/x_loop.cpp
index 17919e3..def6a40 100644
--- a/src/src/Plugins/X11/x_loop.cpp
+++ b/src/src/Plugins/X11/x_loop.cpp
@@ -273,8 +273,7 @@ x_gui_rep::process_event (x_window win, XEvent* ev) {
       if (N(key)>0) win->key_event (key);
       break;
     }
-  case SelectionRequest:
-    {
+  case SelectionRequest: {
       XSelectionRequestEvent& req= ev->xselectionrequest;
       XSelectionEvent sel;
       sel.type      = SelectionNotify;
@@ -282,10 +281,14 @@ x_gui_rep::process_event (x_window win, XEvent* ev) {
       sel.selection = req.selection;
       sel.target    = req.target;
       sel.time      = req.time;
-      Atom XA_TARGETS = XInternAtom(dpy, "TARGETS", False);
-      if (selection==NULL)
+      string key = "none";
+      if(req.selection == XA_PRIMARY) {
+        key = "mouse";
+      } else if(req.selection == XA_CLIPBOARD)
+        key = "primary";
+      if (!selection_s->contains(key)) {
         sel.property  = None;
-      else if (req.target==XA_TARGETS) {
+      } else if (req.target==XA_TARGETS) {
         Atom targets[2];
         targets[0] = XA_TARGETS;
         targets[1] = XA_STRING;
@@ -294,19 +297,24 @@ x_gui_rep::process_event (x_window win, XEvent* ev) {
                          (unsigned char*)&targets[0],2);
         sel.property  = req.property;
       } else if ((req.target==AnyPropertyType) || (req.target==XA_STRING)) {
+        char *txt = as_charp (selection_s(key));
         XChangeProperty (dpy, req.requestor, req.property, XA_STRING,
                          8, PropModeReplace,
-                         (unsigned char*) selection,
-                         strlen (selection));
+                         (unsigned char*) txt,
+                         strlen (txt));
+        tm_delete_array (txt);
         sel.property  = req.property;
       } else
         sel.property  = None;
       XSendEvent (dpy, sel.requestor, false, 0, (XEvent*) &sel);
-      break;
-    }
-  case SelectionClear:
-    clear_selection ("primary");
-    break;
+    } break;
+  case SelectionClear: {
+      XSelectionClearEvent& req= ev->xselectionclear;
+      if(req.selection == XA_PRIMARY) {
+        clear_selection("mouse");
+      } else if(req.selection == XA_CLIPBOARD)
+        clear_selection ("primary");
+    } break;
   case ClientMessage:
     {
       Atom wm_protocols     = XInternAtom(win->dpy, "WM_PROTOCOLS",     1);
diff --git a/src/src/Plugins/X11/x_window.hpp b/src/src/Plugins/X11/x_window.hpp
index a4631e9..56ad01b 100644
--- a/src/src/Plugins/X11/x_window.hpp
+++ b/src/src/Plugins/X11/x_window.hpp
@@ -97,7 +97,7 @@ public:
 
   friend class x_gui_rep;
   friend class x_drawable_rep;
-  friend Bool my_predicate (Display* dpy, XEvent* ev, XPointer arg);
+  friend Bool my_selnotify_predicate (Display* dpy, XEvent* ev, XPointer arg);
   friend int get_identifier (window w);
 };
 
Makes selections non-persistent following Windows (KDE etc.) standard.

From:  <>

The selection is deactivated as soon as the cursor is moved by keys or mouse 
click.
This solves the annoying problem that a selection might still exist somewhere
off-screen, being replaced unnoticed when new text is entered. Now a selection 
may only
exist at the position of the cursor. (Clipboard and mouse-copy-paste buffer are
preserved beyond the life of the selection itself.)
---

 src/src/Edit/Interface/edit_mouse.cpp |   11 ++++++++++-
 src/src/Edit/Replace/edit_select.cpp  |   34 +++++++++++++++++++++------------
 2 files changed, 32 insertions(+), 13 deletions(-)


diff --git a/src/src/Edit/Interface/edit_mouse.cpp 
b/src/src/Edit/Interface/edit_mouse.cpp
index 3af1018..5639f19 100644
--- a/src/src/Edit/Interface/edit_mouse.cpp
+++ b/src/src/Edit/Interface/edit_mouse.cpp
@@ -132,6 +132,8 @@ edit_interface_rep::mouse_extra_click (SI x, SI y) {
   get_selection (p1, p2);
   if ((p1==p2) || path_less (tp, p1) || path_less (p2, tp)) select (tp, tp);
   select_enlarge ();
+  if (selection_active_any ())
+    selection_set ("mouse", selection_get (), true);
   return false;
 }
 
@@ -139,6 +141,7 @@ void
 edit_interface_rep::mouse_drag (SI x, SI y) {
   if (inside_graphics ()) return;
   if (eb->action ("drag", x, y, 0) != "") return;
+  go_to (x, y);
   end_x  = x;
   end_y  = y;
   selection_visible ();
@@ -150,8 +153,8 @@ edit_interface_rep::mouse_drag (SI x, SI y) {
     p1= p2;
     p2= temp;
   }
-  set_selection (p1, p2);
   if ((p1 == p2) && start_drag) return;
+  set_selection (p1, p2);
   start_drag= start_right_drag= false;
   notify_change (THE_SELECTION);
 }
@@ -179,6 +182,12 @@ edit_interface_rep::mouse_select (SI x, SI y, int mods) {
     invalidate_graphical_object ();
     eval ("(graphics-reset-context 'exit)");
   }
+  if(start_drag) {
+    path sp= find_innermost_scroll (eb, tp);
+    path p0= tree_path (sp, x, y, 0);
+    set_selection (p0, p0);
+    notify_change (THE_SELECTION);
+  }
   if (selection_active_any ())
     selection_set ("mouse", selection_get (), true);
 }
diff --git a/src/src/Edit/Replace/edit_select.cpp 
b/src/src/Edit/Replace/edit_select.cpp
index 9b0fd6f..c99a2fb 100644
--- a/src/src/Edit/Replace/edit_select.cpp
+++ b/src/src/Edit/Replace/edit_select.cpp
@@ -98,21 +98,31 @@ edit_select_rep::select_line () {
 
 void
 edit_select_rep::select_from_cursor () {
-  if (!(selecting || shift_selecting)) return;
-  if (path_less (mid_p, tp)) {
-    start_p= copy (mid_p);
-    end_p  = copy (tp);
-  }
-  else {
-    start_p= copy (tp);
-    end_p  = copy (mid_p);
+  if (selecting) {
+    if (path_less (mid_p, tp)) {
+      start_p= copy (mid_p);
+      end_p  = copy (tp);
+    } else {
+      start_p= copy (tp);
+      end_p  = copy (mid_p);
+    }
+    notify_change (THE_SELECTION);
+    if (shift_selecting) selecting = false;
   }
-  notify_change (THE_SELECTION);
 }
 
 void
 edit_select_rep::select_from_cursor_if_active () {
-  if (selecting) select_from_cursor ();
+  if (selecting) {
+    if (path_less (mid_p, tp)) {
+      start_p= copy (mid_p);
+      end_p  = copy (tp);
+    } else {
+      start_p= copy (tp);
+      end_p  = copy (mid_p);
+    }
+    notify_change (THE_SELECTION);
+  } else selection_cancel ();
 }
 
 void
@@ -128,10 +138,10 @@ edit_select_rep::select_from_shift_keyboard () {
   if ((!shift_selecting) || (end_p == start_p) ||
       ((tp != start_p) && (tp != end_p)))
     {
-      selecting= false;
-      shift_selecting= true;
       mid_p= copy (tp);
     }
+  selecting= true;
+  shift_selecting= true;
 }
 
 /******************************************************************************
Make delete act without touching the clipboard.

From:  <>

Formerly, pressing the <delete> key with an active selection would perform a 
"cut" operation,
placing the deleted text in the clipboard.

Now, <delete> does actually only delete the selected text following Windows 
(KDE etc.) standards.
---

 src/TeXmacs/progs/generic/generic-edit.scm |    3 ++-
 1 files changed, 2 insertions(+), 1 deletions(-)


diff --git a/src/TeXmacs/progs/generic/generic-edit.scm 
b/src/TeXmacs/progs/generic/generic-edit.scm
index 8dc9d92..c5d5d70 100644
--- a/src/TeXmacs/progs/generic/generic-edit.scm
+++ b/src/TeXmacs/progs/generic/generic-edit.scm
@@ -41,7 +41,8 @@
 (tm-define (kbd-remove forward?) (remove-text forward?))
 (tm-define (kbd-remove forward?)
   (:mode with-active-selection?)
-  (clipboard-cut "primary"))
+  (clipboard-cut "nowhere")
+  (clipboard-clear "nowhere"))
 
 (tm-define (kbd-tab)
   (if (not (complete-try?))

reply via email to

[Prev in Thread] Current Thread [Next in Thread]