Logo Search packages:      
Sourcecode: ibm-3270 version File versions  Download package

select.c

/*
 * Copyright (c) 1993-2009, Paul Mattes.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Paul Mattes nor his contributors may be used
 *       to endorse or promote products derived from this software without
 *       specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY PAUL MATTES "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL PAUL MATTES BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Portions of this code were taken from xterm/button.c:
 * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts.
 *
 *                         All Rights Reserved
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of Digital Equipment
 * Corporation not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 *
 *
 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

/*
 *    select.c
 *          This module handles selections.
 */

#include "globals.h"
#include <X11/Xatom.h>
#include <X11/Xmu/Atoms.h>
#include <X11/Xmu/StdSel.h>
#include "3270ds.h"
#include "appres.h"
#include "ctlr.h"
#include "screen.h"
#include "resources.h"

#include "actionsc.h"
#include "charsetc.h"
#include "ctlrc.h"
#include "kybdc.h"
#include "popupsc.h"
#include "screenc.h"
#include "selectc.h"
#include "tablesc.h"
#include "unicodec.h"
#include "utf8c.h"
#include "utilc.h"

#define Max(x, y) (((x) > (y))? (x): (y))
#define Min(x, y) (((x) < (y))? (x): (y))

/*
 * Mouse side.
 */

/* A button click establishes the boundaries of the 'fixed' area. */
static int      f_start = 0;  /* 'fixed' area */
static int      f_end = 0;

/* Mouse motion moves the boundaries of the 'varying' area. */
static int      v_start = 0;  /* 'varying' area */
static int      v_end = 0;

static unsigned long down_time = 0;
static unsigned long down1_time = 0;
static Dimension down1_x, down1_y;
static unsigned long up_time = 0;
static int      saw_motion = 0;
static int      num_clicks = 0;
static void grab_sel(int start, int end, Boolean really, Time t);
#define NS        5
static Atom     want_sel[NS];
static struct {               /* owned selections */
      Atom            atom;   /* atom */
      char           *buffer; /* buffer contents (UTF-8) */
}               own_sel[NS];
static Boolean  cursor_moved = False;
static int      saved_cursor_addr;
static void own_sels(Time t);
static int  n_owned = -1;
static Boolean    any_selected = False;

extern Widget  *screen;

#define CLICK_INTERVAL  300

#define event_x(event)        event->xbutton.x
#define event_y(event)        event->xbutton.y
#define event_time(event)     event->xbutton.time

#define BOUNDED_XY(event, x, y) {   \
      x = X_TO_COL(event_x(event)); \
      if (x < 0)              \
            x = 0;                  \
      if (x >= COLS)                \
            x = COLS - 1;           \
      if (flipped)                  \
            x = (COLS - x) - 1;     \
      y = Y_TO_ROW(event_y(event) - *descent);  \
      if (y <= 0)             \
            y = 0;                  \
      if (y >= ROWS)                \
            y = ROWS - 1;           \
}


static int char_class[256] = {
/* nul soh stx etx eot enq ack bel  bs  ht  nl  vt  np  cr  so  si */
    32,  1,  1,  1,  1,  1,  1,  1,  1, 32,  1,  1,  1,  1,  1,  1,
/* dle dc1 dc2 dc3 dc4 nak syn etb can  em sub esc  fs  gs  rs  us */
     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
/*  sp   !   "   #   $   %   &   '   (   )   *   +   ,   -   .   / */
    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
/*   0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ? */
    48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 58, 59, 60, 61, 62, 63,
/*   @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O */
    64, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
/*   P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _ */
    48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 91, 92, 93, 94, 48,
/*   `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o */
    96, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
/*   p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~  del */
    48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,123,124,125,126,   1,
/* ---,---,---,---,---,---,---,---,---,---,---,---,---,---,---,--- */
     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
/* ---,---,---,---,---,---,---,---,---,---,---,---,---,---,---,--- */
     1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
/* nob exc cen ste cur yen bro sec dia cop ord gui not hyp reg mac */
    32,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,
/* deg plu two thr acu mu  par per ce  one mas gui one one thr que */
   176,177,178,179,180,181,182,183,184,185,186,178,188,189,190,191,
/* Agr Aac Aci Ati Adi Ari AE  Cce Egr Eac Eci Edi Igr Iac Ici Idi */
    48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
/* ETH Nti Ogr Oac Oci Oti Odi mul Oob Ugr Uac Uci Udi Yac THO ssh */
    48, 48, 48, 48, 48, 48, 48,215, 48, 48, 48, 48, 48, 48, 48, 48,
/* agr aac aci ati adi ari ae  cce egr eac eci edi igr iac ici idi */
    48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
/* eth nti ogr oac oci oti odi div osl ugr uac uci udi yac tho ydi */
    48, 48, 48, 48, 48, 48, 48,247, 48, 48, 48, 48, 48, 48, 48, 48
};

/* Parse a charClass string: [low-]high:value[,...] */
void
reclass(char *s)
{
      int n;
      int low, high, value;
      int i;
      char c;

      n = -1;
      low = -1;
      high = -1;
      for (;;) {
            c = *s++;
            if (isdigit(c)) {
                  if (n == -1)
                        n = 0;
                  n = (n * 10) + (c - '0');
                  if (n > 255)
                        goto fail;
            } else if (c == '-') {
                  if (n == -1 || low != -1)
                        goto fail;
                  low = n;
                  n = -1;
            } else if (c == ':') {
                  if (n == -1)
                        goto fail;
                  high = n;
                  n = -1;
            } else if (c == ',' || c == '\0') {
                  if (n == -1)
                        goto fail;
                  value = n;
                  n = -1;
                  if (high == -1)
                        goto fail;
                  if (low == -1)
                        low = high;
                  if (high < low)
                        goto fail;
                  for (i = low; i <= high; i++)
                        char_class[i] = value;
                  low = -1;
                  high = -1;
                  if (c == '\0')
                        return;
            } else
                  goto fail;
      }

    fail:
      popup_an_error("Error in %s string", ResCharClass);
}

