/* aguix.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2026 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "aguix.h"
#include "awindow.h"
#include <locale.h>
#include "utf8.hh"
#include "drawablecont.hh"
#include "popupwindow.hh"
#include "bubblewindow.hh"
#include "timeoutstore.hh"
#include "dndwidget.hh"
#include <algorithm>
#include <iostream>
#include "util.h"
#include "akvp_pool.hh"

#ifdef USE_AGUIXTIMER
#include <signal.h>
#endif

#ifdef HAVE_XINERAMA
#  include <X11/extensions/Xinerama.h>
#endif

#define BUBBLE_HELP_OPEN_TIME_MS ( 1 * 1000 )
#define BUBBLE_HELP_TIMEOUT_MS ( 1000 )
#define XDND_TIMEOUT ( 5 )

#ifdef USE_XIM

#ifndef HAVE_XSETIMVALUES_PROTOTYPE
extern "C" {
  char *XSetIMValues( XIM, ... );
}
#endif

#include <X11/Xlocale.h>

/* ChosseBetterStyle is based on an example program I found somewhere
   But there's was no copyright and the orig codepiece also
   doesn't work...
   seems to be from the book "Xlib Programming Manual: For Version 11 of the X Window System"
*/
static XIMStyle ChooseBetterStyle( XIMStyle style1, XIMStyle style2 )
{
  XIMStyle s, t;
  XIMStyle preedit = XIMPreeditArea |
    XIMPreeditCallbacks |
    XIMPreeditPosition |
    XIMPreeditNothing |
    XIMPreeditNone;
  XIMStyle status = XIMStatusArea |
    XIMStatusCallbacks |
    XIMStatusNothing |
    XIMStatusNone;

  if ( style1 == 0 ) return style2;
  if ( style2 == 0 ) return style1;
  if ( ( style1 & ( preedit | status ) ) == ( style2 & ( preedit | status ) ) )
    return style1;

  s = style1 & preedit;
  t = style2 & preedit;
  if ( s != t ) {
    if ( ( s | t ) & XIMPreeditCallbacks ) {
      return ( s == XIMPreeditCallbacks ) ? style1 : style2;
    } else if ( ( s | t ) & XIMPreeditPosition )
      return ( s == XIMPreeditPosition ) ? style1 : style2;
    else if ( ( s | t ) & XIMPreeditArea )
      return ( s == XIMPreeditArea ) ? style1 : style2;
    else if ( ( s | t ) & XIMPreeditNothing )
      return ( s == XIMPreeditNothing ) ? style1 : style2;
  } else {
    s = style1 & status;
    t = style2 & status;
    if ( ( s | t ) & XIMStatusCallbacks)
      return ( s == XIMStatusCallbacks ) ? style1 : style2;
    else if ( ( s | t ) & XIMStatusArea )
      return ( s == XIMStatusArea ) ? style1 : style2;
    else if ( ( s | t ) & XIMStatusNothing )
      return ( s == XIMStatusNothing ) ? style1 : style2;
  }
  return 0;
}

#ifdef XIM_XREGISTER_OKAY
static void AGUIX_im_inst_callback( Display *calldsp, XPointer client_data, XPointer call_data )
{
  if ( client_data != NULL ) {
    ((AGUIX*)client_data)->IMInstCallback( calldsp );
  }
}

static void AGUIX_im_dest_callback( XIM callim, XPointer client_data, XPointer call_data )
{
  if ( client_data != NULL ) {
    ((AGUIX*)client_data)->IMDestCallback();
  }
}
#endif //XIM_XREGISTER_OKAY

#endif //USE_XIM

std::atomic< long > AGUIX::timerEvent( 0 );

static void (*other_sighandler)( int ) = NULL;

#ifdef USE_AGUIXTIMER
static void aguix_timer( int s )
{
  if ( s == SIGALRM ) {
    AGUIX::timerEvent++;

    if ( other_sighandler != NULL ) {
        other_sighandler( s );
    }
  }
}
#endif

static bool s_badwindow_event_occurred = false;
static bool s_ignore_badwindow_event = false;
static int (*base_error_handler)( Display *dsp, XErrorEvent *ee );

static int worker_x_error_handler( Display *dsp, XErrorEvent *ee )
{
    if ( s_ignore_badwindow_event && ee->error_code == BadWindow ) {
        s_badwindow_event_occurred = true;
    } else {
        base_error_handler( dsp, ee );
    }
    return 0;
}

static bool worker_x_bad_window_occured()
{
    bool v = s_badwindow_event_occurred;
    s_badwindow_event_occurred = false;
    return v;
}

void worker_x_ignore_bad_window_error()
{
    s_badwindow_event_occurred = false;
    s_ignore_badwindow_event = true;
}

void worker_x_handle_bad_window_error()
{
    s_ignore_badwindow_event = false;
}

AGUIX::AGUIX( int argc, char **argv, const std::string &classname ) : m_argc( argc ),
                                                                      m_argv( argv ),
                                                                      m_classname( classname )
{
#ifdef USE_AGUIXTIMER
    struct sigaction sig_ac, old_sig_ac;
#endif

  initOK=False;
  mainfont=NULL;
  dsp=NULL;
  m_user_colors = 0;
  m_system_colors = 0;
  m_extra_colors = 0;
  privatecmap=false;
  backpm = None;
  scr = -1;
#ifdef USE_XIM
  im_style = 0;
  inputmethod = NULL;
#endif
  keybuf_len = 10;
  keybuf = (char*)_allocsafe( keybuf_len );
  transientwindow = NULL;
  lastkeyrelease = None;
  lastmouserelease = 0;
  msgLockElement = NULL;
  timerEnabled = false;
  lastTimerEventNr = 0;

#ifdef USE_AGUIXTIMER
  memset( &sig_ac, 0, sizeof( sig_ac ) );
  sig_ac.sa_handler = aguix_timer;
  sigemptyset( &sig_ac.sa_mask );

  sigaction( SIGALRM, &sig_ac, &old_sig_ac );

  if ( old_sig_ac.sa_handler != SIG_ERR &&
       old_sig_ac.sa_handler != SIG_DFL &&
       old_sig_ac.sa_handler != SIG_IGN ) {
      other_sighandler = old_sig_ac.sa_handler;
  }
#endif

  m_open_popup_counter = 0;
  m_popup_ignore_button_release = false;

  m_current_xfocus_window = 0;

  m_apply_window_dialog_type = false;

  m_override_xim = false;
  m_filtered_key_events_in_a_row = 0;
  m_skip_filter_event = false;

  m_default_fg = 1;
  m_default_bg = 0;
  m_3d_bright = 2;
  m_3d_dark = 1;

  m_x_fd = -1;

  m_user_color_instance = 0;

  m_bubble_help.help_candidate = NULL;
  m_bubble_help.enter_time = { 0 };

  initXDNDTypes();

  base_error_handler = XSetErrorHandler( worker_x_error_handler );
}
  
int AGUIX::initX()
{
  debugmsg("Opening display...");

  if ( getOverrideXIM() ) {
      debugmsg("Overwrite XIM...");
      setenv( "XMODIFIERS", "@im=none", 1 );
  }
  
  char *displayname=getenv("DISPLAY");
  dsp=XOpenDisplay(displayname);
  if(dsp==NULL) {
    debugmsg("failed");
    return 5;
  }
  debugmsg("Ok");
#ifdef HAVE_XCONNECTIONNUMBER
  m_x_fd = XConnectionNumber( dsp );
#else
  m_x_fd = -1;
#endif
  
  if ( setlocale( LC_ALL, "" ) == NULL ) {
    fprintf( stderr, "Worker Warning: locale not supported\n" );
  }
  if ( XSupportsLocale() == False ) {
    fprintf( stderr, "Worker Warning: X doesn't support current locale, setting locale to C\n" );
    setlocale( LC_ALL, "C" );
  }
  if ( XSetLocaleModifiers( "" ) == NULL ) {
    fprintf( stderr, "Worker Warning: Cannot set locale modifiers\n" );
  }

  UTF8::checkSupportedEncodings();

  scr=XDefaultScreen(dsp);
  cmap=DefaultColormap(dsp,scr);
  white=WhitePixel(dsp,scr);
  black=BlackPixel(dsp,scr);

  gc=XCreateGC(dsp,RootWindow(dsp,scr),0,0);
  debugmsg( "Loading font..." );
#ifdef HAVE_XFT
  if ( setFont( "Sans-10" ) != 0 ) {
      panic( "no initial font found, exit" );
  }
#else
  if ( setFont( "fixed" ) != 0 ) {
    debugmsg( "Trying generic font..." );
    if ( setFont( "-misc-fixed-medium-r-semicondensed--13-*-*-*-c-*-*-*" ) != 0 ) {
      debugmsg( "Trying even more generic font..." );
      if ( setFont( "-misc-fixed-medium-r-semicondensed--*-*-*-*-c-*-*-*" ) != 0 ) {
        debugmsg( "Trying * as a last resort..." );
        if ( setFont( "*" ) != 0 ) {
          panic( "no fixed font found, exit" );
        }
      }
    }
  }
#endif
  debugmsg("Ok");

  unsigned long gc_valuemask=GCGraphicsExposures|GCForeground|GCBackground|GCLineWidth|GCCapStyle;
  XGCValues gc_values;
  gc_values.graphics_exposures=False;
  gc_values.foreground=black;
  gc_values.background=white;
  gc_values.line_width=0;
  gc_values.cap_style=CapButt;

  XChangeGC(dsp,gc,gc_valuemask,&gc_values);

  dotted_gc = XCreateGC( dsp, RootWindow( dsp, scr ), 0, 0 );
  XCopyGC( dsp, gc, ~0, dotted_gc );
  gc_values.line_style = LineOnOffDash;
  XChangeGC( dsp, dotted_gc, GCLineStyle, &gc_values );

  dashxor_gc = XCreateGC( dsp, RootWindow( dsp, scr ), 0, 0 );
  XCopyGC( dsp, gc, ~0, dashxor_gc );
  gc_values.line_style = LineOnOffDash;
  gc_values.function = GXxor;
  gc_values.plane_mask = ~0;
  gc_values.foreground = 0xffffffff;
  XChangeGC( dsp, dashxor_gc, GCLineStyle | GCFunction | GCPlaneMask | GCForeground, &gc_values );

  dashdouble_gc = XCreateGC( dsp, RootWindow( dsp, scr ), 0, 0 );
  XCopyGC( dsp, gc, ~0, dashdouble_gc );
  gc_values.line_style = LineDoubleDash;
  gc_values.foreground = black;
  gc_values.background = white;
  XChangeGC( dsp, dashdouble_gc, GCLineStyle | GCBackground | GCForeground, &gc_values );

  WM_delete_window=XInternAtom(dsp,"WM_DELETE_WINDOW",False);
  createGroupWin();

  cursors[WAIT_CURSOR]=XCreateFontCursor(dsp,XC_watch);
  cursors[SCROLLH_CURSOR]=XCreateFontCursor(dsp,XC_sb_h_double_arrow);
  cursors[SCROLLV_CURSOR]=XCreateFontCursor(dsp,XC_sb_v_double_arrow);
  cursors[SCROLLFOUR_CURSOR]=XCreateFontCursor(dsp,XC_fleur);
  cursors[DOUBLE_DOWN_CURSOR]=XCreateFontCursor(dsp,XC_sb_down_arrow);
  cursors[DOUBLE_UP_CURSOR]=XCreateFontCursor(dsp,XC_sb_up_arrow);
  cursors[DOUBLE_LEFT_CURSOR]=XCreateFontCursor(dsp,XC_sb_left_arrow);
  cursors[DOUBLE_RIGHT_CURSOR]=XCreateFontCursor(dsp,XC_sb_right_arrow);

  unsigned int rd,rbw;
  int rx, ry;
  Window rootw;
  XGetGeometry( dsp, DefaultRootWindow( dsp ), &rootw, &rx, &ry, &rootWindowWidth, &rootWindowHeight, &rbw, &rd );
#ifdef USE_XIM
  openIM();
#endif

  XA_NET_WM_NAME = XInternAtom( dsp, "_NET_WM_NAME", False );
  XA_NET_WM_ICON_NAME = XInternAtom( dsp, "_NET_WM_ICON_NAME", False );
  XA_TARGETS = XInternAtom( dsp, "TARGETS", False );
  XA_NET_WM_WINDOW_TYPE = XInternAtom( dsp, "_NET_WM_WINDOW_TYPE", False );
  XA_NET_WM_WINDOW_TYPE_DIALOG = XInternAtom( dsp, "_NET_WM_WINDOW_TYPE_DIALOG", False );
  XA_NET_WM_WINDOW_TYPE_NORMAL = XInternAtom( dsp, "_NET_WM_WINDOW_TYPE_NORMAL", False );
  XA_UTF8_STRING = XInternAtom( dsp, "UTF8_STRING", False );
  XA_NET_WM_STATE = XInternAtom( dsp, "_NET_WM_STATE", False );
  XA_NET_WM_STATE_MAXIMIZED_HORZ = XInternAtom( dsp, "_NET_WM_STATE_MAXIMIZED_HORZ", False );
  XA_NET_WM_STATE_MAXIMIZED_VERT = XInternAtom( dsp, "_NET_WM_STATE_MAXIMIZED_VERT", False );

  XA_XDNDENTER = XInternAtom( dsp, "XdndEnter", False );
  XA_XDNDPOSITION = XInternAtom( dsp, "XdndPosition", False );
  XA_XDNDSTATUS = XInternAtom( dsp, "XdndStatus", False );
  XA_XDNDTYPELIST = XInternAtom( dsp, "XdndTypeList", False );
  XA_XDNDACTIONCOPY = XInternAtom( dsp, "XdndActionCopy", False );
  XA_XDNDDROP = XInternAtom( dsp, "XdndDrop", False );
  XA_XDNDLEAVE = XInternAtom( dsp, "XdndLeave", False );
  XA_XDNDFINISHED = XInternAtom( dsp, "XdndFinished", False );
  XA_XDNDSELECTION = XInternAtom( dsp, "XdndSelection", False );
  XA_XDNDPROXY = XInternAtom( dsp, "XdndProxy", False );
  XA_XDNDAWARE = XInternAtom( dsp, "XdndAware", False );

  XA_MULTIPLE = XInternAtom( dsp, "MULTIPLE", False );
  XA_TEXT_URI_LIST = XInternAtom( dsp, "text/uri-list", False );
  XA_TEXT_PLAIN = XInternAtom( dsp, "text/plain", False );

  XA_NET_WM_ICON = XInternAtom( dsp, "_NET_WM_ICON", False );
  XA_CARDINAL_ATOM = XInternAtom( dsp, "CARDINAL", False );
  XA_CLIPBOARD = XInternAtom( dsp, "CLIPBOARD", False );

  char *current_desktop = getenv( "XDG_CURRENT_DESKTOP" );
  bool is_kde_like_desktop = false;

  if ( current_desktop &&
       strcmp( current_desktop, "KDE" ) == 0 ) {
      // not sure, untested
      is_kde_like_desktop = true;
  }

  if ( is_kde_like_desktop ) {
      XA_SPECIAL_COPIED_FILES = XInternAtom( dsp, "x-special/KDE-copied-files", False );
  } else {
      XA_SPECIAL_COPIED_FILES = XInternAtom( dsp, "x-special/gnome-copied-files", False );
  }

  XA_INCR = XInternAtom( dsp, "INCR", False );

  m_xcursor_plus = XCreateFontCursor( dsp, XC_plus );
  m_xcursor_circle = XCreateFontCursor( dsp, XC_circle );

  m_max_chunk_size = XExtendedMaxRequestSize( dsp );
  if ( m_max_chunk_size == 0 ) {
      m_max_chunk_size = XMaxRequestSize( dsp );
  }
  // m_max_chunk_size is multiple of 4 bytes so keep the value as 1/4
  // of the total size as a limit

  if ( m_max_chunk_size == 0 ) {
      m_max_chunk_size = 4096;
  }

  initOK=True;
  return 0;
}

AGUIX::~AGUIX()
{
  AGMessage *agmsg;

  destroyBGHandlers();

  disableTimer();
  while((agmsg=getAGMsg())!=NULL) ReplyMessage(agmsg);
  if(checkX()==True) {
    if(dsp!=NULL) {
      closeX();
    }
  }
  cancelCut( false );
  cancelCut( true );
  _freesafe( keybuf );
}

void AGUIX::panic(const char *msg)
{
  fprintf(stderr,"%s\n",msg);
  exit(1);
}

void AGUIX::closeX()
{
    destroyBGHandlers();

  destroyGroupWin();

  while ( fonts.empty() == false ) {
      AGUIXFont *tf = fonts.front();
      fonts.pop_front();
      delete tf;
  }

  freeColors();
    
  if(privatecmap==true) {
    XFreeColormap(dsp,cmap);
  }
  XFreeCursor(dsp,cursors[WAIT_CURSOR]);
  XFreeCursor(dsp,cursors[SCROLLH_CURSOR]);
  XFreeCursor(dsp,cursors[SCROLLV_CURSOR]);
  XFreeCursor(dsp,cursors[SCROLLFOUR_CURSOR]);
  XFreeCursor(dsp,cursors[DOUBLE_DOWN_CURSOR]);
  XFreeCursor(dsp,cursors[DOUBLE_UP_CURSOR]);
  XFreeCursor(dsp,cursors[DOUBLE_LEFT_CURSOR]);
  XFreeCursor(dsp,cursors[DOUBLE_RIGHT_CURSOR]);
  XFreeCursor( dsp, m_xcursor_plus );
  XFreeCursor( dsp, m_xcursor_circle );

  XFreeGC( dsp, gc );
  XFreeGC( dsp, dotted_gc );
  XFreeGC( dsp, dashxor_gc );
  XFreeGC( dsp, dashdouble_gc );
  if ( backpm != None ) XFreePixmap( dsp, backpm );
#ifdef USE_XIM
  closeIM();
#endif

#ifdef DEBUG
  printf("Closing display\n");
#endif
  XCloseDisplay(dsp);
  dsp=NULL;
}

int AGUIX::checkX()
{
  return initOK;
}

int AGUIX::getDepth() const
{
  return DefaultDepth(dsp,scr);
}

Display *AGUIX::getDisplay() const
{
  return dsp;
}

int AGUIX::getScreen() const
{
  return scr;
}

int AGUIX::getCharHeight() const
{
  return CharHeight;
}

void AGUIX::setFG( const AGUIXColor &color )
{
    XSetForeground( dsp, gc, getPixel( color ) );
}

void AGUIX::setFG( GC tgc, const AGUIXColor &color )
{
    if ( tgc == 0 ) setFG( color );
    else {
        XSetForeground( dsp, tgc, getPixel( color ) );
    }
}

void AGUIX::setBG( const AGUIXColor &color )
{
    XSetBackground( dsp, gc, getPixel( color ) );
}

void AGUIX::setBG( GC tgc, const AGUIXColor &color )
{
    if ( tgc == 0 ) setBG( color );
    else {
        XSetBackground( dsp, tgc, getPixel( color ) );
    }
}

void AGUIX::FillRectangle(Drawable buffer,int x,int y,int w,int h)
{
  if ( w < 1 ) w = 1;
  if ( h < 1 ) h = 1;
  XFillRectangle(dsp,buffer,gc,x,y,w,h);
}

void AGUIX::FillRectangle(Drawable buffer,GC tgc,int x,int y,int w,int h)
{
  if ( w < 1 ) w = 1;
  if ( h < 1 ) h = 1;

  if(tgc==0) FillRectangle(buffer,x,y,w,h);
  else XFillRectangle(dsp,buffer,tgc,x,y,w,h);
}

void AGUIX::DrawText( DrawableCont &dc, const char *text, int x, int y, const AGUIXColor &color )
{
  DrawText( dc, mainfont, text, x, y, color );
}

void AGUIX::DrawText( DrawableCont &dc, AGUIXFont *tf, const char *text, int x, int y, const AGUIXColor &color )
{
  if ( tf == NULL ) {
    mainfont->drawText( dc, text, x, y, color );
  } else {
    tf->drawText( dc, text, x, y, color );
  }
}