static void
select_word(int baddr, Time t)
{
      unsigned char fa = get_field_attribute(baddr);
      unsigned char ch;
      int class;

      /* Find the initial character class */
      if (FA_IS_ZERO(fa))
            ch = EBC_space;
      else
            ch = ea_buf[baddr].cc;
      class = char_class[ebc2asc0[ch]];

      /* Find the beginning */
      for (f_start = baddr; f_start % COLS; f_start--) {
            fa = get_field_attribute(f_start);
            if (FA_IS_ZERO(fa))
                  ch = EBC_space;
            else
                  ch = ea_buf[f_start].cc;
            if (char_class[ebc2asc0[ch]] != class) {
                  f_start++;
                  break;
            }
      }

      /* Find the end */
      for (f_end = baddr; (f_end+1) % COLS; f_end++) {
            fa = get_field_attribute(f_end);
            if (FA_IS_ZERO(fa))
                  ch = EBC_space;
            else
                  ch = ea_buf[f_end].cc;
            if (char_class[ebc2asc0[ch]] != class) {
                  f_end--;
                  break;
            }
      }

      v_start = f_start;
      v_end = f_end;
      grab_sel(f_start, f_end, True, t);
}

static void
select_line(int baddr, Time t)
{
      f_start = baddr - (baddr % COLS);
      f_end = f_start + COLS - 1;
      v_start = f_start;
      v_end = f_end;
      grab_sel(f_start, f_end, True, t);
}


/*
 * Start a new selection.
 * Usually bound to <Btn1Down>.
 */
void
select_start_action(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
      int x, y;
      register int baddr;

      action_debug(select_start_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(select_start_action));
            return;
      }
      if (w != *screen)
            return;
      BOUNDED_XY(event, x, y);
      baddr = ROWCOL_TO_BA(y, x);
      f_start = f_end = v_start = v_end = baddr;
      down1_time = down_time = event_time(event);
      down1_x = event_x(event);
      down1_y = event_y(event);
      if (down_time - up_time > CLICK_INTERVAL) {
            num_clicks = 0;
            /* Commit any previous cursor move. */
            cursor_moved = False;
      }
      if (num_clicks == 0)
            unselect(0, ROWS*COLS);
}

/*
 * Alternate form of select_start, which combines cursor motion with selection.
 * Usually bound to <Btn1Down> in a user-specified keymap.
 */
void
move_select_action(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
      int x, y;
      register int baddr;

      action_debug(move_select_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(move_select_action));
            return;
      }
      if (w != *screen)
            return;
      BOUNDED_XY(event, x, y);
      baddr = ROWCOL_TO_BA(y, x);

      f_start = f_end = v_start = v_end = baddr;
      down1_time = down_time = event_time(event);
      down1_x = event_x(event);
      down1_y = event_y(event);

      if (down_time - up_time > CLICK_INTERVAL) {
            num_clicks = 0;
            /* Commit any previous cursor move. */
            cursor_moved = False;
      }
      if (num_clicks == 0) {
            if (any_selected) {
                  unselect(0, ROWS*COLS);
            } else {
                  cursor_moved = True;
                  saved_cursor_addr = cursor_addr;
                  cursor_move(baddr);
            }
      }
}

/*
 * Begin extending the current selection.
 * Usually bound to <Btn3Down>.
 */
void
start_extend_action(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
      int x, y;
      int baddr;
      Boolean continuous = (!ever_3270 && !toggled(RECTANGLE_SELECT));

      action_debug(start_extend_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(start_extend_action));
            return;
      }
      if (w != *screen)
            return;

      down1_time = 0L;

      BOUNDED_XY(event, x, y);
      baddr = ROWCOL_TO_BA(y, x);

      if (continuous) {
            /* Think linearly. */
            if (baddr < f_start)
                  v_start = baddr;
            else if (baddr > f_end)
                  v_end = baddr;
            else if (baddr - f_start > f_end - baddr)
                  v_end = baddr;
            else
                  v_start = baddr;
      } else {
            /* Think rectangularly. */
            int nrow = baddr / COLS;
            int ncol = baddr % COLS;
            int vrow_ul = v_start / COLS;
            int vrow_lr = v_end / COLS;
            int vcol_ul = Min(v_start % COLS, v_end % COLS);
            int vcol_lr = Max(v_start % COLS, v_end % COLS);

            /* Set up the row. */
            if (nrow <= vrow_ul)
                  vrow_ul = nrow;
            else if (nrow >= vrow_lr)
                  vrow_lr = nrow;
            else if (nrow - vrow_ul > vrow_lr - nrow)
                  vrow_lr = nrow;
            else
                  vrow_ul = nrow;

            /* Set up the column. */
            if (ncol <= vcol_ul)
                  vcol_ul = ncol;
            else if (ncol >= vcol_lr)
                  vcol_lr = ncol;
            else if (ncol - vcol_ul > vcol_lr - ncol)
                  vcol_lr = ncol;
            else
                  vcol_ul = ncol;

            v_start = (vrow_ul * COLS) + vcol_ul;
            v_end = (vrow_lr * COLS) + vcol_lr;
      }

      grab_sel(v_start, v_end, True, event_time(event));
      saw_motion = 1;
      num_clicks = 0;
}

/*
 * Continuously extend the current selection.
 * Usually bound to <Btn1Motion> and <Btn3Motion>.
 */
void
select_extend_action(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
      int x, y;
      int baddr;

      action_debug(select_extend_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(select_extend_action));
            return;
      }
      if (w != *screen)
            return;

      /* Ignore initial drag events if are too near. */
      if (down1_time != 0L &&
          abs((int) event_x(event) - (int) down1_x) < *char_width &&
          abs((int) event_y(event) - (int) down1_y) < *char_height)
            return;
      else
            down1_time = 0L;

      /* If we moved the 3270 cursor on the first click, put it back. */
      if (cursor_moved) {
            cursor_move(saved_cursor_addr);
            cursor_moved = False;
      }

      BOUNDED_XY(event, x, y);
      baddr = ROWCOL_TO_BA(y, x);

      /*
       * If baddr falls outside if the v range, open up the v range.  In
       * addition, if we are extending one end of the v range, make sure the
       * other end at least covers the f range.
       */
      if (baddr <= v_start) {
            v_start = baddr;
            v_end = f_end;
      }
      if (baddr >= v_end) {
            v_end = baddr;
            v_start = f_start;
      }

      /*
       * If baddr falls within the v range, narrow up the nearer end of the
       * v range.
       */
      if (baddr > v_start && baddr < v_end) {
            if (baddr - v_start < v_end - baddr)
                  v_start = baddr;
            else
                  v_end = baddr;
      }

      num_clicks = 0;
      saw_motion = 1;
      grab_sel(v_start, v_end, False, event_time(event));
}

/*
 * End the selection.
 * Usually bound to <BtnUp>.
 */
void
select_end_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      Cardinal i;
      int x, y;

      action_debug(select_end_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(select_end_action));
            return;
      }
      if (w != *screen)
            return;

      if (n_owned == -1) {
            for (i = 0; i < NS; i++)
                  own_sel[i].atom = None;
            n_owned = 0;
      }
      for (i = 0; i < NS; i++)
            if (i < *num_params)
                  want_sel[i] = XInternAtom(display, params[i], False);
            else
                  want_sel[i] = None;
      if (*num_params == 0)
            want_sel[0] = XA_PRIMARY;

      BOUNDED_XY(event, x, y);
      up_time = event_time(event);

      if (up_time - down_time > CLICK_INTERVAL)
            num_clicks = 0;

      if (++num_clicks > 3)
            num_clicks = 1;

      switch (num_clicks) {
          case 1:
            if (saw_motion) {
                  f_start = v_start;
                  f_end = v_end;
                  grab_sel(f_start, f_end, True, event_time(event));
            }
            break;
          case 2:
            /*
             * If we moved the 3270 cursor on the first click, put it back.
             */
            if (cursor_moved) {
                  cursor_move(saved_cursor_addr);
                  cursor_moved = False;
            }
            select_word(f_start, event_time(event));
            break;
          case 3:
            select_line(f_start, event_time(event));
            break;
      }
      saw_motion = 0;
}

/*
 * New-style selection actions.
 *
 * These actions work a bit more intuitively in 3270 mode than the historic
 * ones.
 *  SelectDown is usually bound to Btn1Down.
 *  SelectMotion is usually bound to Btn1Motion.
 *  SelectUp is usually bound to Btn1Up.
 *
 * SelectDown deselects on the first click, and remembers the screen position.
 *
 * SelectMotion extends the selection from the location remembered by
 *  SelectDown to the current location.
 *
 * SelectUp moves the cursor on the first click.  On clicks two and three, it
 *  selects a word or line.  On click four, it deselects and resets the click
 *  counter to one.
 */

void
SelectDown_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      int x, y;
      register int baddr;

      action_debug(SelectDown_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(SelectDown_action));
            return;
      }
      if (w != *screen)
            return;
      BOUNDED_XY(event, x, y);
      baddr = ROWCOL_TO_BA(y, x);

      if (event_time(event) - down_time > CLICK_INTERVAL)
            num_clicks = 0;

      down_time = event_time(event);
      if (num_clicks == 0) {
            f_start = f_end = v_start = v_end = baddr;
            down1_time = down_time;
            if (any_selected) {
                  unselect(0, ROWS*COLS);
            }
      }
}

void
SelectMotion_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      int x, y;
      register int baddr;

      action_debug(SelectMotion_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(SelectMotion_action));
            return;
      }
      if (w != *screen)
            return;

      /* Ignore initial drag events if are too near. */
      if (down1_time != 0L &&
          abs((int) event_x(event) - (int) down1_x) < *char_width &&
          abs((int) event_y(event) - (int) down1_y) < *char_height)
            return;
      else
            down1_time = 0L;

      BOUNDED_XY(event, x, y);
      baddr = ROWCOL_TO_BA(y, x);

      /*
       * If baddr falls outside if the v range, open up the v range.  In
       * addition, if we are extending one end of the v range, make sure the
       * other end at least covers the f range.
       */
      if (baddr <= v_start) {
            v_start = baddr;
            v_end = f_end;
      }
      if (baddr >= v_end) {
            v_end = baddr;
            v_start = f_start;
      }

      /*
       * If baddr falls within the v range, narrow up the nearer end of the
       * v range.
       */
      if (baddr > v_start && baddr < v_end) {
            if (baddr - v_start < v_end - baddr)
                  v_start = baddr;
            else
                  v_end = baddr;
      }

      num_clicks = 0;
      saw_motion = 1;
      grab_sel(v_start, v_end, False, event_time(event));
}