int AGUIX::getFontBaseline() const
{
  return mainfont->getBaseline();
}

AGUIXColor AGUIX::AddColor( int red, int green, int blue, AGUIXColor::color_type_t type )
{
    AGUIXColor ret_col( -1, type );

    if ( type == AGUIXColor::SYSTEM_COLOR && m_system_colors >= 16 ) return ret_col;
    if ( type == AGUIXColor::USER_COLOR && m_user_colors >= 256 ) return ret_col;

#ifndef HAVE_XFT
    XColor col;
    col.flags = DoRed | DoGreen | DoBlue;
    col.red = red * 256;
    col.green = green * 256;
    col.blue = blue * 256;
    
    if ( !XAllocColor( dsp, cmap, &col ) ) {
        // error
        if ( privatecmap == true ) return -1;
        else {
            // now try to go to a private colormap
            changeColormap();
            return AddColor( red, green, blue, type );
        }
    }

    if ( type == AGUIXColor::SYSTEM_COLOR ) {
        ret_col = AGUIXColor( m_system_colors++, type );
        m_system_col_buf[ret_col.getColor()] = col.pixel;
    } else if ( type == AGUIXColor::EXTRA_COLOR ) {
        ret_col = AGUIXColor( m_extra_colors++, type );
        m_extra_col_buf.push_back( col.pixel );
    } else {
        ret_col = AGUIXColor( m_user_colors++, type );
        m_user_col_buf[ret_col.getColor()] = col.pixel;
    }
#else
    XRenderColor col;
    col.red = red * 256;
    col.green = green * 256;
    col.blue = blue * 256;
    col.alpha = 0xffff;

    XftColor res;

    if ( ! XftColorAllocValue ( dsp,
                                DefaultVisual( dsp, scr ),
                                cmap,
                                &col,
                                &res ) ) {
        // error
        if ( privatecmap == true ) return -1;
        else {
            // now try to go to a private colormap
            changeColormap();
            return AddColor( red, green, blue, type );
        }
    }

    if ( type == AGUIXColor::SYSTEM_COLOR ) {
        m_system_col_buf[m_system_colors] = res;
        ret_col = AGUIXColor( m_system_colors++, type );
    } else if ( type == AGUIXColor::EXTRA_COLOR ) {
        m_extra_col_buf.push_back( res );
        ret_col = AGUIXColor( m_extra_colors++, type );
    } else {
        m_user_col_buf[m_user_colors] = res;
        ret_col = AGUIXColor( m_user_colors++, type );
    }
#endif
    if ( ret_col.getColor() >= 0 ) {
        m_color_values[ret_col] = col_values_t( red, green, blue );
    }

    if ( type == AGUIXColor::USER_COLOR ) {
        m_user_color_instance++;
        updateSystemColors( m_user_colors - 1 );
    }
    return ret_col;
}

void AGUIX::freeColors()
{
    while ( m_user_colors > 0 ) {
#ifdef HAVE_XFT
        XftColorFree( dsp,
                      DefaultVisual( dsp, scr ),
                      cmap,
                      &m_user_col_buf[ m_user_colors - 1 ] );
#else
        XFreeColors( dsp, cmap, m_user_col_buf + m_user_colors - 1, 1, 0 );
#endif
        m_user_colors--;
    }

    while ( m_system_colors > 0 ) {
#ifdef HAVE_XFT
        XftColorFree( dsp,
                      DefaultVisual( dsp, scr ),
                      cmap,
                      &m_system_col_buf[ m_system_colors - 1 ] );
#else
        XFreeColors( dsp, cmap, m_system_col_buf + m_system_colors - 1, 1, 0 );
#endif
        m_system_colors--;
    }

    while ( m_extra_colors > 0 ) {
#ifdef HAVE_XFT
        XftColorFree( dsp,
                      DefaultVisual( dsp, scr ),
                      cmap,
                      &m_extra_col_buf[ m_extra_colors - 1 ] );
#else
        XFreeColors( dsp, cmap, &m_extra_col_buf[ m_extra_colors - 1 ], 1, 0 );
#endif
        m_extra_colors--;
    }
    m_extra_col_buf.clear();

    m_color_values.clear();

    m_user_color_instance++;
}

int AGUIX::getNumberOfColorsForType( AGUIXColor::color_type_t type ) const
{
    if ( type == AGUIXColor::SYSTEM_COLOR ) {
        return m_system_colors;
    } else if ( type == AGUIXColor::EXTRA_COLOR ) {
        return m_extra_colors;
    } else {
        return m_user_colors;
    }
}

std::string AGUIX::getClassname() const
{
    return m_classname;
}

unsigned long AGUIX::getPixel( const AGUIXColor &color ) const
{
    int col_pos;
    switch( color.getColorType() ) {
      case AGUIXColor::USER_COLOR:
          col_pos = color.getColor();
          if ( col_pos >= m_user_colors ) col_pos = m_user_colors - 1;
          if ( col_pos < 0 ) col_pos = 0;
          if ( col_pos >= m_user_colors ) return 0;
#ifdef HAVE_XFT
          return m_user_col_buf[col_pos].pixel;
#else
          return m_user_col_buf[col_pos];
#endif
          break;
      case AGUIXColor::SYSTEM_COLOR:
          col_pos = color.getColor();
          if ( col_pos >= m_system_colors ) col_pos = m_system_colors - 1;
          if ( col_pos < 0 ) col_pos = 0;
          if ( col_pos >= m_system_colors ) return 0;
#ifdef HAVE_XFT
          return m_system_col_buf[col_pos].pixel;
#else
          return m_system_col_buf[col_pos];
#endif
          break;
      case AGUIXColor::EXTRA_COLOR:
          col_pos = color.getColor();
          if ( col_pos >= m_extra_colors ) col_pos = m_extra_colors - 1;
          if ( col_pos < 0 ) col_pos = 0;
          if ( col_pos >= m_extra_colors ) return 0;
#ifdef HAVE_XFT
          return m_extra_col_buf[col_pos].pixel;
#else
          return m_extra_col_buf[col_pos];
#endif
          break;
    }
    return 0;
}

#ifdef HAVE_XFT
XftColor *AGUIX::getColBufEntry( const AGUIXColor &color )
{
    switch ( color.getColorType() ) {
      case AGUIXColor::USER_COLOR:
          if ( color.getColor() < m_user_colors && color.getColor() >= 0 ) {
              return &m_user_col_buf[color.getColor()];
          }
          break;
      case AGUIXColor::SYSTEM_COLOR:
          if ( color.getColor() < m_system_colors && color.getColor() >= 0 ) {
              return &m_system_col_buf[color.getColor()];
          }
          break;
      case AGUIXColor::EXTRA_COLOR:
          if ( color.getColor() < m_extra_colors && color.getColor() >= 0 ) {
              return &m_extra_col_buf[color.getColor()];
          }
          break;
    }

    // default to first color
    return &m_user_col_buf[0];
}
#else
unsigned long AGUIX::getColBufEntry( const AGUIXColor &color )
{
    switch ( color.getColorType() ) {
      case AGUIXColor::USER_COLOR:
          if ( color.getColor() < m_user_colors && color.getColor() >= 0 ) {
              return m_user_col_buf[color.getColor()];
          }
          break;
      case AGUIXColor::SYSTEM_COLOR:
          if ( color.getColor() < m_system_colors && color.getColor() >= 0 ) {
              return m_system_col_buf[color.getColor()];
          }
          break;
      case AGUIXColor::EXTRA_COLOR:
          if ( color.getColor() < m_extra_colors && color.getColor() >= 0 ) {
              return m_extra_col_buf[color.getColor()];
          }
          break;
    }
    return 0;
}
#endif

Atom *AGUIX::getCloseAtom()
{
  return &WM_delete_window;
}

void AGUIX::insertWindow( AWindow *win, bool change_transient )
{
  if(win==NULL) return;

  if ( msg_handler_active ) {
      // inserting new windows during msg handler is ok but should not
      // be done for consistency
      std::cerr << "Inserting window during X message loop discouraged" << std::endl;
  }
  
  wins.push_back( win );

  if ( win->isTopLevel() == true && change_transient ) {
      setTransientWindow( win );
  }
}

void AGUIX::removeWindow(AWindow *win)
{
  if(win==NULL) return;

  if ( msg_handler_active ) {
      std::cerr << "Deleting window during X message loop forbidden" << std::endl;
      abort();
  }
  
  PopUpWindow *popwin = dynamic_cast<PopUpWindow*>( win );
  if ( popwin != NULL ) {
      popup_wins.remove( popwin );
  }

  wins.remove( win );
  wins_as_transient_for.remove( win );

  if ( win == transientwindow ) {
    AWindow *new_transient_win = NULL;

    if ( ! wins_as_transient_for.empty() ) {
        new_transient_win = wins_as_transient_for.back();
    }

    setTransientWindow( new_transient_win );
  }
}