void
SelectUp_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      int x, y;
      register int baddr;
      Cardinal i;

      action_debug(SelectUp_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(SelectUp_action));
            return;
      }
      if (w != *screen)
            return;

      if (n_owned == -1) {
            for (i = 0; i < NS; i++)
                  own_sel[i].atom = None;
            n_owned = 0;
      }
      for (i = 0; i < NS; i++) {
            if (i < *num_params)
                  want_sel[i] = XInternAtom(display, params[i], False);
            else
                  want_sel[i] = None;
      }
      if (*num_params == 0)
            want_sel[0] = XA_PRIMARY;

      BOUNDED_XY(event, x, y);
      baddr = ROWCOL_TO_BA(y, x);

      if (event_time(event) - up_time > CLICK_INTERVAL) {
#if defined(DEBUG_CLICKS) /*[*/
            printf("too long, reset\n");
#endif /*]*/
            num_clicks = 0;
      }
      up_time = event_time(event);

      if (++num_clicks > 3) {
#if defined(DEBUG_CLICKS) /*[*/
            printf("wrap\n");
#endif /*]*/
            num_clicks = 1;
      }

#if defined(DEBUG_CLICKS) /*[*/
      printf("%d clicks\n", num_clicks);
#endif /*]*/
      switch (num_clicks) {
          case 1:
              /*
             * If we saw motion, then take the selection.
             * Otherwise, if we're in 3270 mode, move the cursor.
             */
            if (saw_motion) {
                  f_start = v_start;
                  f_end = v_end;
                  grab_sel(f_start, f_end, True, event_time(event));
            } else if (IN_3270) {
                  cursor_move(baddr);
            }
            break;
          case 2:
            /*
             * If we moved the 3270 cursor on the first click, put it back.
             */
            select_word(f_start, event_time(event));
            break;
          case 3:
            select_line(f_start, event_time(event));
            break;
      }
      saw_motion = 0;
}

/*
 * Set the selection.
 * Usually bound to the Copy key.
 */
void
set_select_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      Cardinal i;

      action_debug(set_select_action, event, params, num_params);

      if (!any_selected)
            return;
      if (n_owned == -1) {
            for (i = 0; i < NS; i++)
                  own_sel[i].atom = None;
            n_owned = 0;
      }
      for (i = 0; i < NS; i++)
            if (i < *num_params)
                  want_sel[i] = XInternAtom(display, params[i], False);
            else
                  want_sel[i] = None;
      if (*num_params == 0)
            want_sel[0] = XA_PRIMARY;
      own_sels(event_time(event));
}

/*
 * Translate the mouse position to a buffer address.
 */
int
mouse_baddr(Widget w, XEvent *event)
{
      int x, y;

      if (w != *screen)
            return 0;
      BOUNDED_XY(event, x, y);
      return ROWCOL_TO_BA(y, x);
}

/*
 * Cut action.
 * For now, merely erases all unprotected characters currently selected.
 * In future, it may interact more with selections.
 */
#define ULS sizeof(unsigned long)
#define ULBS      (ULS * 8)

void
Cut_action(Widget w _is_unused, XEvent *event, String *params, Cardinal *num_params)
{
      register int baddr;
      unsigned char fa = get_field_attribute(0);
      unsigned long *target;
      register unsigned char repl;

      action_debug(Cut_action, event, params, num_params);

      target = (unsigned long *)XtCalloc(ULS, ((ROWS*COLS)+(ULBS-1))/ULBS);

      /* Identify the positions to empty. */
      for (baddr = 0; baddr < ROWS*COLS; baddr++) {
            if (ea_buf[baddr].fa)
                  fa = ea_buf[baddr].fa;
            else if ((IN_ANSI || !FA_IS_PROTECTED(fa)) && SELECTED(baddr))
                  target[baddr/ULBS] |= 1 << (baddr%ULBS);
      }

      /* Erase them. */
      if (IN_3270)
            repl = EBC_null;
      else
            repl = EBC_space;
      for (baddr = 0; baddr < ROWS*COLS; baddr++)
            if (target[baddr/ULBS] & (1 << (baddr%ULBS)))
                  ctlr_add(baddr, repl, 0);

      Free(target);
}

/*
 * KybdSelect action.  Extends the selection area in the indicated direction.
 */
void
KybdSelect_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      enum { UP, DOWN, LEFT, RIGHT } direction;
      int x_start, x_end;
      Cardinal i;

      action_debug(KybdSelect_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(select_start_action));
            return;
      }
      if (w != *screen)
            return;

      if (*num_params < 1) {
            popup_an_error("%s requires one argument",
                action_name(KybdSelect_action));
            return;
      }
      if (!strcasecmp(params[0], "Up")) {
            direction = UP;
      } else if (!strcasecmp(params[0], "Down")) {
            direction = DOWN;
      } else if (!strcasecmp(params[0], "Left")) {
            direction = LEFT;
      } else if (!strcasecmp(params[0], "Right")) {
            direction = RIGHT;
      } else {
            popup_an_error("%s first argument must be Up, Down, Left, or "
                "Right", action_name(KybdSelect_action));
            return;
      }

      if (!any_selected)
            x_start = x_end = cursor_addr;
      else {
            if (f_start < f_end) {
                  x_start = f_start;
                  x_end = f_end;
            } else {
                  x_start = f_end;
                  x_end = f_start;
            }
      }

      switch (direction) {
          case UP:
            if (!(x_start / COLS))
                  return;
            x_start -= COLS;
            break;
          case DOWN:
            if ((x_end / COLS) == ROWS - 1)
                  return;
            x_end += COLS;
            break;
          case LEFT:
            if (!(x_start % COLS))
                  return;
            x_start--;
            break;
          case RIGHT:
            if ((x_end % COLS) == COLS - 1)
                  return;
            x_end++;
            break;
      }

      /* Figure out the atoms they want. */
      if (n_owned == -1) {
            for (i = 0; i < NS; i++)
                  own_sel[i].atom = None;
            n_owned = 0;
      }
      for (i = 1; i < NS; i++)
            if (i < *num_params)
                  want_sel[i] = XInternAtom(display, params[i], False);
            else
                  want_sel[i] = None;
      if (*num_params == 1)
            want_sel[0] = XA_PRIMARY;

      /* Grab the selection. */
      f_start = v_start = x_start;
      f_end = v_end = x_end;
      grab_sel(f_start, f_end, True, event_time(event));
}