Message *AGUIX::wait4mess( int mode )
{
  Message *newmsg;
  KeySym J;
  int count;
  AWindow *msgawin;
#ifdef USE_XIM
  Window msgwin;
  Status status;
#endif
  
  if(mode==MES_GET) {
    if(XEventsQueued(dsp,QueuedAfterFlush)==0) return NULL;
  }
  XNextEvent(dsp,&LastEvent);

#ifdef USE_XIM
  switch ( LastEvent.type ) {
    case KeyPress:
      msgwin = LastEvent.xkey.window;

      msgawin = NULL;
      for ( wins_cit_t it1 = wins.begin(); it1 != wins.end(); it1++ ) {
          msgawin = *it1;
          if ( msgawin->isTopLevel() == true ) {
              if ( msgawin->isParent( msgwin, false ) == true ) break;
          }
          msgawin = NULL;
      }
      break;
    default:
      msgawin = NULL;
      break;
  }
  if ( ! m_skip_filter_event &&
       XFilterEvent( &LastEvent, ( msgawin != NULL ) ? msgawin->getWindow() : None ) == True ) {
      if ( LastEvent.type == KeyPress ) {
          m_filtered_key_events_in_a_row++;
      }
      return NULL;
  }

  if ( LastEvent.type == KeyPress ) {
      m_filtered_key_events_in_a_row = 0;
  }
#else
  msgawin = NULL;
#endif

  newmsg = AGUIX_allocMessage();
  newmsg->type=LastEvent.type;
  newmsg->gadgettype=NON_GADGET;
  newmsg->gadget=NULL;
  newmsg->window=0;
  newmsg->time=0;

  newmsg->lockElement = msgLockElement;
  newmsg->ack = false;
  newmsg->loop = 0;

  if ( m_xdnd.drop_issued &&
       now - m_xdnd.last_activity > XDND_TIMEOUT ) {
      cleanupDND();
      m_xdnd = aguix_xdnd_state();
  }

  m_clipboard_state.cleanup_old_requestor( now );

  switch(LastEvent.type) {
    case MapNotify:
    case UnmapNotify:
      newmsg->window = LastEvent.xmap.window;
      break;
    case MappingNotify:
      XRefreshKeyboardMapping((XMappingEvent*)&LastEvent);
      break;
    case KeyPress:
    case KeyRelease:
        if ( ( LastEvent.type == KeyPress ) && ( msgawin != NULL ) ) {
#ifdef USE_XIM
            if ( msgawin->getXIC() != NULL ) {
                count = XmbLookupString( msgawin->getXIC(), &(LastEvent.xkey), keybuf, keybuf_len - 1, &J, &status );
                if ( status == XBufferOverflow ) {
                    _freesafe( keybuf );
                    keybuf_len = count + 1;
                    keybuf = (char*)_allocsafe( keybuf_len );
                    count = XmbLookupString( msgawin->getXIC(), &(LastEvent.xkey), keybuf, keybuf_len - 1, &J, &status );
                }
                switch ( status ) {
                    case XLookupBoth:
                        keybuf[count] = '\0';
                        break;
                    case XLookupKeySym:
                        keybuf[0] = '\0';
                        break;
                    case XLookupChars:
                        J = XK_VoidSymbol;
                        keybuf[count] = '\0';
                        break;
                    default:
                        keybuf[0] = '\0';
                        J = XK_VoidSymbol;
                        break;
                }
                newmsg->key = J;
            } else {
                count = XLookupString( &(LastEvent.xkey), keybuf, keybuf_len - 1, &J, NULL );
                keybuf[ count ] = '\0';
                newmsg->key=J;
            }
#endif
        } else {
            count = XLookupString( &(LastEvent.xkey), keybuf, keybuf_len - 1, &J, NULL );
            keybuf[ count ] = '\0';
            newmsg->key=J;
        }
        newmsg->keystate=LastEvent.xkey.state;
        newmsg->window=LastEvent.xkey.window;
        newmsg->mousex=LastEvent.xkey.x;
        newmsg->mousey=LastEvent.xkey.y;
        newmsg->keybuf = dupstring( keybuf );
        newmsg->time = LastEvent.xkey.time;

        if ( newmsg->key == XK_Shift_L ||
             newmsg->key == XK_Shift_R ) {
            if ( newmsg->type == KeyPress ) {
                m_modifier_pressed |= ShiftMask;
            } else {
                m_modifier_pressed &= ~ShiftMask;
            }
        }

        if ( newmsg->type == KeyPress &&
             newmsg->key == XK_Escape &&
             m_xdnd.drag_active &&
             m_xdnd.drag_current_target_window != None ) {
            // abort DND

            // mark as internal so we dont create an AGMessage
            newmsg->internal = true;

            abortDND();
        }
        break;
    case ButtonPress:
    case ButtonRelease:
      newmsg->button = LastEvent.xbutton.button;
      newmsg->keystate = LastEvent.xbutton.state;
      newmsg->mousex = LastEvent.xbutton.x;
      newmsg->mousey = LastEvent.xbutton.y;
      newmsg->window = LastEvent.xbutton.window;
      newmsg->time = LastEvent.xbutton.time;

      if ( LastEvent.type == ButtonPress ) {
          m_last_click_modifier = KEYSTATEMASK( newmsg->keystate );
      }

      if ( LastEvent.type == ButtonRelease &&
           m_xdnd.drag_active &&
           LastEvent.xbutton.button == m_xdnd.cancel_mouse_button ) {

          if ( m_xdnd.skip_release_count <= 0 ) {
              // abort DND

              // mark as internal so we dont create an AGMessage
              newmsg->internal = true;

              abortDND();
          } else {
              m_xdnd.skip_release_count--;
          }
      } else if ( LastEvent.type == ButtonRelease &&
                  m_xdnd.drag_active &&
                  LastEvent.xbutton.button == m_xdnd.mouse_button ) {

          XClientMessageEvent m;

          memset( &m, 0, sizeof( m ) );

          m.type = ClientMessage;
          m.display = LastEvent.xbutton.display;

          if ( m_xdnd.drag_target_status == m_xdnd.AGUIX_XDND_ACCEPT ) {
              m.window = m_xdnd.drag_current_proxy_target_window != None ? m_xdnd.drag_current_proxy_target_window : m_xdnd.drag_current_target_window;
              m.message_type = XA_XDNDDROP;

              m.format = 32;
              m.data.l[0] = m_xdnd.drag_initiator_window;
              m.data.l[1] = 0;
              m.data.l[2] = CurrentTime;
              m.data.l[3] = 0;
              m.data.l[4] = 0;

              m_xdnd.drop_issued = true;
              m_xdnd.last_activity = now;
          } else {
              // send leave
              m.window = m_xdnd.drag_current_proxy_target_window != None ? m_xdnd.drag_current_proxy_target_window : m_xdnd.drag_current_target_window;
              m.message_type = XA_XDNDLEAVE;

              m.format = 32;
              m.data.l[0] = m_xdnd.drag_initiator_window;
              m.data.l[1] = 0;
              m.data.l[2] = 0;
              m.data.l[3] = 0;
              m.data.l[4] = 0;

              cleanupDND();
          }

          worker_x_ignore_bad_window_error();
          XSendEvent( dsp, m.window,
                      False, NoEventMask, (XEvent*)&m );

          XFlush( dsp );

          XUngrabPointer( dsp, CurrentTime );

          m_xdnd.drag_active = false;
          m_xdnd.drag_target_status = m_xdnd.AGUIX_XDND_UNAWARE;
          m_xdnd.drag_current_target_window = None;
          m_xdnd.drag_current_target_version = -1;
      }
      break;
    case MotionNotify:
        /* I no longer use XQueryPointer here because of error if the window
           this message is for no longer exists and then XQueryPointer fails
           Instead use queryPointer when get AG_MOUSEMOVE */
        newmsg->mousex = LastEvent.xmotion.x;
        newmsg->mousey = LastEvent.xmotion.y;
        newmsg->mouserx = LastEvent.xmotion.x_root;
        newmsg->mousery = LastEvent.xmotion.y_root;
        newmsg->window = LastEvent.xmotion.window;
        newmsg->keystate = LastEvent.xmotion.state;

		if ( m_xdnd.drag_active ) {
			Window window = None;
			Window proxy_window = None;
			Atom atmp;
			int xdnd_version = -1;
			int fmt;
			unsigned long nitems, bytes_remaining;
			unsigned char *data = NULL;

			window = findXDNDAwareWindow( RootWindow( dsp, scr ), &proxy_window );

            worker_x_ignore_bad_window_error();

            if ( window == None ) {
                // not dnd aware window
            } else if ( window == m_xdnd.drag_current_target_window ) {
                // still the same window
                xdnd_version = m_xdnd.drag_current_target_version;
            } else if ( XGetWindowProperty( dsp,
                                            window,
                                            XA_XDNDAWARE,
                                            0,
                                            2,
                                            False,
                                            AnyPropertyType,
                                            &atmp,
                                            &fmt,
                                            &nitems,
                                            &bytes_remaining,
                                            &data ) != Success ) {
                // could not get property
            } else if ( data == NULL ) {
                // got no data
            } else if ( fmt != 32 ) {
                // unexpected format
                XFree( data );
            } else if ( nitems != 1 ) {
                // there should only be one item
                XFree( data );
            } else {
				xdnd_version = data[0];
                XFree( data );
			}

            if ( worker_x_bad_window_occured() ) {
                xdnd_version = -1;
                window = None;
            }

			if ( m_xdnd.drag_target_status == m_xdnd.AGUIX_XDND_UNAWARE && xdnd_version != -1 ) {
				m_xdnd.drag_target_status = m_xdnd.AGUIX_XDND_UNRESPONSIVE;
            } else if ( xdnd_version == -1 ) {
				m_xdnd.drag_target_status = m_xdnd.AGUIX_XDND_UNAWARE;
            }

			if ( m_xdnd.drag_target_status == m_xdnd.AGUIX_XDND_UNAWARE ) {
				XChangeActivePointerGrab( dsp, ButtonMotionMask | ButtonReleaseMask, m_xcursor_circle, CurrentTime );
            } else if ( m_xdnd.drag_target_status == m_xdnd.AGUIX_XDND_UNRESPONSIVE ) {
				XChangeActivePointerGrab( dsp, ButtonMotionMask | ButtonReleaseMask, m_xcursor_circle, CurrentTime );
            } else {
				XChangeActivePointerGrab( dsp, ButtonMotionMask | ButtonReleaseMask, m_xcursor_plus, CurrentTime );
            }

			if ( window != m_xdnd.drag_current_target_window && m_xdnd.drag_current_target_version != -1 ) {
				XClientMessageEvent m;

				memset( &m, 0, sizeof( m ) );
				m.type = ClientMessage;
				m.display = LastEvent.xmotion.display;
                m.window = m_xdnd.drag_current_proxy_target_window != None ? m_xdnd.drag_current_proxy_target_window : m_xdnd.drag_current_target_window;
				m.message_type = XA_XDNDLEAVE;
				m.format = 32;
				m.data.l[0] = m_xdnd.drag_initiator_window;
				m.data.l[1] = 0;
				m.data.l[2] = 0;
				m.data.l[3] = 0;
				m.data.l[4] = 0;

                worker_x_ignore_bad_window_error();
				XSendEvent( dsp, m.window, False, NoEventMask, (XEvent*)&m );
				XFlush( dsp );
			}

			if  ( window != m_xdnd.drag_current_target_window && xdnd_version != -1 ) {
				XClientMessageEvent m;
				memset( &m, 0, sizeof( m ) );
				m.type = ClientMessage;
				m.display = LastEvent.xmotion.display;
				m.window = proxy_window != None ? proxy_window : window;
				m.message_type = XA_XDNDENTER;
				m.format = 32;
				m.data.l[0] = m_xdnd.drag_initiator_window;
				m.data.l[1] = a_min( 5, xdnd_version ) << 24 | 0; // bit for more than three types not set
				m.data.l[2] = XA_TEXT_PLAIN;
				m.data.l[3] = XA_TEXT_URI_LIST;

                worker_x_ignore_bad_window_error();
                XSendEvent( dsp, m.window, False, NoEventMask, (XEvent*)&m );
				XFlush( dsp );

                m_xdnd.position_silent = false;
			}

			if ( xdnd_version != -1) {
				int x, y, tmp;
				unsigned int utmp;
				Window wtmp;

                worker_x_ignore_bad_window_error();
				XQueryPointer( dsp, window, &wtmp, &wtmp, &x, &y, &tmp, &tmp, &utmp );
                if ( worker_x_bad_window_occured() ) {
                    xdnd_version = -1;
                    window = None;
                } else if ( m_xdnd.position_silent == false ||
                            m_xdnd.position_silent_w == 0 ||
                            m_xdnd.position_silent_h == 0 ||
                            x < m_xdnd.position_silent_x ||
                            x >= m_xdnd.position_silent_x + m_xdnd.position_silent_w ||
                            y < m_xdnd.position_silent_y ||
                            y >= m_xdnd.position_silent_y + m_xdnd.position_silent_h ) {
                    XClientMessageEvent m;
                    memset( &m, 0, sizeof( m ) );
                    m.type = ClientMessage;
                    m.display = LastEvent.xmotion.display;
                    m.window = proxy_window != None ? proxy_window : window;
                    m.message_type = XA_XDNDPOSITION;
                    m.format = 32;
                    m.data.l[0] = m_xdnd.drag_initiator_window;
                    m.data.l[1] = 0;
                    m.data.l[2] = ( x << 16 ) | y;
                    m.data.l[3] = CurrentTime;
                    m.data.l[4] = XA_XDNDACTIONCOPY;

                    worker_x_ignore_bad_window_error();
                    XSendEvent( dsp, m.window, False, NoEventMask, (XEvent*)&m );
                    XFlush( dsp);

                    m_xdnd.drag_selection_pending = true;
                }
			}

			m_xdnd.drag_current_target_window  = window;	
			m_xdnd.drag_current_target_version = xdnd_version;
            m_xdnd.drag_current_proxy_target_window = None;
		}
        break;
    case Expose:
      newmsg->window = LastEvent.xexpose.window;
      newmsg->x = LastEvent.xexpose.x;
      newmsg->y = LastEvent.xexpose.y;
      newmsg->width = LastEvent.xexpose.width;
      newmsg->height = LastEvent.xexpose.height;
      break;
    case ConfigureNotify:
      // first try to find further configure notifies for
      // this window to improve handling of many resizes due to
      // solid resize used by many WMs
      while ( XCheckTypedWindowEvent( dsp,
				      LastEvent.xconfigure.window,
				      ConfigureNotify,
				      &LastEvent ) == True );
      newmsg->window=LastEvent.xconfigure.window;
      newmsg->mousex=LastEvent.xconfigure.x;
      newmsg->mousey=LastEvent.xconfigure.y;
      newmsg->width = LastEvent.xconfigure.width;
      newmsg->height = LastEvent.xconfigure.height;
      newmsg->x = LastEvent.xconfigure.x;
      newmsg->y = LastEvent.xconfigure.y;
      break;
    case ClientMessage:
      if(LastEvent.xclient.data.l[0]==(long)WM_delete_window) {
        newmsg->gadgettype=CLOSE_GADGET;
        newmsg->gadget=NULL;
        newmsg->window=LastEvent.xclient.window;
      } else if ( LastEvent.xclient.message_type == XA_XDNDENTER ) {
          handleXDNDEnter( &LastEvent );
      } else if ( LastEvent.xclient.message_type == XA_XDNDPOSITION ) {
          handleXDNDPosition( &LastEvent );
      } else if ( LastEvent.xclient.message_type == XA_XDNDLEAVE ) {
          handleXDNDLeave( &LastEvent );
      } else if ( LastEvent.xclient.message_type == XA_XDNDDROP ) {
          handleXDNDDrop( &LastEvent );
      } else if ( LastEvent.xclient.message_type == XA_XDNDSTATUS ) {
          handleXDNDStatus( &LastEvent );
      } else if ( LastEvent.xclient.message_type == XA_XDNDFINISHED ) {
          handleXDNDFinished( &LastEvent );
      }
      break;
    case EnterNotify:
    case LeaveNotify:
      newmsg->window=LastEvent.xcrossing.window;
      newmsg->mousex=LastEvent.xcrossing.x;
      newmsg->mousey=LastEvent.xcrossing.y;
      break;
    case SelectionClear:
        if( m_primary_buffer_state.cutstart &&
            LastEvent.xselectionclear.window == getGroupWin() &&
            LastEvent.xselection.selection == XA_PRIMARY ) {
            cancelCut( false );
        } else if( m_clipboard_state.cutstart &&
                   LastEvent.xselectionclear.window == getGroupWin() &&
                   LastEvent.xselection.selection == XA_CLIPBOARD ) {
            cancelCut( true );
        } else if( LastEvent.xselectionclear.window == getGroupWin() &&
                   LastEvent.xselection.selection == XA_PRIMARY ) {
            m_primary_buffer_state.buffer_string = "";
        } else if( LastEvent.xselectionclear.window == getGroupWin() &&
                   LastEvent.xselection.selection == XA_CLIPBOARD ) {
            m_clipboard_state.buffer_string = "";
            m_clipboard_state.file_list.clear();
        }

        if ( ! m_primary_buffer_state.cutstart &&
             ! m_clipboard_state.cutstart &&
             m_xdnd.drag_active ) {
            m_xdnd = aguix_xdnd_state();

            cleanupDND();
        }
      break;
    case SelectionNotify:
        {
            Atom target = LastEvent.xselection.target;

            if ( m_xdnd.drop_active && m_xdnd.target_awin ) {
                AGUIX_XProperty prop = getProperty( m_xdnd.target_awin->getWindow(), XA_PRIMARY );

                if ( target == XA_TARGETS && ! m_xdnd.sent_target_request ) {
                    m_xdnd.sent_target_request = true;

					m_xdnd.selected_target = selectBestTargetForDND( prop );

					if ( m_xdnd.selected_target == None) {
                        m_xdnd.drop_active = false;
					} else {
						XConvertSelection( dsp,
                                           XA_PRIMARY,
                                           m_xdnd.selected_target,
                                           XA_PRIMARY,
                                           m_xdnd.target_awin->getWindow(),
                                           CurrentTime );
					}
                } else if ( target == m_xdnd.selected_target ) {
                    std::string dnd_data( (const char*)prop.property_data,
                                          prop.number_of_items * prop.format / 8 );

                    AGMessage *agmsg = AGUIX_allocAGMessage();
                    agmsg->type = AG_DND_END;
                    agmsg->dnd.target_awindow = m_xdnd.target_awin;

                    if ( m_xdnd.drag_initiator ) {
                        if ( auto *dndw = dynamic_cast<DNDWidget*>( m_xdnd.drag_initiator ) ) {
                            dndw->addDragData( agmsg );
                        }
                    } else {
                        agmsg->dnd.specialinfo.external_data = new std::string( dnd_data );
                    }
                    
                    if ( m_xdnd.target_widget ) {
                        if ( auto *dndw = dynamic_cast<DNDWidget*>( m_xdnd.target_widget ) ) {
                            dndw->processDNDDrop( agmsg );
                            agmsg = nullptr;
                        }
                    }

                    AGUIX_freeAGMessage( agmsg );

                    cleanupDND();
                    
                    XClientMessageEvent m;

                    memset( &m, 0, sizeof( m ) );
                    m.type = ClientMessage;
                    m.display = dsp;
                    m.window = m_xdnd.source;
                    m.message_type = XA_XDNDFINISHED;
                    m.format = 32;
                    m.data.l[0] = m_xdnd.target_awin ? m_xdnd.target_awin->getWindow() : None;
                    m.data.l[1] = 1;
                    m.data.l[2] = XA_XDNDACTIONCOPY;

                    worker_x_ignore_bad_window_error();
                    XSendEvent( dsp,
                                m_xdnd.source,
                                False,
                                NoEventMask,
                                (XEvent*)&m );
                    XFlush( dsp );
                }

                prop.freeProperty();

                m_xdnd.drop_active = false;
            } else if ( ( LastEvent.xselection.selection == XA_PRIMARY ||
                          LastEvent.xselection.selection == XA_CLIPBOARD ) &&
                        LastEvent.xselection.property != None ) {
                Atom ret_type;
                int ret_format;
                unsigned long ret_len,ret_after;
                unsigned char *ret_prop;
                const bool use_clipboard = LastEvent.xselection.selection == XA_CLIPBOARD;
                copy_and_paste_state &state = use_clipboard ? m_clipboard_state : m_primary_buffer_state;
                if ( XGetWindowProperty(dsp,
                                        LastEvent.xselection.requestor,
                                        LastEvent.xselection.property,
                                        0,
                                        8192,
                                        False,
                                        LastEvent.xselection.target,
                                        &ret_type,
                                        &ret_format,
                                        &ret_len,
                                        &ret_after,
                                        &ret_prop) == Success ) {
                    if (ret_len > 0) {
                        if ( LastEvent.xselection.target == XA_SPECIAL_COPIED_FILES ) {
                            auto res = convertPasteDataToFileList( ret_prop );

                            AGMessage *agmsg = AGUIX_allocAGMessage();
                            agmsg->type = AG_PASTE_FILE_LIST;
                            agmsg->paste.agmessage_data = state.paste_agmessage_data;
                            agmsg->paste.file_list = new auto( res );
                            putAGMsg( agmsg );
                        } else {
                            if ( state.paste_elem ) {
                                state.paste_elem->paste( ret_prop );
                            }
                        }
                        XDeleteProperty(dsp,LastEvent.xselection.requestor,LastEvent.xselection.property);
                    } else {
                        if ( state.paste_elem !=NULL ) state.paste_elem->cancelpaste();
                        if ( LastEvent.xselection.target == XA_SPECIAL_COPIED_FILES ) {
                            AGMessage *agmsg = AGUIX_allocAGMessage();
                            agmsg->type = AG_PASTE_FILE_LIST;
                            agmsg->paste.agmessage_data = state.paste_agmessage_data;
                            agmsg->paste.file_list = new std::tuple< int, std::string, std::vector< std::string > >();
                            std::get<0>( *agmsg->paste.file_list ) = -ENOENT;
                            putAGMsg( agmsg );
                        }

                        state.reset();
                    }
                    XFree(ret_prop);
                } else {
                    if ( state.paste_elem !=NULL ) state.paste_elem->cancelpaste();
                    if ( LastEvent.xselection.target == XA_SPECIAL_COPIED_FILES ) {
                        AGMessage *agmsg = AGUIX_allocAGMessage();
                        agmsg->type = AG_PASTE_FILE_LIST;
                        agmsg->paste.agmessage_data = state.paste_agmessage_data;
                        agmsg->paste.file_list = new std::tuple< int, std::string, std::vector< std::string > >();
                        std::get<0>( *agmsg->paste.file_list ) = -EINVAL;
                        putAGMsg( agmsg );
                    }

                    state.reset();
                }
            } else if ( ( LastEvent.xselection.selection == XA_PRIMARY ||
                          LastEvent.xselection.selection == XA_CLIPBOARD ) &&
                        LastEvent.xselection.property == None &&
                        m_primary_buffer_state.last_selection_request_atom != XA_UTF8_STRING ) {
                const bool use_clipboard = LastEvent.xselection.selection == XA_CLIPBOARD;
                copy_and_paste_state &state = use_clipboard ? m_clipboard_state : m_primary_buffer_state;
                if ( state.paste_elem !=NULL ) state.paste_elem->cancelpaste();
                if ( LastEvent.xselection.target == XA_SPECIAL_COPIED_FILES ) {
                    AGMessage *agmsg = AGUIX_allocAGMessage();
                    agmsg->type = AG_PASTE_FILE_LIST;
                    agmsg->paste.agmessage_data = state.paste_agmessage_data;
                    agmsg->paste.file_list = new std::tuple< int, std::string, std::vector< std::string > >();
                    std::get<0>( *agmsg->paste.file_list ) = -ENOENT;
                    putAGMsg( agmsg );
                }

                state.reset();
            } else {
                // start re-request with alternative type (STRING instead of UTF8_STRING)
                rerequestCut();
            }
        }
        break;
    case SelectionRequest:
        if ( m_xdnd.drag_selection_pending &&
             LastEvent.xselectionrequest.selection == XA_XDNDSELECTION ) {
            processSelectionRequest( &LastEvent );
        } else {
            XEvent ev;
            bool dont_send = false;

            ev.type=SelectionNotify;
            ev.xselection.requestor=LastEvent.xselectionrequest.requestor;
            ev.xselection.selection=LastEvent.xselectionrequest.selection;
            ev.xselection.target=LastEvent.xselectionrequest.target;
            ev.xselection.time=LastEvent.xselectionrequest.time;
            ev.xselection.property=LastEvent.xselectionrequest.property;
            if( ( LastEvent.xselectionrequest.selection == XA_PRIMARY ||
                  LastEvent.xselectionrequest.selection == XA_CLIPBOARD ) &&
                ( LastEvent.xselectionrequest.target==XA_STRING ) ) {
                copy_and_paste_state &state = LastEvent.xselectionrequest.selection == XA_CLIPBOARD ? m_clipboard_state : m_primary_buffer_state;

                worker_x_ignore_bad_window_error();

                std::string data;

                if ( state.file_list.empty() ) {
                    data = state.buffer_string;
                } else {
                    for ( const auto &l : state.file_list ) {
                        if ( ! data.empty() ) {
                            data += "\n";
                        }
                        data += l;
                    }
                }

                XChangeProperty(dsp,
                                ev.xselection.requestor,
                                ev.xselection.property,
                                ev.xselection.target,
                                8,
                                PropModeReplace,
                                (unsigned char*)data.c_str(),
                                data.length());

                if ( worker_x_bad_window_occured() ) {
                    dont_send = true;
                }
#ifndef DISABLE_UTF8_SUPPORT
            } else if( ( LastEvent.xselectionrequest.selection == XA_PRIMARY ||
                         LastEvent.xselectionrequest.selection == XA_CLIPBOARD ) &&
                       LastEvent.xselectionrequest.target == XA_UTF8_STRING &&
                       UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED &&
                       UTF8::getCurrentEncoding() == UTF8::ENCODING_UTF8 ) {
                copy_and_paste_state &state = LastEvent.xselectionrequest.selection == XA_CLIPBOARD ? m_clipboard_state : m_primary_buffer_state;

                worker_x_ignore_bad_window_error();

                std::string data;

                if ( state.file_list.empty() ) {
                    data = state.buffer_string;
                } else {
                    for ( const auto &l : state.file_list ) {
                        if ( ! data.empty() ) {
                            data += "\n";
                        }
                        data += l;
                    }
                }

                XChangeProperty(dsp,
                                ev.xselection.requestor,
                                ev.xselection.property,
                                ev.xselection.target,
                                8,
                                PropModeReplace,
                                (unsigned char*)data.c_str(),
                                data.length());

                if ( worker_x_bad_window_occured() ) {
                    dont_send = true;
                }
#endif
            } else if ( LastEvent.xselectionrequest.selection == XA_CLIPBOARD &&
                        LastEvent.xselectionrequest.target == XA_SPECIAL_COPIED_FILES ) {
                copy_and_paste_state &state = m_clipboard_state;
                ev.xselection.target = XA_SPECIAL_COPIED_FILES;

                if ( state.file_list.empty() ) {
                    ev.xselection.property = None;
                } else {
                    std::string s;

                    if ( state.file_list_cut ) {
                        s += "cut";
                    } else {
                        s += "copy";
                    }

                    for ( const auto &f : state.file_list ) {
                        s += "\nfile://";
                        s += AGUIXUtils::escape_uri( f );
                    }

                    worker_x_ignore_bad_window_error();

                    // there is supposted to be a null byte at the end
                    // so include that too
                    XChangeProperty( dsp,
                                     ev.xselection.requestor,
                                     ev.xselection.property,
                                     ev.xselection.target,
                                     8,
                                     PropModeReplace,
                                     (unsigned char*)s.c_str(),
                                     s.length() + 1 );

                    if ( worker_x_bad_window_occured() ) {
                        dont_send = true;
                    }
                }
            } else if ( LastEvent.xselectionrequest.selection == XA_CLIPBOARD &&
                        ev.xselection.target == m_clipboard_state.target ) {
                copy_and_paste_state &state = m_clipboard_state;

                worker_x_ignore_bad_window_error();

                if ( state.buffer_string.size() > (size_t)m_max_chunk_size ) {
                    XChangeProperty( dsp,
                                     ev.xselection.requestor,
                                     ev.xselection.property,
                                     XA_INCR,
                                     32, PropModeReplace, 0, 0 );

                    /* get events for requestor window */
                    XSelectInput( dsp, ev.xselection.requestor, PropertyChangeMask );

                    m_clipboard_state.remove_requestor( ev.xselection.requestor );
                    m_clipboard_state.requestors.push_back( {
                            .requestor_window = ev.xselection.requestor,
                            .property = ev.xselection.property,
                            .target_atom = ev.xselection.target,
                            .buffer_pos = 0,
                            .in_progress = true,
                            .buffer_string = state.buffer_string,
                            .last_update = now } );
                } else {
                    XChangeProperty(dsp,
                                    ev.xselection.requestor,
                                    ev.xselection.property,
                                    ev.xselection.target,
                                    8,
                                    PropModeReplace,
                                    (unsigned char*)state.buffer_string.c_str(),
                                    state.buffer_string.length());

                    if ( worker_x_bad_window_occured() ) {
                        dont_send = true;
                    }
                }
            } else if ( LastEvent.xselectionrequest.target == XA_TARGETS ) {
                // send the list of supported atoms
                std::vector< Atom > supported_targets = { XA_TARGETS };

                if ( LastEvent.xselectionrequest.selection == XA_CLIPBOARD &&
                     m_clipboard_state.target != None &&
                     m_clipboard_state.target != XA_STRING &&
                     m_clipboard_state.target != XA_UTF8_STRING &&
                     m_clipboard_state.target != XA_SPECIAL_COPIED_FILES ) {
                    supported_targets.push_back( m_clipboard_state.target );
                }
                supported_targets.push_back( XA_STRING );
                supported_targets.push_back( XA_UTF8_STRING );

                if ( LastEvent.xselectionrequest.selection == XA_CLIPBOARD &&
                     ! m_clipboard_state.file_list.empty() ) {
                    supported_targets.push_back( XA_SPECIAL_COPIED_FILES );
                }

                worker_x_ignore_bad_window_error();

                XChangeProperty( dsp,
                                 ev.xselection.requestor,
                                 ev.xselection.property,
                                 XA_ATOM,
                                 32, PropModeReplace,
                                 (unsigned char *)&supported_targets[0],
                                 (int)supported_targets.size() );

                if ( worker_x_bad_window_occured() ) {
                    dont_send = true;
                }
            } else {
                ev.xselection.property=None;
            }

            if ( dont_send == false ) {
                worker_x_ignore_bad_window_error();
                XSendEvent(dsp,ev.xselection.requestor,False,0,&ev);
                XFlush( dsp );
            }
        }
        break;
    case PropertyNotify:
        if ( LastEvent.xproperty.state == PropertyDelete ) {
            auto &req_state = m_clipboard_state.find_requestor( LastEvent.xproperty.window );
            if ( req_state.requestor_window != None &&
                 req_state.in_progress ) {

                if ( req_state.buffer_pos < req_state.buffer_string.length() ) {
                    size_t remaining_length = req_state.buffer_string.length();
                    remaining_length -= req_state.buffer_pos;

                    if ( remaining_length > (size_t)m_max_chunk_size ) {
                        remaining_length = m_max_chunk_size;
                    }

                    XChangeProperty( dsp,
                                     req_state.requestor_window,
                                     req_state.property,
                                     req_state.target_atom,
                                     8, PropModeReplace,
                                     (unsigned char *)&req_state.buffer_string.c_str()[ req_state.buffer_pos ],
                                     remaining_length );

                    req_state.buffer_pos += remaining_length;
                    req_state.last_update = now;
                } else {
                    XChangeProperty( dsp,
                                     req_state.requestor_window,
                                     req_state.property,
                                     req_state.target_atom,
                                     8, PropModeReplace, 0, 0 );

                    m_clipboard_state.remove_requestor( req_state.requestor_window );
                }
            }
        }
        break;
    case FocusIn:
      newmsg->window = LastEvent.xfocus.window;
      m_current_xfocus_window = LastEvent.xfocus.window;
      break;
    case FocusOut:
      newmsg->window = LastEvent.xfocus.window;
      m_current_xfocus_window = 0;
      break;
  }
  return newmsg;
}

void AGUIX::buildAGMessage( Message *msg )
{
  AGMessage *agmsg[2];

  if ( ! msg->internal ) {
      agmsg[0] = AGUIX_allocAGMessage( msg );  // Hauptmessage
  } else {
      agmsg[0] = NULL;
  }
  agmsg[1] = NULL;           // eventl. Nebenmessage;
  switch( msg->type ) {
    case ButtonRelease:
      agmsg[1] = AGUIX_allocAGMessage();
      agmsg[1]->type = AG_MOUSECLICKED;
      agmsg[1]->mouse.window = msg->window;
      agmsg[1]->mouse.button = msg->button;
      agmsg[1]->mouse.x = msg->mousex;
      agmsg[1]->mouse.y = msg->mousey;
      agmsg[1]->mouse.time = msg->time;
  }
  // agmsg in FIFO speichern
  if ( agmsg[0] != NULL ) putAGMsg( agmsg[0] );
  if ( agmsg[1] != NULL ) putAGMsg( agmsg[1] );
}

int AGUIX::msgHandler( int mode, AWindow *parent, bool onlyExpose )
{
  Message *tmsg;
  int waitmode;
  long timerEventNr;
  struct timeval curTime;
#ifndef USE_AGUIXTIMER
  long t1;
#endif

  if ( msg_handler_active ) {
      std::cerr << "Error: Calling msgHandler while active" << std::endl;
      abort();
  }

  msg_handler_active = true;

  // do not limit to special window if there is a guielement
  // holding the lock
  if ( msgLockElement != NULL ) parent = NULL;

  now = time( NULL );
  
  for (;;) {
    waitmode = mode;

    // do not wait when timer is enabled
    if ( ( waitmode == MES_WAIT ) &&
	 ( msgLockElement != NULL ) &&
	 ( timerEnabled == true ) ) {
      waitmode = MES_GET;
    }

    if ( ( msgLockElement != NULL ) &&
	 ( timerEnabled == true ) ) {
      onlyExpose = false;
    }
    tmsg = wait4mess( waitmode );
    if ( tmsg != NULL ) {
      if ( ( onlyExpose == false ) || ( ( onlyExpose == true ) && ( tmsg->type == Expose ) ) ) {
	if ( ReactMessage( tmsg, parent ) == false ) {
          if ( msgLockElement == NULL ) {
            // only build msg for application when no element holds the lock
            //TODO: at the time of this writing (20031115) it would be okay
            //      to build the msg in any case, AGUIX can handle this
            //      but I keep this conforming to previous version
            buildAGMessage( tmsg );
          }
        }
      }
      if ( tmsg->type == ButtonRelease ) {
	lastmouserelease = tmsg->button;
      } else if ( tmsg->type == KeyRelease ) {
	lastkeyrelease = tmsg->key;
      }

      checkPopUpWindows( tmsg );
      
      AGUIX_freeMessage( tmsg );
    }

    if ( XEventsQueued( dsp, QueuedAfterFlush ) > 0 ) continue;
    // and check whether we hit a timer limit

    if ( ! getOverrideXIM() &&
         m_filtered_key_events_in_a_row == 10 ) {

        m_filtered_key_events_in_a_row++;

        tmsg = AGUIX_allocMessage();
        tmsg->type = ClientMessage;
        tmsg->specialType = Message::XIMSTUCK;
      
        buildAGMessage( tmsg );
        AGUIX_freeMessage( tmsg );
    }

    // break loop when timer isn't activated
    if ( ! ( ( msgLockElement != NULL ) &&
	     ( timerEnabled == true ) ) ) {
      break;
    }
    //TODO Because it's possible to break the loop
    //     the msgLockElement could be destroyed already
    //     ReactMessage will reset msgLockElement if it doesn't
    //     set ack, but when there's no msg, we can reach this
    //     code
    //     possible solution: check every window using contains()
    if ( msgLockElement->getAllowTimerEventBreak() == true ) {
      if ( msgLockElement->getNrOfLastEventBreak() != lastTimerEventNr ) {
	msgLockElement->setNrOfLastEventBreak( lastTimerEventNr );
	break;
      }
    }

    gettimeofday( &curTime, NULL );

    // now wait some time
#ifndef USE_AGUIXTIMER
    //TODO das koennte ein Precision-Problem sein, wenn zu lange im Timer-Mode
    //     verblieben wird
    t1 = ( ( lastTimerEventNr + 1 ) * 1000 / TIMER_TICKS ) - ldiffgtod_m( &curTime, &timerStart );
    if ( ( tmsg == NULL ) && ( t1 > 1 ) ) waittime( 5 );
#else
    if ( tmsg == NULL ) waittime( 5 );
#endif

#ifndef USE_AGUIXTIMER
    gettimeofday( &curTime, NULL );
    timerEventNr = ( ldiffgtod_m( &curTime, &timerStart ) * TIMER_TICKS ) / 1000;
#else
    timerEventNr = timerEvent;
#endif

    if ( timerEventNr != lastTimerEventNr ) {
      // send timer event msg
      tmsg = AGUIX_allocMessage();
      tmsg->type = ClientMessage;
      tmsg->specialType = Message::TIMEREVENT;
      tmsg->time = timerEventNr;
      tmsg->window = None;
      
      tmsg->lockElement = msgLockElement;
      tmsg->ack = false;
      tmsg->loop = 0;
      
      ReactMessage( tmsg, parent );
      AGUIX_freeMessage( tmsg );
      lastTimerEventNr = timerEventNr;
    }

    // test again for timer since the timer message could
    // have disabled the timer
    if ( ! ( ( msgLockElement != NULL ) &&
	     ( timerEnabled == true ) ) ) {
        break;
    }
  }

  gettimeofday( &curTime, NULL );

  msg_handler_active = false;
  
  if ( m_bubble_help.help_candidate &&
       ldiffgtod_m( &curTime, &m_bubble_help.enter_time ) >= BUBBLE_HELP_OPEN_TIME_MS ) {
      m_bubble_help.help_candidate->openBubbleHelp();
      clearBubbleHelpCandidate();
  }

  while ( ! m_post_msg_handler_cbs.empty() ) {
      // get a copy of all post actions so the callback can insert new
      // actions. Also clear it so if msgHandler is called inside the
      // callback, it will not re-execute the functions again.
      auto cbs = m_post_msg_handler_cbs;
      m_post_msg_handler_cbs.clear();

      for ( auto &f : cbs ) {
          f();
      }
  }
  
  return XEventsQueued( dsp, QueuedAfterFlush );
}

AGMessage *AGUIX::GetMessage(AWindow *parent)
{
  msgHandler( MES_GET, parent, false );
  return getAGMsg();
}

AGMessage *AGUIX::WaitMessage(AWindow *parent)
{
    while ( messages.empty() == true ) {
        int mode = MES_WAIT;

        // this code is to simulate external timeouts for bubblehelp handling
        if ( m_bubble_help.help_candidate ) {
            if ( ! waitForEventOnFD( BUBBLE_HELP_TIMEOUT_MS ) ) {
                // nothing on the FD so call handler in get mode to
                // not block

                external_timeout_callback();
                
                mode = MES_GET;
            }
        }
        msgHandler( mode, parent, false );
    }
    return getAGMsg();
}

void AGUIX::copyArea(Drawable source,Drawable dest,int s_x,int s_y,int width,int height,int d_x,int d_y)
{
  if ( ( width < 1 ) || ( height < 1 ) ) return;  // do nothing when no valid width/height
  XCopyArea(dsp,source,dest,gc,s_x,s_y,width,height,d_x,d_y);
}

void AGUIX::DrawLine(Drawable buffer,int px1,int py1,int px2,int py2)
{
  XDrawLine(dsp,buffer,gc,px1,py1,px2,py2);
}

void AGUIX::DrawLine(Drawable buffer,GC tgc,int px1,int py1,int px2,int py2)
{
  if(tgc==0) DrawLine(buffer,px1,py1,px2,py2);
  else XDrawLine(dsp,buffer,tgc,px1,py1,px2,py2);
}

void AGUIX::DrawPoint(Drawable buffer,int px,int py)
{
  XDrawPoint(dsp,buffer,gc,px,py);
}

void AGUIX::DrawPoint(Drawable buffer,GC tgc,int px,int py)
{
  if(tgc==0) DrawPoint(buffer,px,py);
  else XDrawPoint(dsp,buffer,tgc,px,py);
}

bool AGUIX::ReactMessage(Message *msg,AWindow *parent)
{
  // als erstes das Fenster ausfindig machen
  // dann ReactMessage des Fenster aufrufen
  bool returnvalue=false;
  if ( ( msg->type == ClientMessage ) &&
       ( msg->specialType == Message::NONE ) ) {
    return returnvalue;
  }
  
  bool popup_opened = ( m_open_popup_counter > 0 );
  
  while ( msg->loop < 2 ) {
      for ( wins_cit_t it1 = wins.begin();
            ( it1 != wins.end() ) && ( returnvalue == false );
            it1++ ) {
          AWindow *win = *it1;

          if ( msg->type == Expose ) {
              returnvalue = win->handleMessage( &LastEvent, msg );
          } else if ( m_open_popup_counter > 0 ) {
              if ( dynamic_cast<PopUpWindow*>( win ) != NULL ) {
                  returnvalue = win->handleMessage( &LastEvent, msg );
              }
          } else if ( parent == NULL || parent->isParent( win->getWindow(), false ) == true ) {
              returnvalue = win->handleMessage( &LastEvent, msg );
          } else if ( m_bubble_help.bubble_help_window && m_bubble_help.bubble_help_window->isVisible() ) {
              // special handling for bubblehelp window
              // let it handle the message if the bubblehelp window is
              // the parent of the message window. Let also the current AWindow handle
              // the message since AWindow won't forward the message to its subwindows
              if ( win == m_bubble_help.bubble_help_window.get() ||
                   m_bubble_help.bubble_help_window->isParent( win->getWindow(), false ) ) {
                  returnvalue = win->handleMessage( &LastEvent, msg );
              }
          }
          // with lockElement don't quit this loop
          if ( msg->lockElement != NULL ) returnvalue = false;
      }

      if ( msg->loop == 0 && msg->type == LeaveNotify ) {
          if ( m_bubble_help.bubble_help_window ) {
              if ( m_bubble_help.bubble_help_window->ownedByLinkedWidget( msg->window ) ||
                   m_bubble_help.bubble_help_window->isParent( msg->window, false ) ) {

                  // execute at end of event handler
                  pushPostMsgHandlerCB( [this, w = m_bubble_help.bubble_help_window]() {
                      // we have an own reference so it get destroyed
                      // when leaving this function
                      w->hide();
                  });

                  // reset shared_ptr, but reference still lives in
                  // msg handler
                  m_bubble_help.bubble_help_window.reset();
              }
          }
          clearBubbleHelpCandidate();
      }

    if ( msg->lockElement != NULL ) {
      // there is a lock element
      // check whether the element accepted this msg
      // ack should be false only when the element doesn't exists anymore
      if ( msg->ack == true ) {
	// okay, element accepted msg
	break;
      } else {
	// lost element
	msgLockElement = NULL;
	msg->lockElement = NULL;
	fprintf( stderr, "Worker: msg lock element lost!\n" );
	// no repeat
      }
    } else {
      // no lock element => just quit loop
      break;
    }
    msg->loop++;
  }
  
  if ( popup_opened == true ) {
      // if there was (or is) a popup opened always
      // return true so we don't create AGMessages
      // (especially for key events)
      return true;
  }
  return returnvalue;
}

void AGUIX::Flush()
{
  XFlush(dsp);
}

int AGUIX::getargc() const
{
  return m_argc;
}

char **AGUIX::getargv() const
{
  return m_argv;
}

void AGUIX::putAGMsg(AGMessage *msg)
{
    bool found = false;
    if ( msg->type == AG_SIZECHANGED ) {
        for ( auto &m : messages ) {
            if ( m->type == AG_SIZECHANGED &&
                 m->size.window == msg->size.window ) {
                m->size.neww = msg->size.neww;
                m->size.newh = msg->size.newh;
                found = true;
                break;
            }
        }
    }

    if ( found ) {
        ReplyMessage( msg );
    } else {
        messages.push_back( msg );
    }
}

AGMessage *AGUIX::getAGMsg()
{
    if ( messages.empty() == true )
        return NULL;
    
    AGMessage *msg = messages.front();
    messages.pop_front();
    return msg;
}

void AGUIX::ReplyMessage(AGMessage *msg)
{
  AGUIX_freeAGMessage( msg );
}

void AGUIX::ClearWin(Window win)
{
  XClearWindow(dsp,win);
}

void AGUIX::SetWindowBG( Window win, const AGUIXColor &color )
{
    XSetWindowBackground( dsp, win, getPixel( color ) );
}

void AGUIX::WindowtoBack(Window win)
{
  XLowerWindow(dsp,win);
}

void AGUIX::WindowtoFront(Window win)
{
  XRaiseWindow(dsp,win);
}

int AGUIX::changeColor( const AGUIXColor &index2, int red, int green, int blue )
{
#ifdef DEBUG
    if ( index2.getColorType() == AGUIXColor::SYSTEM_COLOR ) {
        printf( "changeColor: system %d/%d\n", index2.getColor(), m_system_colors );
    } else if ( index2.getColorType() == AGUIXColor::EXTRA_COLOR ) {
        printf( "changeColor: extra %d/%d\n", index2.getColor(), m_extra_colors );
    } else {
        printf( "changeColor: user %d/%d\n", index2.getColor(), m_user_colors );
    }
#endif
    if ( index2.getColorType() == AGUIXColor::SYSTEM_COLOR ) {
        if ( index2.getColor() < 0 || index2.getColor() >= m_system_colors ) return 1;
    } else if ( index2.getColorType() == AGUIXColor::EXTRA_COLOR ) {
        if ( index2.getColor() < 0 || index2.getColor() >= m_extra_colors ) return 1;
    } else {
        if ( index2.getColor() < 0 || index2.getColor() >= m_user_colors ) return 1;
    }

#ifdef HAVE_XFT
    XftColor *buf_entry = NULL;

    if ( index2.getColorType() == AGUIXColor::SYSTEM_COLOR ) {
        buf_entry = &m_system_col_buf[index2.getColor()];
    } else if ( index2.getColorType() == AGUIXColor::EXTRA_COLOR ) {
        buf_entry = &m_extra_col_buf[index2.getColor()];
    } else {
        buf_entry = &m_user_col_buf[index2.getColor()];
    }
    XftColorFree( dsp,
                  DefaultVisual( dsp, scr ),
                  cmap,
                  buf_entry );
    
    XRenderColor col;
    col.red = red * 256;
    col.green = green * 256;
    col.blue = blue * 256;
    col.alpha = 0xffff;
    if ( ! XftColorAllocValue ( dsp,
                                DefaultVisual( dsp, scr ),
                                cmap,
                                &col,
                                buf_entry ) ) {
        return -1;
    }
#else
    XColor col;
    unsigned long *buf_entry = NULL;

    if ( index2.getColorType() == AGUIXColor::SYSTEM_COLOR ) {
        buf_entry = &m_system_col_buf[index2.getColor()];
    } else if ( index2.getColorType() == AGUIXColor::EXTRA_COLOR ) {
        buf_entry = &m_extra_col_buf[index2.getColor()];
    } else {
        buf_entry = &m_user_col_buf[index2.getColor()];
    }

    XFreeColors( dsp, cmap, buf_entry, 1, 0 );
    col.flags = DoRed | DoGreen | DoBlue;
    col.red = red * 256;
    col.green = green * 256;
    col.blue = blue * 256;
    if ( !XAllocColor( dsp, cmap, &col ) ) return -1;
    *buf_entry = col.pixel;
#endif
    
    m_color_values[index2] = col_values_t( red, green, blue );

    if ( index2.getColorType() == AGUIXColor::USER_COLOR ) {
        m_user_color_instance++;
        updateSystemColors( index2.getColor() );
    }
    return 0;
}

AGUIXFont *AGUIX::getFont( const char *name )
{
  AGUIXFont *tf = NULL;

  for ( aguixfont_list_cit_t it1 = fonts.begin();
        it1 != fonts.end();
        it1++ ) {
      tf = *it1;
      if ( strcmp( tf->getName(), name ) == 0 ) break;
      tf = NULL;
  }

  if ( tf == NULL ) {
    tf = new AGUIXFont( this );
    if ( tf->setFont( name ) != 0 ) {
      delete tf;
      return NULL;
    }
    fonts.push_back( tf );
  }
  return tf;
}

void AGUIX::ExposeHandler(Message *msg)
{
  if ( msg->window != 0 ) {
      for ( wins_cit_t it1 = wins.begin();
            it1 != wins.end();
            it1++ ) {
          AWindow *win = *it1;
          if ( msg->type == Expose ) {
              win->handleMessage( &LastEvent, msg );
          }
      }
  }
}

AWindow *AGUIX::findAWindow(Window win, bool top_level)
{
  AWindow *awin = NULL;

  for ( wins_cit_t it1 = wins.begin();
        it1 != wins.end();
        it1++ ) {
      awin = *it1;

      if ( top_level && ! awin->isTopLevel() ) continue;
      
      if ( awin->isParent( win, top_level ? false : true ) == true ) break;
      awin = NULL;
  }
  
  return awin;
}

char *AGUIX::getNameOfKey(KeySym key,unsigned int mod) const
{
  char *tstr,*tstr2;
  tstr2=XKeysymToString(key); // Nicht freilassen
  if(tstr2==NULL) return NULL;
  tstr=dupstring(tstr2);
  if((mod&Mod1Mask)==Mod1Mask) {
    tstr2=(char*)_allocsafe(11+strlen(tstr)+2);
    sprintf(tstr2,"Left Alt + %s",tstr);
    _freesafe(tstr);
    tstr=tstr2;
  }
  if((mod&ControlMask)==ControlMask) {
    tstr2=(char*)_allocsafe(11+strlen(tstr)+2);
    sprintf(tstr2,"Control + %s",tstr);
    _freesafe(tstr);
    tstr=tstr2;
  }
  if((mod&ShiftMask)==ShiftMask) {
    tstr2=(char*)_allocsafe(11+strlen(tstr)+2);
    sprintf(tstr2,"Shift + %s",tstr);
    _freesafe(tstr);
    tstr=tstr2;
  }
  return tstr;
}

Window AGUIX::getGroupWin() const
{
  return groupwin;
}