/*
 * unselect action.  Removes a selection.
 */
void
Unselect_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      action_debug(Unselect_action, event, params, num_params);

      /* It's just cosmetic. */
      unselect(0, ROWS*COLS);
}

void
SelectAll_action(Widget w _is_unused, XEvent *event, String *params,
    Cardinal *num_params)
{
      Cardinal i;

      action_debug(SelectUp_action, event, params, num_params);
      if (event == NULL) {
            popup_an_error("%s can only be used as a keymap action",
                action_name(SelectAll_action));
            return;
      }
      if (w != *screen)
            return;

      if (n_owned == -1) {
            for (i = 0; i < NS; i++)
                  own_sel[i].atom = None;
            n_owned = 0;
      }
      for (i = 0; i < NS; i++) {
            if (i < *num_params)
                  want_sel[i] = XInternAtom(display, params[i], False);
            else
                  want_sel[i] = None;
      }
      if (*num_params == 0)
            want_sel[0] = XA_PRIMARY;

      grab_sel(0, (ROWS * COLS) - 1, True, event_time(event));
}


/*
 * Screen side.
 */

static char    *select_buf = CN;
static char    *sb_ptr = CN;
static int      sb_size = 0;
#define SB_CHUNK  1024

static Time     sel_time;

static void
init_select_buf(void)
{
      if (select_buf == CN)
            select_buf = XtMalloc(sb_size = SB_CHUNK);
      sb_ptr = select_buf;
}

static void
store_sel(char c)
{
      if (sb_ptr - select_buf >= sb_size) {
            sb_size += SB_CHUNK;
            select_buf = XtRealloc(select_buf, sb_size);
            sb_ptr = select_buf + sb_size - SB_CHUNK;
      }
      *(sb_ptr++) = c;
}

/*
 * Convert the UTF-8 string to an ICCCM-defined STRING (ISO 8859-1 printable
 * plus tab and newline).
 *
 * Returns the length of the stored string.
 */
static unsigned long
store_icccm_string(XtPointer value, const char *buf)
{
      char *dst = (char *)value;
      unsigned long len = 0;
      Boolean skip = False;

      while (*buf) {
            int nw;
            ucs4_t ucs;

            if (*buf == '\033') {
                  /* Funky GE sequence.  Skip it. */
                  *dst++ = ' ';
                  len++;
                  buf++;
                  skip = True;
                  continue;
            }
            nw = utf8_to_unicode(buf, strlen(buf), &ucs);
            if (nw <= 0)
                  return len;
            if (skip) {
                  skip = False;
                  continue;
            }
            if (ucs == '\n' ||
                (ucs >= 0x20 && ucs <= 0x7f) ||
                (ucs >= 0xa0 && ucs <= 0xff)) {
                  *dst++ = ucs & 0xff;
                  len++;
            }
            buf += nw;
      }
      return len;
}

static Boolean
convert_sel(Widget w, Atom *selection, Atom *target, Atom *type,
    XtPointer *value, unsigned long *length, int *format)
{
      int i;

      /* Find the right selection. */
      for (i = 0; i < NS; i++)
            if (own_sel[i].atom == *selection)
                  break;
      if (i >= NS)      /* not my selection */
            return False;

      if (*target == XA_TARGETS(display)) {
            Atom* targetP;
            Atom* std_targets;
            unsigned long std_length;

            XmuConvertStandardSelection(w, sel_time, selection,
                target, type, (caddr_t*) &std_targets, &std_length, format);
#if defined(XA_UTF8_STRING) /*[*/
            *length = std_length + 6;
#else /*][*/
            *length = std_length + 5;
#endif /*]*/
            *value = (XtPointer) XtMalloc(sizeof(Atom) * (*length));
            targetP = *(Atom**)value;
            *targetP++ = XA_STRING;
            *targetP++ = XA_TEXT(display);
            *targetP++ = XA_COMPOUND_TEXT(display);
#if defined(XA_UTF8_STRING) /*[*/
            *targetP++ = XA_UTF8_STRING(display);
#endif /*]*/
            *targetP++ = XA_LENGTH(display);
            *targetP++ = XA_LIST_LENGTH(display);
            (void) memmove(targetP,  std_targets,
                           (int) (sizeof(Atom) * std_length));
            XtFree((char *) std_targets);
            *type = XA_ATOM;
            *format = 32;
            return True;
      }

      if (*target == XA_STRING ||
          *target == XA_TEXT(display) ||
          *target == XA_COMPOUND_TEXT(display)
#if defined(XA_UTF8_STRING) /*[*/
          || *target == XA_UTF8_STRING(display)
#endif /*]*/
          ) {
            if (*target == XA_COMPOUND_TEXT(display)
#if defined(XA_UTF8_STRING) /*[*/
                || *target == XA_UTF8_STRING(display)
#endif /*]*/
                  )
                  *type = *target;
            else
                  *type = XA_STRING;
            *length = strlen(own_sel[i].buffer);
            *value = XtMalloc(*length);
#if defined(XA_UTF8_STRING) /*[*/
            if (*target == XA_UTF8_STRING(display))
                  (void) memmove(*value, own_sel[i].buffer,
                               (int) *length);
            else
#endif /*]*/
                  /*
                   * N.B.: We return a STRING for COMPOUND_TEXT.
                   * Someday we may do real ISO 2022, but not today.
                   */
                  *length = store_icccm_string(*value,
                        own_sel[i].buffer);
            *format = 8;
            return True;
      }
      if (*target == XA_LIST_LENGTH(display)) {
            *value = XtMalloc(4);
            if (sizeof(long) == 4)
                  *(long *)*value = 1;
            else {
                  long temp = 1;
                  (void) memmove(*value,
                              ((char*) &temp) + sizeof(long) - 4,
                              4);
            }
            *type = XA_INTEGER;
            *length = 1;
            *format = 32;
            return True;
      }
      if (*target == XA_LENGTH(display)) {
            *value = XtMalloc(4);
            if (sizeof(long) == 4)
                  *(long*)*value = strlen(own_sel[i].buffer);
            else {
                  long temp = strlen(own_sel[i].buffer);
                  (void) memmove(*value,
                                     ((char *) &temp) + sizeof(long) - 4,
                                 4);
            }
            *type = XA_INTEGER;
            *length = 1;
            *format = 32;
            return True;
      }

      if (XmuConvertStandardSelection(w, sel_time, selection,
          target, type, (caddr_t *)value, length, format))
            return True;

      /* else */
#if 0
      printf("Unknown conversion request: %s to %s\n",
          XGetAtomName(display, *selection),
          XGetAtomName(display, *target));
#endif
      return False;
}