void AGUIX::createGroupWin()
{
  XTextProperty windowname,iconname;
  Visual *vis=DefaultVisual(dsp,scr);
  unsigned long mask=CWEventMask;
  XSetWindowAttributes attr;
  char *tstr;
  attr.event_mask=0;
  groupwin=XCreateWindow(dsp,RootWindow(dsp,scr),0,0,10,10,0,0,InputOnly,vis,mask,&attr);
  if(!groupwin) {
    return;
  }
  XClassHint classhint;
  tstr = dupstring( getClassname().c_str() );
  classhint.res_name=tstr;
  classhint.res_class=tstr;
  XSetClassHint(dsp,groupwin,&classhint);
  XSetWMProtocols(dsp,groupwin,getCloseAtom(),1);
  XSizeHints *SizeHints;
  XWMHints *WMHints;

  SizeHints=XAllocSizeHints();

  if ( SizeHints ) {
      SizeHints->flags=PSize;
      SizeHints->width=10;
      SizeHints->height=10;

      XSetWMNormalHints(dsp,groupwin,SizeHints);

      XFree(SizeHints);
  }

  WMHints=XAllocWMHints();

  if ( WMHints ) {
      WMHints->input=False;
      WMHints->window_group=groupwin;
      WMHints->flags=InputHint|WindowGroupHint;

      XSetWMHints(dsp,groupwin,WMHints);

      XFree(WMHints);
  }

  if ( XStringListToTextProperty(&tstr,1,&windowname) != 0 ) {
      XSetWMName(dsp,groupwin,&windowname);
      XFree( windowname.value );
  }

  if ( XStringListToTextProperty(&tstr,1,&iconname) != 0 ) {
      XSetWMIconName(dsp,groupwin,&iconname);
      XFree( iconname.value );
  }
  XStoreName(dsp,groupwin,tstr);
  _freesafe(tstr);
  XSetCommand(dsp,groupwin,getargv(),getargc());
}

void AGUIX::destroyGroupWin()
{
  XDestroyWindow(dsp,groupwin);
  groupwin=0;
}

int
AGUIX::queryPointer(Window win,int *x,int *y)
{
  Window usewin,root,child;
  int root_x,root_y;
  unsigned int keys_buttons;

  if ( ( x == NULL ) || ( y == NULL ) ) return -1;

  if(win==0) usewin=DefaultRootWindow(dsp);
  else usewin=win;
  XQueryPointer(dsp,usewin,&root,&child,&root_x,&root_y,x,y,&keys_buttons);
  return 0;
}

int
AGUIX::queryPointer(Window win,int *x,int *y,unsigned int *buttons)
{
  Window usewin,root,child;
  int root_x,root_y;

  if ( ( x == NULL ) || ( y == NULL ) ) return -1;

  if(win==0) usewin=DefaultRootWindow(dsp);
  else usewin=win;
  XQueryPointer(dsp,usewin,&root,&child,&root_x,&root_y,x,y,buttons);
  return 0;
}

int
AGUIX::queryRootPointer(int *x,int *y)
{
  Window usewin,root,child;
  int root_x,root_y;
  unsigned int keys_buttons;

  if ( ( x == NULL ) || ( y == NULL ) ) return -1;

  usewin=DefaultRootWindow(dsp);
  XQueryPointer(dsp,usewin,&root,&child,&root_x,&root_y,x,y,&keys_buttons);
  return 0;
}

int
AGUIX::setFont( const char *newfont )
{
  AGUIXFont *afont=getFont(newfont);
  if(afont==NULL) return 1;
  mainfont=afont;
  CharHeight=mainfont->getCharHeight();
  return 0;
}

void
AGUIX::changeColormap()
{
  Colormap newcmap;
  if(privatecmap==false) {
#ifdef DEBUG
    printf("go to private cmap\n");
#endif
    newcmap=XCopyColormapAndFree(dsp,cmap);
    cmap=newcmap;
    privatecmap=true;

    for ( wins_cit_t it1 = wins.begin();
          it1 != wins.end();
          it1++ ) {
        AWindow *win = *it1;
        XSetWindowColormap( dsp, win->getWindow(), cmap );
    }
  }
}

Colormap
AGUIX::getColormap() const
{
  return cmap;
}

void AGUIX::setCursor(Window win,int type)
{
  if((type>=WAIT_CURSOR)&&(type<MAXCURSORS)) {
    XDefineCursor(dsp,win,cursors[type]);
  }
}

void AGUIX::unsetCursor(Window win)
{
  XUndefineCursor(dsp,win);
}

int AGUIX::startCut( GUIElement *elem,const char *buffer, bool use_clipboard )
{
    copy_and_paste_state &state = use_clipboard ? m_clipboard_state : m_primary_buffer_state;

    XSetSelectionOwner( dsp,
                        use_clipboard ? XA_CLIPBOARD : XA_PRIMARY,
                        getGroupWin(),
                        CurrentTime );
    if ( getGroupWin() == XGetSelectionOwner( dsp,
                                              use_clipboard ? XA_CLIPBOARD : XA_PRIMARY ) ) {
        state.cutstart = elem;
        state.buffer_string = buffer;
        state.file_list.clear();
        state.target = None;
    } else {
        cancelCut( use_clipboard );
        return 1;
    }
    return 0;
}

void AGUIX::cancelCut( bool use_clipboard )
{
    copy_and_paste_state &state = use_clipboard ? m_clipboard_state : m_primary_buffer_state;

    if ( state.cutstart != NULL ) {
        state.cutstart->cancelcut();
    }
    state.cutstart = NULL;
    state.last_selection_request_atom = None;
    state.last_selection_request_window = 0;
}

bool AGUIX::amiOwner( bool use_clipboard ) const
{
    const copy_and_paste_state &state = use_clipboard ? m_clipboard_state : m_primary_buffer_state;

    if ( state.cutstart != NULL ) return true;

    return false;
}

std::string AGUIX::getCutBuffer( bool use_clipboard ) const
{
    if ( use_clipboard ) {
        return m_clipboard_state.buffer_string;
    } else {
        return m_primary_buffer_state.buffer_string;
    }
}

void AGUIX::requestCut( Window win,
                        bool fallback,
                        bool use_clipboard,
                        GUIElement *elem,
                        void *agmessage_data,
                        clipboard_request_e request_type )
{
    copy_and_paste_state &state = use_clipboard ? m_clipboard_state : m_primary_buffer_state;

    Atom myproperty = XInternAtom( dsp, "Worker Paste Property", False );

    Atom target_atom;

    if ( request_type == CLIPBOARD_REQUEST_FILE_LIST ) {
        if ( fallback ) return; // we can't fallback to another type
        target_atom = XA_SPECIAL_COPIED_FILES;
    } else {
        target_atom = XA_STRING;
#ifndef DISABLE_UTF8_SUPPORT
        if ( ! fallback ) {
            if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED && UTF8::getCurrentEncoding() == UTF8::ENCODING_UTF8 ) {
                target_atom = XA_UTF8_STRING;
            }
        }
#endif
    }

    // override win with group window so the clipboard content remains
    // valid even when closing the original window
    win = getGroupWin();

    XConvertSelection( dsp,
                       use_clipboard ? XA_CLIPBOARD : XA_PRIMARY,
                       target_atom,
                       myproperty,
                       win,
                       CurrentTime);

    state.last_selection_request_atom = target_atom;
    state.last_selection_request_window = win;

    state.paste_elem = elem;
    state.paste_agmessage_data = agmessage_data;
}

void AGUIX::DrawTriangleFilled(Drawable buffer, int px1, int py1, int px2, int py2, int px3, int py3)
{
  DrawTriangleFilled( buffer, 0, px1, py1, px2, py2, px3, py3 );
}

void AGUIX::DrawTriangleFilled(Drawable buffer, GC tgc, int px1, int py1, int px2, int py2, int px3, int py3)
{
  GC usegc;
  XPoint points[4];

  if( tgc == 0 )
    usegc = gc;
  else
    usegc = tgc;

  points[0].x = px1;
  points[0].y = py1;
  points[1].x = px2;
  points[1].y = py2;
  points[2].x = px3;
  points[2].y = py3;
  points[3].x = px1;
  points[3].y = py1;

  XFillPolygon( dsp, buffer, usegc, points, 4, Convex, CoordModeOrigin );
  XDrawLines( dsp, buffer, usegc, points ,4, CoordModeOrigin );
}

void AGUIX::cancelCutPaste( GUIElement *elem, bool use_clipboard )
{
    copy_and_paste_state &state = use_clipboard ? m_clipboard_state : m_primary_buffer_state;

    if ( state.cutstart == elem ) {
        cancelCut( use_clipboard );
    }
    if ( state.paste_elem == elem ) {
        state.paste_elem = NULL;
    }
}

bool AGUIX::isDoubleClick(struct timeval *t1,struct timeval *t2) const
{
  int dt=0;
  int s,us;

  s = abs( (int)( t1->tv_sec - t2->tv_sec ) );
  if ( s > 2 ) return false;
  us=t1->tv_usec-t2->tv_usec;
  if(us<0) {
    us+=1000000;
    s--;
  }
  dt=us+s*1000000;
  if(dt<350000) return true;
  return false;
}

bool AGUIX::isDoubleClick( Time t1, Time t2) const
{
  if ( abs( (int)( t2 - t1 ) ) < 350 ) return true;
  return false;
}

#define ADJUST_SPACE()				\
{						\
  w1 = 0;					\
  for ( i = 0; i < nr; i++ ) {			\
    w1 += elems[i]->getWidth();			\
  }						\
  						\
  sp = wantedWidth - w1 - borderwidth * 2;	\
  						\
  last = borderwidth;				\
  for ( i = 0; i < nr; i++ ) {			\
    widths[i] = elems[i]->getWidth();		\
    xpos[i] = last;				\
    if ( i < ( nr - 1 ) )			\
      tsp = sp / ( nr - 1 - i );		\
    else					\
      tsp = 0;					\
    sp -= tsp;					\
    last += widths[i] + tsp;			\
  }						\
}

#define ARRANGE_MINSPACE()			\
{						\
  last = borderwidth;				\
  for ( i = 0; i < nr; i++ ) {			\
    widths[i] = elems[i]->getWidth();		\
    xpos[i] = last;				\
    last += widths[i];				\
    if ( minSpace > 0 ) last += minSpace;	\
  }						\
}

/*
 * resize and pos all elems to fit given width
 *
 * args:
 *   wantedWidth: width for resize or <1 for any
 *   borderwidth: left and right border
 *                <0 means 0
 *   minSpace: minimal space between elems
 *             <0 means any
 *   maxSpace: maximal space between elems
 *             <0 means any
 *   allowShrink: true when shrinking is allowed
 *   allowStretch: true when stretching is allowed
 *   elems: Array of elements
 *   minWidths: minimal widths of the elements
 *   nr: Arraysize
 *
 * returnvalue:
 *   used width
 * used width = wantedWidth is NOT guaranteed
 *
 */
//TODO eigentlich obsolete
int AGUIX::scaleElementsW( int wantedWidth,
			   int borderwidth,
			   int minSpace,
			   int maxSpace,
			   bool allowShrink,
			   bool allowStretch,
			   GUIElement **elems,
			   int *minWidths,
			   int nr )
{
  //  GUIElement **elems;
  int cw, i, last, nw;
  int *widths, *xpos;
  int w1, w2, sp, tsp;
  int usedWidth = -1;

  // for ,... notation
  // nr = 0;
  // elems = &elem;
  // while ( elems[nr] != NULL )
  //   nr++;

  if ( elems == NULL ) return -1;
  if ( nr < 1 ) return -1;
  if ( ( minSpace >= 0 ) && ( maxSpace >= 0 ) && ( minSpace > maxSpace ) ) return -1;

  widths = new int[nr];
  xpos = new int[nr];

  if ( borderwidth < 0 ) borderwidth = 0;

  // calc the currently needed width
  cw = 2 * borderwidth;
  if ( nr > 1 )
    cw += ( nr - 1 ) * ( ( minSpace < 0 ) ? 0 : minSpace );
  
  for ( i = 0; i < nr; i++ ) {
    cw += elems[i]->getWidth();
  }
  
  if ( nr == 1 ) {
    // special case
    // center the only element
    // 2 possibilities (when cw<wantedWidth):
    //   1.:stretch never
    //   2.:stretch when allowStretch == true
    // currently I use the second one
    if ( wantedWidth < 1 ) {
      // align to border
      widths[0] = elems[0]->getWidth();
      xpos[0] = borderwidth;
    } else {
      if ( cw < wantedWidth ) {
	// stretching needed
	if ( allowStretch == false ) {
	  // place it in the center
	  widths[0] = elems[0]->getWidth();
	  xpos[0] = ( wantedWidth / 2 ) - ( widths[0] / 2 );
	  
	  // we cannot calc the used width later so set it here
	  usedWidth = wantedWidth;
	} else {
	  widths[0] = wantedWidth - 2 * borderwidth;
	  if ( widths[0] < 4 ) widths[0] = 4;
	  xpos[0] = borderwidth;
	}
      } else if ( cw > wantedWidth ) {
	// shrinking needed
	if ( allowShrink == false ) {
	  // not allowed
	  widths[0] = elems[0]->getWidth();
	  xpos[0] = borderwidth;
	} else {
	  widths[0] = wantedWidth - 2 * borderwidth;
	  if ( widths[0] < 4 ) widths[0] = 4;
	  if ( minWidths != NULL ) {
	    if ( widths[0] < minWidths[0] )
	      widths[0] = minWidths[0];
	  }
	  xpos[0] = borderwidth;
	}
      } else {
	// no changes needed
	widths[0] = elems[0]->getWidth();
	xpos[0] = borderwidth;
      }
    }
  } else {
    if ( wantedWidth < 1 ) {
      // means any width so just arrange them with minSpace
      ARRANGE_MINSPACE();
    } else {
      // try to get the elems to fit wantedWidth
      // prio: 1.change space between elems
      //       2.always stretch or shrink, not both
      
      if ( cw == wantedWidth ) {
	// nothing to do
	last = borderwidth;
	for ( i = 0; i < nr; i++ ) {
	  widths[i] = elems[i]->getWidth();
	  xpos[i] = last;
	  last += widths[i];
	  if ( minSpace > 0 ) last += minSpace;
	}
      } else if ( cw < wantedWidth ) {
	// stretching needed
	if ( maxSpace < 0 ) {
	  // no maxSpace set so just adjust space
	  ADJUST_SPACE();
	} else {
	  // first test if we need to stretch when using maxSpace
	  
	  w1 = 2 * borderwidth;
	  if ( nr > 1 )
	    w1 += ( nr - 1 ) * maxSpace;
	  
	  for ( i = 0; i < nr; i++ ) {
	    w1 += elems[i]->getWidth();
	  }
	  // w1 is now the width when using maxSpace
	  
	  if ( w1 > wantedWidth ) {
	    // we can adjust the space
	    ADJUST_SPACE();
	  } else {
	    // maxSpace isn't enough
	    // we have to stretch some elems
	    if ( allowStretch == false ) {
	      // not allowed
	      // we cannot give wantedWidth so use maxSpace
	      last = borderwidth;
	      for ( i = 0; i < nr; i++ ) {
		widths[i] = elems[i]->getWidth();
		xpos[i] = last;
		last += widths[i] + maxSpace;
	      }
	    } else {
	      bool *finished, change;
	      int tnr;

              finished = new bool[nr];
	      w1 = wantedWidth - ( nr - 1 ) * maxSpace;
	      w1 -= 2 * borderwidth;
	      tnr = nr;

	      for ( i = 0; i < nr; i++ ) {
		widths[i] = elems[i]->getWidth();
		finished[i] = false;
	      }
	      do {
		w2 = w1 / tnr;
		change = false;
		for ( i = 0; i < nr; i++ ) {
		  if ( finished[i] == false ) {
		    if ( widths[i] > w2 ) {
		      finished[i] = true;
		      w1 -= widths[i];
		      tnr--;
		      change = true;
		    }
		  }
		}
	      } while ( change == true );
	      for ( i = 0; i < nr; i++ ) {
		if ( finished[i] == false ) {
		  w2 = w1 / tnr;
		  widths[i] = w2;
		  w1 -= w2;
		  tnr--;
		}
	      }
	      // now calc the xpos
	      last = borderwidth;
	      for ( i = 0; i < nr; i++ ) {
		xpos[i] = last;
		last += widths[i] + maxSpace;
	      }
              delete [] finished;
	    }
	  }
	}
      } else {
	// shrinking needed
	// works different from stretch-case
	// because we calced the width need when using minSpace ( or 0 when <0)
	// so in any case we have to shrink the elems
	// maxSpace doesn't count at all
	if ( allowShrink == false ) {
	  // nothing we can do to fit wantedWidth
	  // just arrange them with minSpace
	  ARRANGE_MINSPACE();
	} else {
	  // again only shrink elems which are not smaller then the needed average

	  bool *finished, change;
	  int tnr;
          
          finished = new bool[nr];
	  w1 = wantedWidth - ( nr - 1 ) * ( minSpace < 0 ? 0 : minSpace );
	  w1 -= 2 * borderwidth;
	  
	  tnr = nr;
	  
	  for ( i = 0; i < nr; i++ ) {
	    widths[i] = elems[i]->getWidth();
	    finished[i] = false;
	  }

	  do {
	    w2 = w1 / tnr;
	    change = false;
	    for ( i = 0; i < nr; i++ ) {
	      if ( finished[i] == false ) {
		if ( widths[i] < w2 ) {
		  finished[i] = true;
		  w1 -= widths[i];
		  tnr--;
		  change = true;
		}
	      }
	    }
	  } while ( change == true );
	  for ( i = 0; i < nr; i++ ) {
	    if ( finished[i] == false ) {
	      w2 = w1 / tnr;
	      widths[i] = w2;
	      w1 -= w2;
	      tnr--;
	    }
	  }

	  if ( minWidths != NULL ) {
	    // now check for minWidths
	    // this algo is stupid, I know, but because we only have few elements
	    // I think it's okay

	    bool *finished2;
	    int *mws;
	    int nf, dis, tnf, tdis, t;
	    
            finished2 = new bool[nr];
            mws = new int[nr];
	    for ( i = 0; i < nr; i++ ) {
	      mws[i] = minWidths[i];
	      if ( mws[i] < 4 )
		mws[i] = 4;
	      finished2[i] = false;
	    }
	    
	    nf = nr;
	    do {
	      dis = 0;
	      for ( i = 0; i < nr; i++ ) {
		if ( widths[i] < mws[i] ) {
		  dis += mws[i] - widths[i];
		  widths[i] = mws[i];
		  finished2[i] = true;
		  nf--;
		}
	      }
	      if ( dis > 0 ) {
		// now distribute dis at all elements finished == false
		tdis = dis;
		tnf = nf;
		for ( i = 0; i < nr; i++ ) {
		  if ( finished2[i] == false ) {
		    t = tdis / tnf;
		    widths[i] -= t;
		    tnf--;
		    tdis -= t;
		  }
		}
		// now dis is distributed to all widths
		// but this means that some new elements could be too small
	      }
	    } while ( dis > 0 );
	    // dis will only be > 0 is there are too small elements
	    // because in each loop atleast one will be corrected this will terminated after n loops
            delete [] finished2;
            delete [] mws;
	  }

	  // now calc the xpos
	  last = borderwidth;
	  for ( i = 0; i < nr; i++ ) {
	    xpos[i] = last;
	    last += widths[i];
	    if ( minSpace > 0 ) last += minSpace;
	  }
          delete [] finished;
	}
      }
    } 
  }
  
  // now arrange the elements
  for ( i = 0; i < nr; i++ ) {
    elems[i]->move( xpos[i], elems[i]->getY() );
    elems[i]->resize( widths[i], elems[i]->getHeight() );
  }
  
  delete [] widths;
  delete [] xpos;

  if ( usedWidth >= 0 ) {
    return usedWidth;
  } else {
    nw = elems[nr - 1]->getX() + elems[nr - 1]->getWidth() + borderwidth;
    return nw;
  }
}

#undef ADJUST_SPACE
#undef ARRANGE_MINSPACE

int AGUIX::centerElementsY( GUIElement *element,
			    GUIElement *center2element )
{
  int h1,h2;

  if ( ( element == NULL ) || ( center2element == NULL ) )
    return -1;

  h1 = element->getHeight();
  h2 = center2element->getHeight();

  element->move( element->getX(),
		 center2element->getY() +
		 h2 / 2 -
		 h1 / 2 );
  return 0;
}