static void
lose_sel(Widget w _is_unused, Atom *selection)
{
      int i;

      for (i = 0; i < NS; i++)
            if (own_sel[i].atom != None && own_sel[i].atom == *selection) {
                  own_sel[i].atom = None;
                  XtFree(own_sel[i].buffer);
                  own_sel[i].buffer = CN;
                  n_owned--;
                  break;
            }
      if (!n_owned)
            unselect(0, ROWS*COLS);
}

/*
 * Somewhat convoluted logic to return an ASCII character for a given screen
 * position.
 *
 * The character has to be found indirectly from ea_buf and the field
 * attirbutes, so that zero-intensity fields become blanks.
 */
static Boolean osc_valid = False;

static void
osc_start(void)
{
      osc_valid = False;
}

/*
 * Return a 'selection' version of a given character on the screen.
 * Returns a printable ASCII character, or 0 if the character is a NULL and
 * shouldn't be included in the selection.
 */
static void
onscreen_char(int baddr, unsigned char *r, int *rlen)
{
      static int osc_baddr;
      static unsigned char fa;
      ucs4_t uc;
#if defined(X3270_DBCS) /*[*/
      int baddr2;
#endif /*]*/

      *rlen = 1;

      /* If we aren't moving forward, all bets are off. */
      if (osc_valid && baddr < osc_baddr)
            osc_valid = False;

      if (osc_valid) {
            /*
             * Search for a new field attribute between the address we
             * want and the last address we searched.  If we found a new
             * field attribute, save the address for next time.
             */
            (void) get_bounded_field_attribute(baddr, osc_baddr, &fa);
            osc_baddr = baddr;
      } else {
            /*
             * Find the attribute the old way.
             */
            fa = get_field_attribute(baddr);
            osc_baddr = baddr;
            osc_valid = True;
      }

      /* If it isn't visible, then make it a blank. */
      if (FA_IS_ZERO(fa)) {
            *r = ' ';
            return;
      }

#if defined(X3270_DBCS) /*[*/
      /* Handle DBCS. */
      switch (ctlr_dbcs_state(baddr)) {
      case DBCS_LEFT:
          baddr2 = baddr;
          INC_BA(baddr2);
          uc = ebcdic_to_unicode((ea_buf[baddr].cc << 8) | ea_buf[baddr2].cc,
                CS_BASE, EUO_NONE);
          *rlen = unicode_to_utf8(uc, (char *)r);
          return;
      case DBCS_RIGHT:
          /* Returned the entire character when the left half was read. */
          *rlen = 0;
          return;
      case DBCS_SI:
          /* Suppress SI's altogether.  They'll expand back on paste. */
          *rlen = 0;
          return;
      case DBCS_SB:
          /* Treat SB's as normal SBCS characters. */
          break;
      default:
          break;
      }
#endif /*]*/

      switch (ea_buf[baddr].cs) {
          case CS_BASE:
          default:
            switch (ea_buf[baddr].cc) {
#if defined(X3270_DBCS) /*[*/
                case EBC_so:
                  /*
                   * Suppress SO's altogether.  They'll expand back on
                   * paste.
                   */
                  *rlen = 0;
                  return;
#endif /*]*/
                case EBC_null:
                  *r = 0;
                  return;
                default:
                  /*
                   * Note that we use the 'for_display' flavor of
                   * ebcdic_base_to_unicode here, so DUP and FM are
                   * translated to special private-use Unicode values.
                   * These will (hopefully) be ignored by other
                   * applications, but translated back to DUP and FM if
                   * pasted back into x3270.
                   */
                  uc = ebcdic_base_to_unicode(ea_buf[baddr].cc,
                        EUO_BLANK_UNDEF | EUO_UPRIV);
                  *rlen = unicode_to_utf8(uc, (char *)r);
                  if (*rlen < 0)
                        *rlen = 0;
                  return;
            }
          case CS_GE:
            switch (ea_buf[baddr].cc) {
                case EBC_null:
                  *r = 0;
                  return;
                case EBC_Yacute:
                  *r = '[';
                  return;
                case EBC_diaeresis:
                  *r = ']';
                  return;
                default:
                  /* Translate APL to Unicode. */
                  uc = apl_to_unicode(ea_buf[baddr].cc, EUO_NONE);
                  if (uc == (ucs4_t)-1 ||
                      (appres.apl_mode && (uc < 0x100))) {
                        /*
                         * No translation, or we're in APL mode and the
                         * GE character maps back onto a non-GE
                         * character.  Use private-use characters.
                         */
                        uc = UPRIV_GE_00 + ea_buf[baddr].cc;
                  }
                  *rlen = unicode_to_utf8(uc, (char *)r);
                  if (*rlen < 0)
                        *rlen = 0;
                  return;
            }
          case CS_LINEDRAW:
            /* vt100 line-drawing character */
            *r = ea_buf[baddr].cc + 0x5f;
            return;
      }
}