int AGUIX::getRootWindowWidth() const
{
  return rootWindowWidth;
}

int AGUIX::getRootWindowHeight() const
{
  return rootWindowHeight;
}

Pixmap AGUIX::createPixmap( Drawable d, int width, int height )
{
  if ( ( width < 1 ) || ( height < 1 ) || ( d == 0 ) ) return 0;
  
  return XCreatePixmap( dsp, d, width, height, DefaultDepth( dsp, scr ) );
}

void AGUIX::freePixmap( Pixmap p )
{
  if ( p == 0 ) return;
  XFreePixmap ( dsp, p );
}

void AGUIX::DrawDottedLine( Drawable buffer, int px1, int py1, int px2, int py2 )
{
  XDrawLine( dsp, buffer, dotted_gc, px1, py1, px2, py2 );
}

void AGUIX::DrawDottedRectangle( Drawable buffer, int x, int y, int w, int h )
{
  if ( ( w < 1 ) || ( h < 1 ) ) return;
  DrawDottedLine( buffer, x, y, x + w - 1, y );
  DrawDottedLine( buffer, x + w - 1, y, x + w - 1, y + h - 1 );
  DrawDottedLine( buffer, x + w - 1, y + h - 1, x, y + h - 1 );
  DrawDottedLine( buffer, x, y + h - 1, x, y );
}

void AGUIX::setDottedFG( const AGUIXColor &color )
{
  setFG( dotted_gc, color );
}

void AGUIX::DrawDashXorLine( Drawable buffer, int px1, int py1, int px2, int py2 )
{
  XDrawLine( dsp, buffer, dashxor_gc, px1, py1, px2, py2 );
}

void AGUIX::DrawDashXorRectangle( Drawable buffer, int x, int y, int w, int h )
{
  if ( ( w < 1 ) || ( h < 1 ) ) return;
  DrawDashXorLine( buffer, x, y, x + w - 1, y );
  DrawDashXorLine( buffer, x + w - 1, y, x + w - 1, y + h - 1 );
  DrawDashXorLine( buffer, x + w - 1, y + h - 1, x, y + h - 1 );
  DrawDashXorLine( buffer, x, y + h - 1, x, y );
}

void AGUIX::DrawDashDLine( Drawable buffer, int px1, int py1, int px2, int py2 )
{
  XDrawLine( dsp, buffer, dashdouble_gc, px1, py1, px2, py2 );
}

void AGUIX::DrawDashDRectangle( Drawable buffer, int x, int y, int w, int h )
{
  if ( ( w < 1 ) || ( h < 1 ) ) return;
  DrawDashDLine( buffer, x, y, x + w - 1, y );
  DrawDashDLine( buffer, x + w - 1, y, x + w - 1, y + h - 1 );
  DrawDashDLine( buffer, x + w - 1, y + h - 1, x, y + h - 1 );
  DrawDashDLine( buffer, x, y + h - 1, x, y );
}

void AGUIX::setDashDFG( const AGUIXColor &color )
{
  setFG( dashdouble_gc, color );
}

void AGUIX::setDashDBG( const AGUIXColor &color )
{
  setBG( dashdouble_gc, color );
}

bool AGUIX::isModifier( KeySym key )
{
  if ( IsModifierKey( key ) == true ) return true;

  // some additional modifiers not in IsModifierKey
  switch ( key ) {
  case XK_Multi_key:
    return true;
    break;
  }
  return false;
}

char *AGUIX::getStringForKeySym( KeySym key )
{
  char *tstr;

  tstr = XKeysymToString( key );
  if ( tstr != NULL ) {
    return dupstring( tstr );
  }
  return NULL;
}

KeySym AGUIX::getKeySymForString( const char *str1 )
{
  if ( str1 == NULL ) return NoSymbol;

  return XStringToKeysym( str1 );
}

void AGUIX::rebuildBackgroundPixmap()
{
  if ( backpm != None ) XFreePixmap( dsp, backpm );
  backpm = XCreatePixmap( dsp, DefaultRootWindow( dsp ), 2, 2, getDepth() );
  setFG( getFaceCol_default_bg() );
  FillRectangle( backpm, 0, 0, 2, 2 );
  setFG( getFaceCol_3d_bright() );
  DrawPoint( backpm, 0, 1 );
  DrawPoint( backpm, 1, 0 );
}

void AGUIX::setWindowBackgroundPixmap( Window win )
{
  if ( backpm == None ) {
    rebuildBackgroundPixmap();
  }
  XSetWindowBackgroundPixmap( dsp, win, backpm );
}

void AGUIX::doXMsgs( AWindow *parent, bool onlyexpose )
{
  doXMsgs( MES_GET, parent, onlyexpose );
}

void AGUIX::doXMsgs( int mode, AWindow *parent, bool onlyexpose )
{
  if ( ( mode != MES_GET ) && ( mode != MES_WAIT ) ) mode = MES_GET;
  while ( msgHandler( mode, parent, onlyexpose ) > 0 );
}

bool AGUIX::noMoreMessages() const
{
    if ( messages.empty() == true ) return true;
    return false;
}

#ifdef USE_XIM
XIMStyle AGUIX::getXIMStyle() const
{
  return im_style;
}

XIM AGUIX::getXIM() const
{
  return inputmethod;
}

int AGUIX::openIM()
{
  XIMStyle supported_styles, style;
  XIMStyles *im_supported_styles;
  int i;
  
  inputmethod = XOpenIM( dsp, NULL, NULL, NULL );
  if ( inputmethod == NULL ) {
    fprintf( stderr, "Worker Warning: Cannot open input method\n" );
#ifdef XIM_XREGISTER_OKAY
    XRegisterIMInstantiateCallback( dsp, NULL, NULL, NULL, AGUIX_im_inst_callback, (XPointer)this );
#endif
  } else {
#ifdef XIM_XREGISTER_OKAY
    XIMCallback destCB;

    destCB.callback = AGUIX_im_dest_callback;
    destCB.client_data = (XPointer)this;
    if ( XSetIMValues( inputmethod, XNDestroyCallback, &destCB, (void*)NULL ) != NULL ) {
      fprintf( stderr, "Worker Warning: Couldn't set IM destroy callback\n" );
    }
#endif
    
    /* I could also "support" XIM...None but with these styles dead keys
       will be completly ignored (at least at my machine)
       But my code will fallback to normal XLookupString when my wanted styles
       are not supported and dead keys will be loockuped to their normal character
    */
    supported_styles = XIMPreeditNothing | XIMStatusNothing;

    im_style = 0;

    if ( XGetIMValues( inputmethod, XNQueryInputStyle, &im_supported_styles, (void*)NULL ) == NULL ) {
        for ( i = 0; i < im_supported_styles->count_styles; i++ ) {
            style = im_supported_styles->supported_styles[i];
            if ( ( style & supported_styles ) == style ) {
                im_style = ChooseBetterStyle( style, im_style );
            }
        }
        XFree( im_supported_styles );
    }
    
    if ( im_style == 0 ) {
#ifdef DEBUG
      fprintf( stderr, "Worker Warning: Wanted inputstyle not available\n");
#endif
    } else {
        for ( wins_cit_t it1 = wins.begin();
              it1 != wins.end();
              it1++ ) {
            (*it1)->createXIC();
        }
    }
  }
  return ( ( ( inputmethod != NULL ) && ( im_style != 0 ) ) ? 0 : 1 );
}

void AGUIX::closeIM()
{
  for ( wins_cit_t it1 = wins.begin();
        it1 != wins.end();
        it1++ ) {
      (*it1)->closeXIC();
  }
      
  im_style = 0;
  if ( inputmethod != NULL ) {
    XCloseIM( inputmethod );
    inputmethod = NULL;
  }
}

void AGUIX::IMInstCallback( Display *calldsp )
{
  if ( calldsp != dsp ) return; // not my display

#ifdef XIM_XREGISTER_OKAY
  XUnregisterIMInstantiateCallback( dsp, NULL, NULL, NULL, AGUIX_im_inst_callback, (XPointer)this );
#endif
  openIM();
}

void AGUIX::IMDestCallback()
{
    for ( wins_cit_t it1 = wins.begin();
          it1 != wins.end();
          it1++ ) {
        (*it1)->XICdestroyed();
    }
    
    im_style = 0;
    inputmethod = NULL;
}

#endif

/*
 * set transient window variable
 *
 * apply only when the window is registered
 * clear variable with NULL arg
 */
void AGUIX::setTransientWindow( AWindow *twin )
{
  if ( twin == NULL ) {
    transientwindow = NULL;
  } else {
      wins_cit_t it1 = std::find( wins.begin(), wins.end(), twin );
      if ( it1 != wins.end() ) {
          // window found
          transientwindow = twin;

          wins_as_transient_for.remove( twin );
          wins_as_transient_for.push_back( twin );
      }
  }
}

const AWindow* AGUIX::getTransientWindow() const
{
  return transientwindow;
}

int AGUIX::addDefaultColors()
{
    if ( AddColor( 180, 180, 180 ).getColor() < 0 ) return -1;
    if ( AddColor( 0, 0, 0 ).getColor() < 0 ) return -1;
    if ( AddColor( 255, 255, 255 ).getColor() < 0 ) return -1;
    if ( AddColor( 0, 85, 187).getColor() < 0 ) return -1;
    if ( AddColor( 204, 34, 0 ).getColor() < 0 ) return -1;
    if ( AddColor( 50, 180, 20 ).getColor() < 0 ) return -1;
    if ( AddColor( 119, 0, 119 ).getColor() < 0 ) return -1;
    if ( AddColor( 238, 170, 68 ).getColor() < 0 ) return -1;
    return 0;
}

/*
 * the following two functions returns the last key/mouse and reset
 * the variables
 * the variables are set by doXMsg currently
 */
KeySym AGUIX::getLastKeyRelease()
{
  KeySym k;

  k = lastkeyrelease;
  lastkeyrelease = None;
  return k;
}

unsigned int AGUIX::getLastMouseRelease()
{
  unsigned int m;

  m = lastmouserelease;
  lastmouserelease = 0;
  return m;
}

void AGUIX::msgLock( Widget *e )
{
  msgLockElement = e;
}

void AGUIX::msgUnlock( const Widget *e )
{
  if ( msgLockElement != e ) {
    fprintf( stderr, "Worker:wrong msgUnlock\n" );
  }
  msgLockElement = NULL;
}

bool AGUIX::msgHoldsLock( const Widget *e )
{
    if ( msgLockElement == e )
        return true;
    return false;
}

void AGUIX::enableTimer()
{
#ifdef USE_AGUIXTIMER
  struct itimerval itv;
#endif

  if ( timerEnabled == false ) {
    timerEnabled = true;
#ifndef USE_AGUIXTIMER
    gettimeofday( &timerStart, NULL );
    lastTimerEventNr = 0;
#else
    itv.it_value.tv_sec = itv.it_interval.tv_sec = 0;
    itv.it_value.tv_usec = itv.it_interval.tv_usec = 1000000 / TIMER_TICKS;
    setitimer( ITIMER_REAL, &itv, NULL );
    lastTimerEventNr = timerEvent;
#endif
  }
}

void AGUIX::disableTimer()
{
#ifdef USE_AGUIXTIMER
  struct itimerval itv;
#endif

  if ( timerEnabled == true ) {
#ifdef USE_AGUIXTIMER
    itv.it_value.tv_sec = itv.it_interval.tv_sec = 0;
    itv.it_value.tv_usec = itv.it_interval.tv_usec = 0;
    setitimer( ITIMER_REAL, &itv, NULL );
#endif
  }
  timerEnabled = false;
}

int AGUIX::getTextWidth( const char *str, AGUIXFont *font )
{
  return getTextWidth( str, font, -1 );
}

int AGUIX::getTextWidth( const char *str, AGUIXFont *font, int len )
{
  AGUIXFont *use_font;

  if ( str == NULL ) return 0;

  if ( font != NULL ) {
    use_font = font;
  } else {
    use_font = mainfont;
  }
  if ( use_font == NULL ) return 0;

  if ( len < 0 ) {
      len = strlen( str );
  } else {
      if ( AGUIXUtils::stringIsShorter( str, len ) == true ) {
          len = strlen( str );
      }
  }

  return use_font->textWidth( str, len );
}

int AGUIX::getStrlen4Width( const char *str, int width, int *return_width, AGUIXFont *font )
{
    return getStrlen4WidthMaxlen( str, -1, width, return_width, font );
}

int AGUIX::getStrlen4WidthMaxlen( const char *str, int maxlen,
                                  int width, int *return_width, AGUIXFont *font )
{
  int leftlen, leftw, rightlen, rightw, erglen, ergw, midlen, midw;
  AGUIXFont *use_font;

  if ( font != NULL ) {
    use_font = font;
  } else {
    use_font = mainfont;
  }
  if ( use_font == NULL ) return 0;

  if ( str == NULL || width <= 0 ) {
    if ( return_width != NULL ) *return_width = 0;
    return 0;
  }

  std::vector<int> lookup_list;

  if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED ) {  
    if ( UTF8::buildCharacterLookupListMaxlen( str, maxlen, lookup_list ) != 0 ) {
      if ( return_width != NULL ) *return_width = 0;
      return 0;
    }
    
    if ( lookup_list.size() < 1 ) {
      if ( return_width != NULL ) *return_width = 0;
      return 0;
    }
  }

  leftlen = leftw = 0;

  if ( maxlen < 0 ) {
      rightlen = strlen( str );
  } else {
      if ( AGUIXUtils::stringIsShorter( str, maxlen ) == true ) {
          rightlen = strlen( str );
      } else {
          rightlen = maxlen;
      }
  }
  
  /* check if length is too large, some X libs have problems with too large strings
     a byte (or character) needs at least 2 pixel so width/2 is a good upper limit
  */

  if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED ) {
      int all_characters = lookup_list.size() - 1;
      if ( all_characters > ( width / 2 ) && width > 0 ) {
          // rightlen is supposed to be number of bytes
          rightlen = lookup_list[width / 2];
      }
  } else {
      if ( rightlen > ( width / 2 ) && width > 0 ) {
          rightlen = width / 2;
      }
  }
  rightw = use_font->textWidth( str, rightlen );

  if ( leftw >= width ) {
    erglen = leftlen;
    ergw = leftw;
  } else if ( rightw <= width ) {
    erglen = rightlen;
    ergw = rightw;
  } else {
    // from now on leftw is always less than width
    // and rightw always more than width

    // in UTF8 mode length means characters and not bytes!
    if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED ) {
      rightlen = lookup_list.size() - 1;
      
      // check again for pixel limit
      if ( rightlen > ( width / 2 ) && width > 0 ) {
          rightlen = width / 2;
      }
    }

    erglen = ergw = 0;
    for (;;) {
      midlen = ( leftlen + rightlen ) / 2;
      if ( ( midlen == leftlen ) || ( midlen == rightlen ) ) {
        erglen = leftlen;
	ergw = leftw;
	break;
      }

      if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED ) {
        midw = use_font->textWidth( str, lookup_list[midlen] );
      } else {
        midw = use_font->textWidth( str, midlen );
      }
      if ( midw == width ) {
	erglen = midlen;
	ergw = midw;
	break;
      }
      
      if ( midw < width ) {
	leftlen = midlen;
	leftw = midw;
      } else {
	rightlen = midlen;
	rightw = midw;
      }
    }
    if ( UTF8::getUTF8Mode() != UTF8::UTF8_DISABLED ) {
      if ( erglen > 0 ) {
        // erglen is the number of characters
        // the lookup list stores the number of bytes
        erglen = lookup_list[erglen];
      }
    }
  }

  if ( return_width != NULL ) *return_width = ergw;
  return erglen;
}

void AGUIX::setClip( AGUIXFont *font, DrawableCont *dc, int x, int y, int width, int height )
{
  if ( dc != NULL ) {
    dc->clip( x, y, width, height );
  }
  if ( font != NULL ) {
    font->clip( x, y, width, height );
  } else {
    mainfont->clip( x, y, width, height );
    
    XRectangle clip_rect;
    
    clip_rect.x = x;
    clip_rect.y = y;
    clip_rect.width = width;
    clip_rect.height = height;
    
    XSetClipRectangles( dsp, gc, 0, 0, &clip_rect, 1, Unsorted );
  }
}

void AGUIX::unclip( AGUIXFont *font, DrawableCont *dc )
{
  if ( dc != NULL ) {
    dc->unclip();
  }
  if ( font != NULL ) {
    font->unclip();
  } else {
    mainfont->unclip();
    XSetClipMask( dsp, gc, None );
  }
}

void AGUIX::drawBorder( Drawable buffer, bool pressed, int x, int y, int w, int h, int topright_space )
{
  drawBorder( buffer, gc, pressed, x, y, w, h, topright_space );
}

void AGUIX::drawBorder( Drawable buffer, GC usegc, bool pressed, int x, int y, int w, int h, int topright_space )
{
  XPoint wp[3], bp[3], gp[3];

  if ( w < 4 ) w = 4;
  if ( h < 4 ) h = 4;

  if ( topright_space > ( w - 2 ) ) topright_space = w - 2;
  if ( topright_space > ( h - 2 ) ) topright_space = h - 2;
  if ( topright_space < 0 ) topright_space = 0;

  if ( pressed == true ) {
    wp[0].x = x + 1;
    wp[0].y = y + h - 1;
    wp[1].x = x + w - 1;
    wp[1].y = y + h - 1;
    wp[2].x = x + w - 1;
    wp[2].y = y + topright_space + 1;
    
    bp[0].x = x;
    bp[0].y = y + h - 1;
    bp[1].x = x;
    bp[1].y = y;
    bp[2].x = x + w - 1 - topright_space;
    bp[2].y = y;
    
    gp[0].x = x + 1;
    gp[0].y = y + h - 2;
    gp[1].x = x + 1;
    gp[1].y = y + 1;
    gp[2].x = x + w - 2 - topright_space;
    gp[2].y = y + 1;
  } else {
    wp[0].x = x;
    wp[0].y = y + h - 2;
    wp[1].x = x;
    wp[1].y = y;
    wp[2].x = x + w - 2 - topright_space;
    wp[2].y = y;
    
    bp[0].x = x;
    bp[0].y = y + h - 1;
    bp[1].x = x + w - 1;
    bp[1].y = y + h - 1;
    bp[2].x = x + w - 1;
    bp[2].y = y + topright_space;
    
    gp[0].x = x + 1;
    gp[0].y = y + h - 2;
    gp[1].x = x + w - 2;
    gp[1].y = y + h - 2;
    gp[2].x = x + w - 2;
    gp[2].y = y + topright_space + 1;
  }
  
  setFG( usegc, m_3d_bright );
  DrawLine( buffer, usegc, wp[0].x, wp[0].y, wp[1].x, wp[1].y );
  DrawLine( buffer, usegc, wp[1].x, wp[1].y, wp[2].x, wp[2].y );

  setFG( usegc, m_3d_dark );
  DrawLine( buffer, usegc, bp[0].x, bp[0].y, bp[1].x, bp[1].y );
  DrawLine( buffer, usegc, bp[1].x, bp[1].y, bp[2].x, bp[2].y );

  // dark grey
  setFG( usegc, AGUIXColor( 1, AGUIXColor::SYSTEM_COLOR ) );
  DrawLine( buffer, usegc, gp[0].x, gp[0].y, gp[1].x, gp[1].y );
  DrawLine( buffer, usegc, gp[1].x, gp[1].y, gp[2].x, gp[2].y );
}

Atom AGUIX::getAtom( atom_name_t name ) const
{
    switch ( name ) {
        case CLOSE_ATOM:
            return WM_delete_window;
        case NET_WM_NAME_ATOM:
            return XA_NET_WM_NAME;
        case NET_WM_ICON_NAME_ATOM:
            return XA_NET_WM_ICON_NAME;
        case TARGETS_ATOM:
            return XA_TARGETS;
        case NET_WM_WINDOW_TYPE:
            return XA_NET_WM_WINDOW_TYPE;
        case NET_WM_WINDOW_TYPE_DIALOG:
            return XA_NET_WM_WINDOW_TYPE_DIALOG;
        case NET_WM_WINDOW_TYPE_NORMAL:
            return XA_NET_WM_WINDOW_TYPE_NORMAL;
        case NET_WM_STATE:
            return XA_NET_WM_STATE;
        case NET_WM_STATE_MAXIMIZED_HORZ:
            return XA_NET_WM_STATE_MAXIMIZED_HORZ;
        case NET_WM_STATE_MAXIMIZED_VERT:
            return XA_NET_WM_STATE_MAXIMIZED_VERT;
        case UTF8_STRING:
            return XA_UTF8_STRING;
        case XDNDENTER:
            return XA_XDNDENTER;
        case XDNDPOSITION:
            return XA_XDNDPOSITION;
        case XDNDSTATUS:
            return XA_XDNDSTATUS;
        case XDNDTYPELIST:
            return XA_XDNDTYPELIST;
        case XDNDACTIONCOPY:
            return XA_XDNDACTIONCOPY;
        case XDNDDROP:
            return XA_XDNDDROP;
        case XDNDLEAVE:
            return XA_XDNDLEAVE;
        case XDNDFINISHED:
            return XA_XDNDFINISHED;
        case XDNDSELECTION:
            return XA_XDNDSELECTION;
        case XDNDPROXY:
            return XA_XDNDPROXY;
        case XDNDAWARE:
            return XA_XDNDAWARE;
        case CARDINAL_ATOM:
            return XA_CARDINAL_ATOM;
        case NET_WM_ICON_ATOM:
            return XA_NET_WM_ICON;
        case CLIPBOARD:
            return XA_CLIPBOARD;
    }
    return None;
}

void AGUIX::registerPopUpWindow( PopUpWindow *win )
{
    if ( win == NULL ) return;

    popup_wins.push_back( win );
}

void AGUIX::checkPopUpWindows( Message *msg )
{
    if ( msg == NULL ) return;

    if ( m_open_popup_counter < 1 ) return;
    if ( msg->type != ButtonPress && msg->type != ButtonRelease ) return;

    // check whether the event window is owned by some of the registered popups
    //  if there is a popup, get its groupid and close all other popups
    //  if not, close all popups

    if ( m_popup_ignore_button_release == true ) {
        m_popup_ignore_button_release = false;

        if ( msg->type == ButtonRelease ) {
            return;
        }
    }

    PopUpWindow *win_for_msg = NULL;

    for ( popup_wins_cit_t it1 = popup_wins.begin();
          it1 != popup_wins.end();
          it1++ ) {
        if ( (*it1)->isParent( msg->window ) == true ) {
            win_for_msg = *it1;
            break;
        }
    }

    if ( win_for_msg == NULL ) {
        // not a message for a popup window
        // so hide all popups
        for ( popup_wins_cit_t it1 = popup_wins.begin();
              it1 != popup_wins.end();
              it1++ ) {
            (*it1)->hide();
        }
    } else {
        // found a popup window for the message
        // so hide all popups which are not in the same group
        hideOtherPopUpWindows( win_for_msg->getGroupID() );
    }
}

void AGUIX::hidePopUpWindows( int group_id )
{
    for ( popup_wins_cit_t it1 = popup_wins.begin();
          it1 != popup_wins.end();
          it1++ ) {
        if ( (*it1)->getGroupID() == group_id ) {
            (*it1)->hide();
        }
    }
}

void AGUIX::hideOtherPopUpWindows( int group_id )
{
    for ( popup_wins_cit_t it1 = popup_wins.begin();
          it1 != popup_wins.end();
          it1++ ) {
        if ( (*it1)->getGroupID() != group_id ) {
            (*it1)->hide();
        }
    }
}

void AGUIX::xSync()
{
    XSync( dsp, False );
}

void AGUIX::popupOpened()
{
    m_open_popup_counter++;

    if ( m_open_popup_counter == 1 ) {
        // first popup opened, so ignore next button release
        m_popup_ignore_button_release = true;
    }
}

void AGUIX::popupClosed()
{
    m_open_popup_counter--;
    if ( m_open_popup_counter < 0 ) {
        fprintf( stderr, "Worker:bug in popup counter\n" );
        m_open_popup_counter = 0;
    }
}

int AGUIX::getWidgetRootPosition( Widget *widget, int *x, int *y )
{
    if ( widget == NULL || x == NULL || y == NULL ) return 1;
    
    int tx, tx2, ty, ty2;
    
    //TODO this is a rather ugly hack, better walk through window hierarchy with
    // XQueryTree and querying the corresponding postions
    queryPointer( widget->getWindow(), &tx, &ty );
    queryRootPointer( &tx2, &ty2 );
    
    tx2 -= tx;
    ty2 -= ty;
    
    *x = tx2;
    *y = ty2;
    
    return 0;
}

void AGUIX::updateSystemColors( int changed_user_color )
{
    int default_fg = 1;
    int default_bg = 0;

    default_fg = getFaces().getColor( "default-fg" );
    default_bg = getFaces().getColor( "default-bg" );

    if ( changed_user_color == default_bg || changed_user_color == default_fg ) {
        if ( m_color_values.count( AGUIXColor( default_bg, AGUIXColor::USER_COLOR ) ) > 0 ) {
            int r1 = m_color_values[AGUIXColor( default_bg, AGUIXColor::USER_COLOR )].red,
                g1 = m_color_values[AGUIXColor( default_bg, AGUIXColor::USER_COLOR )].green,
                b1 = m_color_values[AGUIXColor( default_bg, AGUIXColor::USER_COLOR )].blue;
            
            int r2 = 0, g2 = 0, b2 = 0;

            if ( m_color_values.count( AGUIXColor( default_fg, AGUIXColor::USER_COLOR ) ) > 0 ) {
                r2 = m_color_values[AGUIXColor( default_fg, AGUIXColor::USER_COLOR )].red,
                g2 = m_color_values[AGUIXColor( default_fg, AGUIXColor::USER_COLOR )].green,
                b2 = m_color_values[AGUIXColor( default_fg, AGUIXColor::USER_COLOR )].blue;
            }

            // let the first system color be 2/3 between user color 0 and
            // user color 1 (if available); usually this is some darker gray
            int r3 = ( r1 * 2 + r2 ) / 3;
            int g3 = ( g1 * 2 + g2 ) / 3;
            int b3 = ( b1 * 2 + b2 ) / 3;

            if ( m_system_colors < 1 ) {
                AddColor( r3, g3, b3, AGUIXColor::SYSTEM_COLOR );
            } else {
                changeColor( AGUIXColor( 0, AGUIXColor::SYSTEM_COLOR ), r3, g3, b3 );
            }

            // let the second system color be 9/10 of user color 0
            r3 = r1 * 9 / 10;
            g3 = g1 * 9 / 10;
            b3 = b1 * 9 / 10;

            if ( m_system_colors < 2 ) {
                AddColor( r3, g3, b3, AGUIXColor::SYSTEM_COLOR );
            } else {
                changeColor( AGUIXColor( 1, AGUIXColor::SYSTEM_COLOR ), r3, g3, b3 );
            }

            // let the second system color be 3/10 of user color 0
            r3 = r1 * 3 / 10;
            g3 = g1 * 3 / 10;
            b3 = b1 * 3 / 10;

            if ( m_system_colors < 3 ) {
                AddColor( r3, g3, b3, AGUIXColor::SYSTEM_COLOR );
            } else {
                changeColor( AGUIXColor( 2, AGUIXColor::SYSTEM_COLOR ), r3, g3, b3 );
            }
        }
    }
}

bool AGUIX::getLastTypedWindowEvent( Window win, int type, XEvent *return_event )
{
    if ( return_event == NULL ) return false;
    
    int count = 0;

    while ( XCheckTypedWindowEvent( dsp, win, type, return_event ) ) count++;

    if ( count == 0 ) return false;
    return true;
}

AWindow *AGUIX::getFocusedAWindow( bool top_level )
{
    if ( m_current_xfocus_window == 0 ) return NULL;

    return findAWindow( m_current_xfocus_window, top_level );
}

void AGUIX::registerBGHandler( AWindow *window, const RefCount< BackgroundMessageHandler > &handler )
{
    if ( ! window ) return;

    if ( m_bg_handlers.count( window ) > 0 ) {
        m_bg_handler_cleanup_list.push_back( m_bg_handlers[ window ] );
    }
    m_bg_handlers[ window ] = handler;
}

void AGUIX::unregisterBGHandler( AWindow *window )
{
    if ( ! window ) return;

    /* since the handler may remove it during its execution we still need a valid
     *  handle so we first move it to a cleanup list which gets clear after execution
     */
    if ( m_bg_handlers.count( window ) > 0 ) {
        m_bg_handler_cleanup_list.push_back( m_bg_handlers[ window ] );
        m_bg_handler_erase_handler.push_back( window );
    }
}

void AGUIX::executeBGHandlers( AGMessage &msg )
{
    std::map< AWindow *, RefCount< BackgroundMessageHandler > >::iterator it1;

    /* TODO check whether window still exists */
    for ( it1 = m_bg_handlers.begin();
          it1 != m_bg_handlers.end();
          it1++ ) {
        it1->second->handleAGUIXMessage( msg );
    }
    m_bg_handler_cleanup_list.clear();

    for ( std::list< AWindow *>::iterator it2 = m_bg_handler_erase_handler.begin();
	  it2 != m_bg_handler_erase_handler.end();
	  it2++ ) {
        m_bg_handlers.erase( *it2 );
    }
    m_bg_handler_erase_handler.clear();
}

/*
 * method to clean up BG handler
 * their destructor will be called so they hopefully release their resources
 */
void AGUIX::destroyBGHandlers()
{
    m_bg_handlers.clear();
    m_bg_handler_cleanup_list.clear();
}

int AGUIX::copyToClipboard( const std::string &s,
                             enum target_clipboard_buffer target,
                             const std::string &mimetype )
{
    if ( (ssize_t)s.size() > (ssize_t)AKVPPool::getGlobalKVPPool()->getEntry( "clipboard.max_content_size" ).value.int_value ) {
        return -EFBIG;
    }

    if ( target == PRIMARY_BUFFER || target == BOTH_BUFFERS ) {
        m_primary_buffer_state.buffer_string = s;
    }
    if ( target == CLIPBOARD_BUFFER || target == BOTH_BUFFERS ) {
        m_clipboard_state.buffer_string = s;
        m_clipboard_state.file_list.clear();
    }

    if ( target == PRIMARY_BUFFER || target == BOTH_BUFFERS ) {
        XStoreBytes( dsp, s.c_str(), s.length() );
        XSetSelectionOwner( dsp, XA_PRIMARY, getGroupWin(), CurrentTime );
        if ( getGroupWin() != XGetSelectionOwner( dsp, XA_PRIMARY ) ) {
            m_primary_buffer_state.buffer_string = "";
        }
    }

    if ( target == CLIPBOARD_BUFFER || target == BOTH_BUFFERS ) {
        XSetSelectionOwner( dsp, XA_CLIPBOARD, getGroupWin(), CurrentTime );
        if ( getGroupWin() != XGetSelectionOwner( dsp, XA_CLIPBOARD ) ) {
            m_clipboard_state.buffer_string = "";
        }
    }

    m_clipboard_state.target = None;

    if ( ! mimetype.empty() ) {
        if ( m_extra_atoms.count( mimetype ) == 0 ) {
            Atom a = XInternAtom( dsp, mimetype.c_str(), False );
            if ( a != None ) {
                m_extra_atoms[ mimetype ] = a;
            }
        }
        if ( m_extra_atoms.count( mimetype ) != 0 ) {
            m_clipboard_state.target = m_extra_atoms[ mimetype ];
        }
    }

    return 0;
}

void AGUIX::copyFileListToClipboard( const std::vector< std::string > &l,
                                     bool cut_operation )
{
    m_clipboard_state.file_list = l;
    m_clipboard_state.file_list_cut = cut_operation;
    m_clipboard_state.buffer_string.clear();

    XSetSelectionOwner( dsp, XA_CLIPBOARD, getGroupWin(), CurrentTime );
    if ( getGroupWin() != XGetSelectionOwner( dsp, XA_CLIPBOARD ) ) {
        m_clipboard_state.file_list.clear();
    }
}

void AGUIX::copyPrimaryToClipboard()
{
    if ( m_primary_buffer_state.buffer_string.empty() ) return;

    m_clipboard_state.buffer_string = m_primary_buffer_state.buffer_string;
    m_clipboard_state.file_list.clear();

    XSetSelectionOwner( dsp, XA_CLIPBOARD, getGroupWin(), CurrentTime );
    if ( getGroupWin() != XGetSelectionOwner( dsp, XA_CLIPBOARD ) ) {
        m_clipboard_state.buffer_string = "";
    }
}

void AGUIX::getLargestDimensionOfCurrentScreen( int *x, int *y,
                                                int *width, int *height )
{
#ifdef HAVE_XINERAMA
    XineramaScreenInfo *xinerama_info;
    int number_of_xinerama_screens = 0;
    int mx, my;
    bool found = false;

    queryRootPointer( &mx, &my );

    xinerama_info = XineramaQueryScreens( dsp,
                                          &number_of_xinerama_screens );

    if ( xinerama_info != NULL ) {
        int l_x = 0, l_y = 0, l_w = 0, l_h = 0;
        for ( int i = 0; i < number_of_xinerama_screens; i++ ) {
            if ( mx >= xinerama_info[i].x_org &&
                 my >= xinerama_info[i].y_org &&
                 mx < xinerama_info[i].x_org + xinerama_info[i].width &&
                 my < xinerama_info[i].y_org + xinerama_info[i].height ) {
                if ( xinerama_info[i].width > l_w &&
                     xinerama_info[i].height > l_h ) {
                    l_x = xinerama_info[i].x_org;
                    l_y = xinerama_info[i].y_org;
                    l_w = xinerama_info[i].width;
                    l_h = xinerama_info[i].height;
                    found = true;
                }
            }
        }

        XFree( xinerama_info );

        if ( found ) {
            if ( x ) *x = l_x;
            if ( y ) *y = l_y;
            if ( width ) *width = l_w;
            if ( height ) *height = l_h;
            return;
        }
    }
#endif

    if ( x ) *x = 0;
    if ( y ) *y = 0;
    if ( width ) *width = getRootWindowWidth();
    if ( height ) *height = getRootWindowHeight();
}

bool AGUIX::isValidScreenPosition( int x, int y,
                                   int w, int h )
{
#ifdef HAVE_XINERAMA
    XineramaScreenInfo *xinerama_info;
    int number_of_xinerama_screens = 0;
    bool found = false;

    xinerama_info = XineramaQueryScreens( dsp,
                                          &number_of_xinerama_screens );

    if ( xinerama_info != NULL ) {
        for ( int i = 0; i < number_of_xinerama_screens; i++ ) {
            if ( x + w > xinerama_info[i].x_org &&
                 y + h > xinerama_info[i].y_org &&
                 x < xinerama_info[i].x_org + xinerama_info[i].width &&
                 y < xinerama_info[i].y_org + xinerama_info[i].height ) {
                found = true;
                break;
            }
        }

        XFree( xinerama_info );

        return found;
    }
#endif

    if ( x + w < 0 ) return false;
    if ( x >= getRootWindowWidth() ) return false;

    if ( y + h < 0 ) return false;
    if ( y >= getRootWindowHeight() ) return false;

    return true;
}

void AGUIX::rerequestCut()
{
    if ( m_primary_buffer_state.last_selection_request_atom == XA_UTF8_STRING &&
         m_primary_buffer_state.last_selection_request_window != None ) {
        // try again for fallback
        requestCut( m_primary_buffer_state.last_selection_request_window,
                    true,
                    false,
                    m_primary_buffer_state.paste_elem,
                    m_primary_buffer_state.paste_agmessage_data,
                    CLIPBOARD_REQUEST_STRING);
    } else {
        if ( m_primary_buffer_state.paste_elem != NULL ) {
            m_primary_buffer_state.paste_elem->cancelpaste();
            m_primary_buffer_state.reset();
        }
    }

    if ( m_clipboard_state.last_selection_request_atom == XA_UTF8_STRING &&
         m_clipboard_state.last_selection_request_window != None ) {
        // try again for fallback
        requestCut( m_clipboard_state.last_selection_request_window,
                    true,
                    false,
                    m_clipboard_state.paste_elem,
                    m_clipboard_state.paste_agmessage_data,
                    CLIPBOARD_REQUEST_STRING );
    } else {
        if ( m_clipboard_state.paste_elem != NULL ) {
            m_clipboard_state.paste_elem->cancelpaste();
            m_clipboard_state.paste_elem = NULL;
            m_clipboard_state.reset();
        }
    }
}

void AGUIX::setApplyWindowDialogType( bool nv )
{
    m_apply_window_dialog_type = nv;
}

bool AGUIX::getApplyWindowDialogType() const
{
    return m_apply_window_dialog_type;
}

void AGUIX::setOverrideXIM( bool nv )
{
    m_override_xim = nv;
}

bool AGUIX::getOverrideXIM() const
{
    return m_override_xim;
}

void AGUIX::setSkipFilterEvent( bool nv )
{
    m_skip_filter_event = true;
}

void AGUIX::applyFaces( const FaceDB &faces )
{
    m_faces = faces;

    m_default_fg = getFaces().getColor( "default-fg" );
    m_default_bg = getFaces().getColor( "default-bg" );
    m_3d_bright = getFaces().getColor( "3d-bright" );
    m_3d_dark = getFaces().getColor( "3d-dark" );

    updateSystemColors( m_default_bg );

    rebuildBackgroundPixmap();
}

void AGUIX::setBubbleHelpWindow( std::shared_ptr< BubbleWindow > bw )
{
    m_bubble_help.bubble_help_window = bw;
}

void AGUIX::setBubbleHelpCandidate( GUIElement *elem )
{
    m_bubble_help.help_candidate = elem;
    gettimeofday( &m_bubble_help.enter_time, NULL );

    m_external_timeouts.enableExternalTimer();
}

void AGUIX::clearBubbleHelpCandidate( GUIElement *elem )
{
    if ( elem == NULL ||
         m_bubble_help.help_candidate == elem ) {
        m_bubble_help.help_candidate = NULL;

        m_external_timeouts.disableExternalTimer();
    }
}

void AGUIX::aguix_timeouts::enableExternalTimer()
{
    if ( timeout_set ) return;

    timeout_set = true;

    if ( auto ts = timeout_store.lock() ) {
        ts->addTimeout( BUBBLE_HELP_TIMEOUT_MS );
    }
}

void AGUIX::aguix_timeouts::disableExternalTimer()
{
    if ( ! timeout_set ) return;

    timeout_set = false;

    if ( auto ts = timeout_store.lock() ) {
        ts->clearTimeout( BUBBLE_HELP_TIMEOUT_MS );
    }
}

void AGUIX::setExternalTimeoutStore( std::weak_ptr< TimeoutStore > timeout_store )
{
    m_external_timeouts.timeout_store = timeout_store;
}

void AGUIX::external_timeout_callback()
{
    struct timeval curTime;
    gettimeofday( &curTime, NULL );

    if ( m_bubble_help.help_candidate &&
         ldiffgtod_m( &curTime, &m_bubble_help.enter_time ) >= BUBBLE_HELP_OPEN_TIME_MS ) {

        AWindow *awin = getFocusedAWindow( true );
        if ( awin ) {
            if ( awin->isParent( m_bubble_help.help_candidate->getWindow(), false ) ) {
                m_bubble_help.help_candidate->openBubbleHelp();
            }
        }
        clearBubbleHelpCandidate();
    }
}

/* returning true means you can ask for next X event without
 * blocking (except when I do not have the fd to wait on
 */
bool AGUIX::waitForEventOnFD( int ms )
{
#ifdef HAVE_XCONNECTIONNUMBER
    fd_set rdfs;
    int ret;

    FD_ZERO( &rdfs );
    int max_fd_plus_one = m_x_fd + 1;

    FD_SET( m_x_fd, &rdfs );

    struct timeval timeout;

    timeout.tv_sec = ms / 1000;
    timeout.tv_usec = ( ms % 1000 ) * 1000;

    ret = select( max_fd_plus_one, &rdfs, NULL, NULL, &timeout );

    if ( ret > 0 ) {
        return true;
    } else {
        return false;
    }
#endif
    return true;
}