/*
 * Attempt to own the selections in want_sel[].
 */
static void
own_sels(Time t)
{
      register int i, j;

      /*
       * Try to grab any new selections we may want.
       */
      for (i = 0; i < NS; i++) {
            Boolean already_own = False;

            if (want_sel[i] == None)
                  continue;

            /* Check if we already own it. */
            for (j = 0; j < NS; j++)
                  if (own_sel[j].atom == want_sel[i]) {
                        already_own = True;
                        break;
                  }

            /* Find the slot for it. */
            if (!already_own) {
                  for (j = 0; j < NS; j++)
                        if (own_sel[j].atom == None)
                              break;
                  if (j >= NS)
                        continue;
            }

            if (XtOwnSelection(*screen, want_sel[i], t, convert_sel,
                lose_sel, NULL)) {
                  if (!already_own) {
                        n_owned++;
                        own_sel[j].atom = want_sel[i];
                  }
                  Replace(own_sel[j].buffer,
                      XtMalloc(strlen(select_buf) + 1));
                  (void) memmove(own_sel[j].buffer, select_buf,
                      strlen(select_buf) + 1);
            } else {
                  XtWarning("Could not get selection");
                  if (own_sel[j].atom != None) {
                        XtFree(own_sel[j].buffer);
                        own_sel[j].buffer = CN;
                        own_sel[j].atom = None;
                        n_owned--;
                  }
            }
      }
      if (!n_owned)
            unselect(0, ROWS*COLS);
      sel_time = t;
}

/*
 * Copy the selected area on the screen into a buffer and attempt to
 * own the selections in want_sel[].
 */
#define VISUAL_LEFT(d)  ((IS_LEFT(d)) || ((d) == DBCS_SI))
static void
grab_sel(int start, int end, Boolean really, Time t)
{
      register int i, j;
      int start_row, end_row;
      int nulls = 0;
      unsigned char osc[16];
      int len;

      unselect(0, ROWS*COLS);

      if (start > end) {
            int exch = end;

            end = start;
            start = exch;
      }

      start_row = start / COLS;
      end_row = end / COLS;

      init_select_buf();      /* prime the store_sel() routine */
      osc_start();            /* prime the onscreen_char() routine */

      if (!ever_3270 && !toggled(RECTANGLE_SELECT)) {
            /* Continuous selections */
            if (IS_RIGHT(ctlr_dbcs_state(start)))
                  DEC_BA(start);
            if (VISUAL_LEFT(ctlr_dbcs_state(end)))
                  INC_BA(end);
            for (i = start; i <= end; i++) {
                  SET_SELECT(i);
                  if (really) {
                        if (i != start && !(i % COLS)) {
                              nulls = 0;
                              store_sel('\n');
                        }
                        onscreen_char(i, osc, &len);
                        for (j = 0; j < len; j++) {
                              if (osc[j]) {
                                    while (nulls) {
                                          store_sel(' ');
                                          nulls--;
                                    }
                                    store_sel((char)osc[j]);
                              } else
                                    nulls++;
                        }
                  }
            }
            /* Check for newline extension on the last line. */
            if ((end % COLS) != (COLS - 1)) {
                  Boolean all_blank = True;

                  for (i = end; i < end + (COLS - (end % COLS)); i++) {
                        onscreen_char(i, osc, &len);
                        for (j = 0; j < len; j++) {
                              if (osc[j]) {
                                    all_blank = False;
                                    break;
                              }
                        }
                  }
                  if (all_blank) {
                        for (i = end; i < end + (COLS - (end % COLS)); i++) {
                              SET_SELECT(i);
                        }
                        if (really)
                              store_sel('\n');
                  }
            }
      } else {
            /* Rectangular selections */
            if (start_row == end_row) {
                  if (IS_RIGHT(ctlr_dbcs_state(start)))
                        DEC_BA(start);
                  if (VISUAL_LEFT(ctlr_dbcs_state(end)))
                        INC_BA(end);
                  for (i = start; i <= end; i++) {
                        SET_SELECT(i);
                        if (really) {
                              onscreen_char(i, osc, &len);
                              for (j = 0; j < len; j++) {
                                    if (osc[j]) {
                                          while (nulls) {
                                                store_sel(' ');
                                                nulls--;
                                          }
                                          store_sel((char)osc[j]);
                                    } else
                                          nulls++;
                              }
                        }
                  }
                  if (really && (end % COLS == COLS - 1))
                        store_sel('\n');
            } else {
                  int row, col;
                  int start_col = start % COLS;
                  int end_col = end % COLS;

                  if (start_col > end_col) {
                        int exch = end_col;

                        end_col = start_col;
                        start_col = exch;
                  }

                  for (row = start_row; row <= end_row; row++) {
                        int sc = start_col;
                        int ec = end_col;

                        if (sc &&
                            IS_RIGHT(ctlr_dbcs_state(row*COLS + sc)))
                              sc = sc - 1;
                        if (ec < COLS-1 &&
                            VISUAL_LEFT(ctlr_dbcs_state(row*COLS + ec)))
                              ec = ec + 1;

                        for (col = sc; col <= ec; col++) {
                              SET_SELECT(row*COLS + col);
                              if (really) {
                                    onscreen_char(row*COLS + col,
                                        osc, &len);
                                    for (j = 0; j < len; j++) {
                                          if (osc[j]) {
                                                while (nulls) {
                                                      store_sel(' ');
                                                      nulls--;
                                                }
                                                store_sel((char)osc[j]);
                                          } else
                                                nulls++;
                                        }
                              }
                        }
                        nulls = 0;
                        if (really)
                              store_sel('\n');
                  }
            }
      }

      /* Terminate the result. */
      if (really)
            store_sel('\0');

      any_selected = True;
      ctlr_changed(0, ROWS*COLS);
      if (really)
            own_sels(t);
}