void AGUIX::handleXDNDEnter( XEvent *e )
{
    if ( m_xdnd.drop_active == true &&
         now - m_xdnd.last_activity < XDND_TIMEOUT ) {
        // ignore other enter events without timeout
        return;
    }

    Window xdnd_source = e->xclient.data.l[0];
    bool more_than_three_types = e->xclient.data.l[1] & 1;
    int protocol_version = e->xclient.data.l[1] >> 24;

    Atom type1 = e->xclient.data.l[2];
    Atom type2 = e->xclient.data.l[3];
    Atom type3 = e->xclient.data.l[4];

    Atom selected_target = None;

    if ( more_than_three_types ) {
        AGUIX_XProperty prop = getProperty( xdnd_source, XA_XDNDTYPELIST );

        selected_target = selectBestTargetForDND( prop );

        prop.freeProperty();
    } else {
        selected_target = selectBestTargetForDND( type1, type2, type3 );
    }

    if ( selected_target != None ) {
        m_xdnd.drop_active = true;
        m_xdnd.source = xdnd_source;
        m_xdnd.selected_target = selected_target;
        m_xdnd.last_activity = now;
        m_xdnd.version = protocol_version;
    }
}

void AGUIX::handleXDNDPosition( XEvent *e )
{
    m_xdnd.last_activity = now;

    Window xdnd_source = e->xclient.data.l[0];
    unsigned long root_x = e->xclient.data.l[2] >> 16;
    unsigned long root_y = e->xclient.data.l[2] & 0xffff;
				
#if 0
    // right now we only support copy anyways
    Atom action = XA_XDNDACTIONCOPY;

    if ( m_xdnd.version >= 2 ) {
        action = e->xclient.data.l[4];
    }
#endif

    //Xdnd: reply with an XDND status message
    XClientMessageEvent m;

    memset( &m, 0, sizeof( m ) );

    m.type = ClientMessage;
    m.display = e->xclient.display;
    m.window = xdnd_source;
    m.message_type = XA_XDNDSTATUS;
    m.format = 32;

    AWindow *awin = findAWindow( e->xclient.window, false );

    m_xdnd.target_awin = awin;
    if ( awin ) {
        m_xdnd.target_widget = awin->findWidgetForRootPos( root_x,
                                                           root_y );
    } else {
        m_xdnd.target_widget = NULL;
    }

    if ( awin ) {
        bool accept_drop = false;

        if ( awin->acceptXDND() &&
             m_xdnd.target_widget != NULL ) {
            auto *dndw = dynamic_cast<DNDWidget*>( m_xdnd.target_widget );

            if ( dndw != NULL ) {
                accept_drop = true;
            }
        }

        m.data.l[0] = awin->getWindow();
        m.data.l[1] = accept_drop ? 1 : 0;
        m.data.l[1] |= 2; // no rectangle of silence
        m.data.l[2] = 0;  // empty rectangle
        m.data.l[3] = 0;
        m.data.l[4] = XA_XDNDACTIONCOPY; // we only support copy

        worker_x_ignore_bad_window_error();
        XSendEvent( dsp, xdnd_source, False, NoEventMask, (XEvent*)&m );
        XFlush( dsp );
    }
}

void AGUIX::handleXDNDLeave( XEvent *e )
{
    m_xdnd.drop_active = false;
}

void AGUIX::handleXDNDDrop( XEvent *e )
{
    Window xdnd_source = e->xclient.data.l[0];

    if ( ! m_xdnd.drop_active || m_xdnd.target_awin == NULL ) {
        XClientMessageEvent m;

        memset( &m, 0, sizeof( m ) );

        m.type = ClientMessage;
        m.display = e->xclient.display;
        m.window = xdnd_source;
        m.message_type = XA_XDNDFINISHED;
        m.format = 32;
        m.data.l[0] = m_xdnd.target_awin ? m_xdnd.target_awin->getWindow() : e->xclient.window;
        m.data.l[1] = 0;
        m.data.l[2] = None;

        worker_x_ignore_bad_window_error();
        XSendEvent( dsp, xdnd_source, False, NoEventMask, (XEvent*)&m );
        XFlush( dsp );

        return;
    }

    if ( m_xdnd.version >= 1 ) {
        XConvertSelection( dsp,
                           XA_XDNDSELECTION,
                           m_xdnd.selected_target,
                           XA_PRIMARY,
                           m_xdnd.target_awin->getWindow(),
                           e->xclient.data.l[2] );
    } else {
        XConvertSelection( dsp,
                           XA_XDNDSELECTION,
                           m_xdnd.selected_target,
                           XA_PRIMARY,
                           m_xdnd.target_awin->getWindow(),
                           CurrentTime );
    }
}

AGUIX_XProperty AGUIX::getProperty( Window win,
                                    Atom property )
{
    AGUIX_XProperty res;

    unsigned long ret_after;

    long bytes_to_read = 1024;

    res.success = true;

    do {
        if ( res.property_data ) {
            XFree( res.property_data );
            res.property_data = NULL;
        }

        worker_x_ignore_bad_window_error();

        if ( XGetWindowProperty( dsp,
                                 win,
                                 property,
                                 0,
                                 bytes_to_read,
                                 False,
                                 AnyPropertyType,
                                 &res.type,
                                 &res.format,
                                 &res.number_of_items,
                                 &ret_after,
                                 &res.property_data ) != Success ) {
            res.success = false;
            break;
        }

        if ( worker_x_bad_window_occured() ) {
            res.success = false;
            break;
        }

        bytes_to_read *= 2;
    } while ( ret_after > 0 );

    return res;
}

Atom AGUIX::selectBestTargetForDND( const std::vector< Atom > &list )
{
    int best_score = -1;
    Atom best_atom = None;

    for ( auto &atom : list ) {
        char *name = XGetAtomName( dsp, atom );
        if ( name ) {
            if ( m_xdnd_receive_types.count( name ) ) {
                int score = m_xdnd_receive_types[ name ];

                if ( score > best_score ) {
                    best_score = score;
                    best_atom = atom;
                }
            }
            
            XFree( name );
        }
    }

    return best_atom;
}

Atom AGUIX::selectBestTargetForDND( Atom type1,
                                    Atom type2,
                                    Atom type3 )
{
    std::vector< Atom > l;

    if ( type1 != None ) {
        l.push_back( type1 );
    }
    if ( type2 != None ) {
        l.push_back( type2 );
    }
    if ( type3 != None ) {
        l.push_back( type3 );
    }

    return selectBestTargetForDND( l );
}

Atom AGUIX::selectBestTargetForDND( AGUIX_XProperty type_list )
{
	if ( ( type_list.type != XA_ATOM &&
           type_list.type != XA_TARGETS ) ||
         type_list.format != 32 ) {
        return None;
    }

    Atom *atom_list = (Atom*)type_list.property_data;
    std::vector< Atom > l;

    for ( size_t i = 0; i < type_list.number_of_items; i++ ) {
        l.push_back( atom_list[i] );
    }
		
    return selectBestTargetForDND( l );
}

void AGUIX::initXDNDTypes()
{
    m_xdnd_receive_types[ "text/uri-list" ] = 10;
    m_xdnd_receive_types[ "text/plain" ] = 5;
}

void AGUIX::setTargetsProperty( Window w, Atom property )
{
    std::vector<Atom> targets;

    targets.push_back( XA_TARGETS );
	targets.push_back( XA_MULTIPLE );
    targets.push_back( XA_TEXT_URI_LIST );
    targets.push_back( XA_TEXT_PLAIN );

    worker_x_ignore_bad_window_error();

	XChangeProperty( dsp, w, property, XA_ATOM, 32, PropModeReplace, 
					(unsigned char*)&targets[0], targets.size() );

    worker_x_bad_window_occured();
}

void AGUIX::processSelectionRequest( XEvent *e )
{
	Atom selection   = e->xselectionrequest.selection;
	Atom target      = e->xselectionrequest.target;
	Atom property    = e->xselectionrequest.property;
	Window requestor = e->xselectionrequest.requestor;
	Time timestamp   = e->xselectionrequest.time;

	XEvent s;

    memset( &s, 0, sizeof( s ) );
    
	s.xselection.type = SelectionNotify;
	s.xselection.requestor = requestor;
	s.xselection.selection = selection;
	s.xselection.target    = target;
	s.xselection.property  = None;   //This means refusal
	s.xselection.time      = timestamp;

	if ( target == XA_TARGETS ) {
		setTargetsProperty( requestor, property );
		s.xselection.property = property;
	} else if ( target == XA_TEXT_URI_LIST ||
                target == XA_TEXT_PLAIN ) {
		s.xselection.property = property;

        std::string res;

        if ( m_xdnd.drag_initiator ) {
            auto *dndw = dynamic_cast<DNDWidget*>( m_xdnd.drag_initiator );

            if ( dndw && dndw->fillDropData( &res ) != 0 ) {
                res.clear();
            }
        }

        worker_x_ignore_bad_window_error();

		XChangeProperty( dsp, requestor, property, target, 8, PropModeReplace,
                         (const unsigned char*)res.c_str(), res.length() );

        if ( worker_x_bad_window_occured() ) {
            return;
        }
	} else if ( target == XA_MULTIPLE ) {
        // not supported
	} else {	
        // not supported
	}

    worker_x_ignore_bad_window_error();
	XSendEvent( dsp, requestor, True, 0, &s );
    XFlush( dsp );
}

Window AGUIX::findXDNDAwareWindow( Window w, Window *return_proxy_window )
{
	int nprops;
    int i;
	Atom *prop_atoms;

    *return_proxy_window = None;
    
	if ( w == None ) {
		return None;
    }

    worker_x_ignore_bad_window_error();

	prop_atoms = XListProperties( dsp, w, &nprops );

    if ( worker_x_bad_window_occured() ) {
        return None;
    }
    
	for ( i = 0; i < nprops; i++ ) {
		if ( prop_atoms[i] == XA_XDNDAWARE ) {
			break;
        }
    }

	if ( nprops ) {
		XFree( prop_atoms );
    }

	if ( i != nprops ) {
        AGUIX_XProperty prop = getProperty( w, XA_XDNDPROXY );

        if ( prop.success && prop.number_of_items == 1 && prop.format == 32) {
            unsigned long *ldata = (unsigned long *)prop.property_data;

            *return_proxy_window = ldata[0];
        }

        prop.freeProperty();

        return w;
    }

	Window child, wtmp;
	int tmp;
	unsigned int utmp;

	XQueryPointer( dsp, w, &wtmp, &child, &tmp, &tmp, &tmp, &tmp, &utmp );

	return findXDNDAwareWindow( child, return_proxy_window );
}

void AGUIX::startXDND( Widget *initiator,
                       Window w,
                       unsigned int mouse_button,
                       unsigned int cancel_mouse_button )
{
    if ( XGrabPointer( dsp, w, True,
                       ButtonMotionMask | ButtonReleaseMask,
                       GrabModeAsync, GrabModeAsync,
                       RootWindow( dsp, scr ),
                       m_xcursor_circle,
                       CurrentTime ) == GrabSuccess ) {
        m_xdnd = aguix_xdnd_state();

        m_xdnd.mouse_button = mouse_button;
        m_xdnd.cancel_mouse_button = cancel_mouse_button;
        m_xdnd.skip_release_count = 1;

        m_xdnd.drag_active = true;
        m_xdnd.drag_initiator = initiator;
        m_xdnd.drag_initiator_window = w;
        XSetSelectionOwner( dsp, XA_XDNDSELECTION, w, CurrentTime );
    }
}

void AGUIX::handleXDNDStatus( XEvent *e )
{
#if 0
    std::cout << "XDND status:" << std::endl;
    std::cout << "  target window = 0x" << std::hex << e->xclient.data.l[0] << std::dec << std::endl;
    std::cout << "  accept = " << (e->xclient.data.l[1] & 1) << std::endl;
    std::cout << "  no rectangle = " << (e->xclient.data.l[1] & 2) << std::endl;
    std::cout << "  x = " << (e->xclient.data.l[2] >> 16) << std::endl;
    std::cout << "  y = " << (e->xclient.data.l[2] & 0xffff) << std::endl;
    std::cout << "  w = " << (e->xclient.data.l[3] >> 16) << std::endl;
    std::cout << "  h = " << (e->xclient.data.l[3] & 0xffff) << std::endl;
#endif

    if ( m_xdnd.drag_active ) {
        if ( ( e->xclient.data.l[1] & 1 ) && m_xdnd.drag_target_status != m_xdnd.AGUIX_XDND_UNAWARE ) {
            m_xdnd.drag_target_status = m_xdnd.AGUIX_XDND_ACCEPT;
        }

        if ( ! ( e->xclient.data.l[1] & 1 ) && m_xdnd.drag_target_status != m_xdnd.AGUIX_XDND_UNAWARE ) {
            m_xdnd.drag_target_status = m_xdnd.AGUIX_XDND_UNRESPONSIVE;
        }

        if ( ( e->xclient.data.l[1] & 2 ) == 0 ) {
            m_xdnd.position_silent = true;
            m_xdnd.position_silent_x = e->xclient.data.l[2] >> 16;
            m_xdnd.position_silent_y = e->xclient.data.l[2] & 0xffff;
            m_xdnd.position_silent_w = e->xclient.data.l[3] >> 16;
            m_xdnd.position_silent_h = e->xclient.data.l[3] & 0xffff;
        }
    }
}

void AGUIX::handleXDNDFinished( XEvent *e )
{
    cleanupDND();
}

void AGUIX::cleanupDND()
{
    if ( m_xdnd.drag_initiator ) {
        if ( auto *dndw = dynamic_cast<DNDWidget*>( m_xdnd.drag_initiator ) ) {
            dndw->cleanupDNDStart();
        }
        m_xdnd.drag_initiator = NULL;
    }

    worker_x_handle_bad_window_error();
}

void AGUIX::abortDND()
{
    XClientMessageEvent m;

    memset( &m, 0, sizeof( m ) );

    m.type = ClientMessage;
    m.display = LastEvent.xbutton.display;

    m.window = m_xdnd.drag_current_proxy_target_window != None ? m_xdnd.drag_current_proxy_target_window : m_xdnd.drag_current_target_window;
    m.message_type = XA_XDNDLEAVE;

    m.format = 32;
    m.data.l[0] = m_xdnd.drag_initiator_window;
    m.data.l[1] = 0;
    m.data.l[2] = 0;
    m.data.l[3] = 0;
    m.data.l[4] = 0;

    worker_x_ignore_bad_window_error();
    XSendEvent( dsp, m.window,
                False, NoEventMask, (XEvent*)&m );

    XFlush( dsp );

    XUngrabPointer( dsp, CurrentTime );

    m_xdnd.drag_active = false;
    m_xdnd.drag_target_status = m_xdnd.AGUIX_XDND_UNAWARE;
    m_xdnd.drag_current_target_window = None;
    m_xdnd.drag_current_target_version = -1;

    cleanupDND();
}

void AGUIX::widgetDestroyed( Widget *w )
{
    if ( w == m_xdnd.drag_initiator ) {
        m_xdnd.drag_initiator = nullptr;
    }
}

AGUIX::col_values_t AGUIX::getColorInfo( const AGUIXColor &color ) const
{
    try {
        return m_color_values.at( color );
    } catch (...) {
        return col_values_t();
    }
}

AGUIX::col_values_t AGUIX::blend( const col_values_t &lhs,
                                  const col_values_t &rhs,
                                  int percentage ) const
{
    if ( percentage < 0 ) percentage = 0;
    else if ( percentage > 100 ) percentage = 100;

    col_values_t res;

    int d = rhs.red - lhs.red;
    d *= percentage;
    d /= 100;

    res.red = lhs.red + d;

    d = rhs.green - lhs.green;
    d *= percentage;
    d /= 100;

    res.green = lhs.green + d;

    d = rhs.blue - lhs.blue;
    d *= percentage;
    d /= 100;

    res.blue = lhs.blue + d;

    return res;
}

AGUIXColor AGUIX::getColor( const col_values_t &v )
{
    for ( auto it = m_color_values.begin();
          it != m_color_values.end();
          it++ ) {
        if ( it->second == v ) {
            return it->first;
        }
    }

    return AddColor( v.red,
                     v.green,
                     v.blue,
                     AGUIXColor::EXTRA_COLOR );
}

int AGUIX::getCurrentUserColorInstance() const
{
    return m_user_color_instance;
}

bool AGUIX::isModifierPressed( unsigned int mask ) const
{
    return m_modifier_pressed & mask;
}

bool AGUIX::lastClickModifier( unsigned int mask ) const
{
    return m_last_click_modifier & mask;
}

void AGUIX::setStandardIconData( const unsigned long *data,
                                 size_t data_length )
{
    m_net_wm_icon_data = data;
    m_net_wm_icon_data_length = data_length;
}

bool AGUIX::getStandardIconData( const unsigned long **return_data,
                                 size_t *return_data_length ) const
{
    if ( ! m_net_wm_icon_data ) return false;
    if ( ! return_data ) return false;
    if ( ! return_data_length ) return false;

    *return_data = m_net_wm_icon_data;
    *return_data_length = m_net_wm_icon_data_length;

    return true;
}

void AGUIX::closeBubbleHelp( AWindow *awin )
{
    if ( ! awin ) {
        return;
    }

    if ( ! m_bubble_help.bubble_help_window ) {
        return;
    }

    if ( m_bubble_help.bubble_help_window ) {
        // it would be more precise to check if the awindow is the
        // parent of the linked widget of the bubblewindow. But on
        // focus out it can be closed anyway.
        pushPostMsgHandlerCB( [this, w = m_bubble_help.bubble_help_window]() {
            // we have an own reference so it get destroyed
            // when leaving this function
            w->hide();
        });

        // reset shared_ptr, but reference still lives in
        // msg handler
        m_bubble_help.bubble_help_window.reset();
    }
}

void AGUIX::pushPostMsgHandlerCB( std::function< void(void) > f )
{
    m_post_msg_handler_cbs.push_back( f );
}

void AGUIX::executeAfterMsgHandler( std::function< void(void) > f )
{
    if ( msg_handler_active ) {
        pushPostMsgHandlerCB( f );
    } else {
        f();
    }
}

std::tuple< int, std::string, std::vector< std::string > > AGUIX::convertPasteDataToFileList( const unsigned char *data )
{
    std::tuple< int, std::string, std::vector< std::string > > res;

    std::vector< std::string > splitted_data;

    AGUIXUtils::split_string( splitted_data, (const char*)data, '\n' );

    if ( splitted_data.size() < 2 ) {
        std::get<0>( res ) = -EINVAL;
    } else {
        if ( splitted_data[0] != "copy" &&
             splitted_data[0] != "cut" ) {
            std::get<0>( res ) = -ENOTSUP;
        } else {
            std::get<1>( res ) = splitted_data[0];

            for ( auto it = splitted_data.begin() + 1;
                  it != splitted_data.end();
                  it++ ) {
                if ( AGUIXUtils::starts_with( *it, "file:///" ) ) {
                    std::string filename = std::string( *it, 7 );
                    if ( AGUIXUtils::ends_with( filename, "\r" ) ) {
                        filename.pop_back();
                    }
                    std::get<2>( res ).push_back( AGUIXUtils::unescape_uri( filename ) );
                }
            }
        }
    }

    return res;
}

AGUIX::copy_requestor_state &AGUIX::copy_and_paste_state::find_requestor( Window requestor )
{
    for ( auto &e : requestors ) {
        if ( e.requestor_window == requestor ) {
            return e;
        }
    }

    return not_found;
}

void AGUIX::copy_and_paste_state::remove_requestor( Window requestor )
{
    requestors.remove_if( [requestor]( auto &e ) {
        return e.requestor_window == requestor;
    } );
}

void AGUIX::copy_and_paste_state::cleanup_old_requestor( time_t now )
{
    requestors.remove_if( [now]( auto &e ) {
        if ( e.last_update == 0 ) return false;

        return now - e.last_update > 10;
    } );
}