/*
 * Check if any character in a given region is selected.
 */
Boolean
area_is_selected(int baddr, int len)
{
      register int i;

      for (i = 0; i < len; i++)
            if (SELECTED(baddr+i))
                  return True;
      return False;
}

/*
 * Unhighlight the region of selected text -- but don't give up the selection.
 */
void
unselect(int baddr _is_unused, int len _is_unused)
{
      if (any_selected) {
            (void) memset((char *) selected, 0, (ROWS*COLS + 7) / 8);
            ctlr_changed(0, ROWS*COLS);
            any_selected = False;
      }
}

/* Selection insertion. */
#define NP  5
static Atom paste_atom[NP];
static int  n_pasting = 0;
static int  pix = 0;
static Time paste_time;
#if defined(XA_UTF8_STRING) /*[*/
static Boolean    paste_utf8;
#endif /*]*/

static void
paste_callback(Widget w, XtPointer client_data _is_unused, Atom *selection _is_unused,
    Atom *type _is_unused, XtPointer value, unsigned long *length,
    int *format _is_unused)
{
      char *s, *t;
      unsigned long s_len, t_len;
      char *ei_buf;
      int ei_len;

      if ((value == NULL) || (*length == 0)) {
            XtFree(value);

            /* Try the next one. */
#if defined(XA_UTF8_STRING) /*[*/
            if (paste_utf8) {
                  paste_utf8 = False;
                  XtGetSelectionValue(w, paste_atom[(pix - 1)], XA_STRING,
                        paste_callback, NULL, paste_time);
            } else
#endif /*]*/
            if (n_pasting > pix) {
#if defined(XA_UTF8_STRING) /*[*/
                  paste_utf8 = True;
#endif /*]*/
                  XtGetSelectionValue(w, paste_atom[pix++],
#if defined(XA_UTF8_STRING) /*[*/
                        XA_UTF8_STRING(display),
#else /*][*/
                        XA_STRING,
#endif /*]*/
                        paste_callback, NULL,
                        paste_time);
            }
            return;
      }

      /* Convert the selection to local multibyte. */
      t_len = 2 * *length;
      t = ei_buf = Malloc(t_len);
      s_len = *length;
      s = (char *)value;
      ei_len = 0;

      while (s_len) {
            ucs4_t uc;
            int nm;

#if defined(XA_UTF8_STRING) /*[*/
            if (paste_utf8) {
                  int nu;

                  nu = utf8_to_unicode(s, s_len, &uc);
                  if (nu <= 0)
                        break;
                  s += nu;
                  s_len -= nu;
            } else
#endif /*]*/
            {
                  /* ISO 8859-1. */
                  uc = *s & 0xff;
                  s++;
                  s_len--;
            }
            nm = unicode_to_multibyte(uc, t, t_len);
            if (nm > 0)
                  nm--;
            t += nm;
            t_len -= nm;
            ei_len += nm;
      }
      (void) emulate_input(ei_buf, ei_len, True);

      XtFree(ei_buf);
      XtFree(value);

      n_pasting = 0;
}

void
insert_selection_action(Widget w, XEvent *event, String *params,
    Cardinal *num_params)
{
      Cardinal i;
      Atom  a;
      XButtonEvent *be = (XButtonEvent *)event;

      action_debug(insert_selection_action, event, params, num_params);

      if (event == NULL) {
            popup_an_error("%s must be called from a keymap\n",
                  action_name(insert_selection_action));
            return;
      }

      n_pasting = 0;
      for (i = 0; i < *num_params; i++) {
            a = XInternAtom(display, params[i], True);
            if (a == None) {
                  popup_an_error("%s: No atom for selection",
                      action_name(insert_selection_action));
                  continue;
            }
            if (n_pasting < NP)
                  paste_atom[n_pasting++] = a;
      }
      pix = 0;
#if defined(XA_UTF8_STRING) /*[*/
      paste_utf8 = True;
#endif /*]*/
      if (n_pasting > pix) {
            paste_time = be->time;
            XtGetSelectionValue(w, paste_atom[pix++],
#if defined(XA_UTF8_STRING) /*[*/
                  XA_UTF8_STRING(display),
#else /*][*/
                  XA_STRING,
#endif /*]*/
                  paste_callback, NULL,
                  paste_time);
      }
}

Generated by  Doxygen 1.6.0   Back to index