/* vi:set ts=8 sts=4 sw=4:
 *
 * VIM - Vi IMproved		by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

/*
 * Porting to KDE(2) was done by
 *
 *  (C) 2000 by Thomas Capricelli <orzel@freehackers.org>
 *
 *  Please visit http://freehackers.org/kvim for other vim- or
 *  kde-related coding.
 *
 *  $Id: gui_kde_widget.cc,v 1.55 2002/09/30 16:10:47 marchand Exp $
 *
 */

#include <assert.h>
#include <qpainter.h>
#include <qevent.h>
#include <qpushbutton.h>
#include <qscrollbar.h>
#include <qlayout.h>
#include <qclipboard.h>
#include <qdragobject.h>
#include <qstrlist.h>
#include <qmenubar.h>
#include <qtextcodec.h>
#if QT_VERSION>=300
#include <qptrlist.h>
#include <ktip.h>
#endif
#ifndef FEAT_GUI_OPIE
#include <kglobal.h>
#include <kconfig.h>
#include <kaboutapplication.h>
#include <dcopclient.h>
#include <kaboutkde.h>
#include <kbugreport.h>
#include <kurldrag.h>
#include <kmenubar.h>
#include <ktoolbar.h>
#include <kstandarddirs.h>
#endif
#include "gui_kde_widget.h"

extern "C" {
#include "version.h"
}

// Pixmap for dialog 
#ifdef FEAT_GUI_DIALOG
# include "../../pixmaps/alert.xpm"
# include "../../pixmaps/error.xpm"
# include "../../pixmaps/generic.xpm"
# include "../../pixmaps/info.xpm"
# include "../../pixmaps/quest.xpm"
#endif

/**
 * Keycodes recognized by vim.
 */
struct special_key {//{{{
    int qtkey;
    char_u code0;
    char_u code1;
} special_keys[] =
{
    { Qt::Key_Up,		'k', 'u' },
    { Qt::Key_Down,		'k', 'd' },
    { Qt::Key_Left,		'k', 'l' },
    { Qt::Key_Right,		'k', 'r' },
    { Qt::Key_F1,		'k', '1' },
    { Qt::Key_F2,		'k', '2' },
    { Qt::Key_F3,		'k', '3' },
    { Qt::Key_F4,		'k', '4' },
    { Qt::Key_F5,		'k', '5' },
    { Qt::Key_F6,		'k', '6' },
    { Qt::Key_F7,		'k', '7' },
    { Qt::Key_F8,		'k', '8' },
    { Qt::Key_F9,		'k', '9' },
    { Qt::Key_F10,		'k', ';' },
    { Qt::Key_F11,		'F', '1' },
    { Qt::Key_F12,		'F', '2' },
    { Qt::Key_F13,		'F', '3' },
    { Qt::Key_F14,		'F', '4' },
    { Qt::Key_F15,		'F', '5' },
    { Qt::Key_F16,		'F', '6' },
    { Qt::Key_F17,		'F', '7' },
    { Qt::Key_F18,		'F', '8' },
    { Qt::Key_F19,		'F', '9' },
    { Qt::Key_F20,		'F', 'A' },
    { Qt::Key_F21,		'F', 'B' },
    { Qt::Key_F22,		'F', 'C' },
    { Qt::Key_F23,		'F', 'D' },
    { Qt::Key_F24,		'F', 'E' },
    { Qt::Key_F25,		'F', 'F' },
    { Qt::Key_F26,		'F', 'G' },
    { Qt::Key_F27,		'F', 'H' },
    { Qt::Key_F28,		'F', 'I' },
    { Qt::Key_F29,		'F', 'J' },
    { Qt::Key_F30,		'F', 'K' },
    { Qt::Key_F31,		'F', 'L' },
    { Qt::Key_F32,		'F', 'M' },
    { Qt::Key_F33,		'F', 'N' },
    { Qt::Key_F34,		'F', 'O' },
    { Qt::Key_F35,		'F', 'P' },
    { Qt::Key_Help,		'%', '1' },
    //    { Qt::Key_Undo,		'&', '8' }, <= hmmm ?
    { Qt::Key_BackSpace,	'k', 'b' },
    { Qt::Key_Insert,		'k', 'I' },
    { Qt::Key_Delete,		'k', 'D' },
    { Qt::Key_Home,		'k', 'h' },
    { Qt::Key_End,		'@', '7' },
    { Qt::Key_Prior,		'k', 'P' },
    { Qt::Key_Next,		'k', 'N' },
    { Qt::Key_Print,		'%', '9' },

    { Qt::Key_Plus,	'K', '6'},
    { Qt::Key_Minus,	'K', '7'},
    { Qt::Key_Slash,	'K', '8'},
    { Qt::Key_multiply,	'K', '9'},
    { Qt::Key_Enter,	'K', 'A'},
    { Qt::Key_Period,	'K', 'B'},

    { Qt::Key_0,	'K', 'C'},
    { Qt::Key_1,	'K', 'D'},
    { Qt::Key_2,	'K', 'E'},
    { Qt::Key_3,	'K', 'F'},
    { Qt::Key_4,	'K', 'G'},
    { Qt::Key_5,	'K', 'H'},
    { Qt::Key_6,	'K', 'I'},
    { Qt::Key_7,	'K', 'J'},
    { Qt::Key_8,	'K', 'K'},
    { Qt::Key_9,	'K', 'L'},
    /* End of list marker: */
    { 0, 0, 0 }
};//}}}

#ifdef FEAT_CLIENTSERVER                   
typedef int (*QX11EventFilter) (XEvent*);
extern QX11EventFilter qt_set_x11_event_filter (QX11EventFilter filter);
static QX11EventFilter oldFilter = 0;
static int kvim_x11_event_filter( XEvent* e);
#endif
void gui_keypress(QKeyEvent *e);

/*
 * Return OK if the key with the termcap name "name" is supported.
 */
    int
gui_mch_haskey(char_u * name)//{{{
{
    for (int i=0; special_keys[i].qtkey != 0; i++)
	if (name[0] == special_keys[i].code0 && 
		name[1] == special_keys[i].code1)
	    return OK;
    return FAIL;
}//}}}

/*
 * custom Frame for drawing ...
 */
void VimWidget::paintEvent( QPaintEvent *e)//{{{
{
    QRect r = e->rect();
    gui_redraw(r.x(), r.y(), r.width(), r.height() );
}//}}}

void VimWidget::draw_string(int x, int y, QString s, int len, int flags)//{{{
{
    gui.current_font->setBold( flags & DRAW_BOLD );
    gui.current_font->setUnderline( flags & DRAW_UNDERL );
    gui.current_font->setItalic(flags & DRAW_ITALIC);
#ifdef FEAT_GUI_OPIE
    painter=new QPainter(this);
#endif
    painter->setBackgroundMode( flags & DRAW_TRANSP ? Qt::TransparentMode : Qt::OpaqueMode);
    painter->setFont( *(gui.current_font) );
    painter->drawText( x, y, s, len);
}//}}}

void VimWidget::mousePressEvent(QMouseEvent *event)//{{{
{
    int button=0;
    int modifiers=0;
    ButtonState state = event->state();	
    ButtonState buttons = event->button();

    //Look at button states
    if(buttons & QMouseEvent::LeftButton) {
	button|=MOUSE_LEFT;
    }
    if(buttons & QMouseEvent::RightButton) {
	button|=MOUSE_RIGHT;
    }
    if(buttons & QMouseEvent::MidButton) {
	button|=MOUSE_MIDDLE;
    }
    //Look for keyboard modifiers
    if(state & QMouseEvent::ShiftButton) {
	modifiers|=MOUSE_SHIFT;
    }
    if(state & QMouseEvent::ControlButton){
	modifiers|=MOUSE_CTRL;
    }
    if(state & QMouseEvent::AltButton){
	modifiers|=MOUSE_ALT;
    }
    gui_send_mouse_event(button,event->x(),event->y(),FALSE,modifiers);	
#if QT_VERSION>=300
    QByteArray params;
    QDataStream stream(params, IO_WriteOnly);
    stream << kapp->dcopClient()->appId() << button << modifiers << gui.row << gui.col;
    kapp->dcopClient()->emitDCOPSignal("mousePEvent(QCString,int,int,int,int)", params);
#endif
}//}}}

#if !defined(FEAT_GUI_OPIE) && defined(FEAT_SESSION)
void VimMainWindow::saveGlobalProperties (KConfig *conf) 
{
    //we write a mksession file to a file written in the user's ~/.kde/share/config/
    //the name of the file in saved in 'conf'
    //when restoring app, we source this file
#if 0 //disabled for release
    QString filename = KGlobal::dirs()->localkdedir() + KGlobal::dirs()->kde_default("config") + kapp->randomString(10);
    QString cmd("mksession ");
    cmd+=filename;
    do_cmdline_cmd((char_u*)cmd.latin1());
    conf->writePathEntry("sessionfile", filename);
    conf->sync();
#endif
}

void VimMainWindow::readGlobalProperties (KConfig *conf) 
{
#if 0
    QString filename = conf->readPathEntry("sessionfile");
    if (filename.isNull()) return;
    QString cmd("source ");
    cmd+=filename;
    do_cmdline_cmd((char_u*)cmd.latin1());
#endif
}
#endif

void VimMainWindow::wheelEvent (QWheelEvent *event)//{{{
{
    ButtonState state = event->state();
    int button=0;
    int modifiers=0;

    if (event->delta()>0) 
	button|=MOUSE_4;
    else button|=MOUSE_5;

    if(state & ShiftButton)	
	modifiers|=MOUSE_SHIFT;
    if(state & ControlButton)
	modifiers|=MOUSE_CTRL;
    if(state & AltButton)
	modifiers|=MOUSE_ALT;

    gui_send_mouse_event(button,event->x(),event->y(),FALSE,modifiers);
#if QT_VERSION>=300
    QByteArray params;
    QDataStream stream(params, IO_WriteOnly);
    stream << kapp->dcopClient()->appId() << button << modifiers << gui.row << gui.col;
    kapp->dcopClient()->emitDCOPSignal("mouseWhlEvent(QCString, int, int,int,int)", params);
#endif
}//}}}

void VimWidget::mouseDoubleClickEvent(QMouseEvent *event)//{{{
{
    ButtonState state = event->state();	
    ButtonState buttons = event->button();	
    int modifiers=0;
    int button=0;

    //Look at button states
    if(buttons & LeftButton)
	button|=MOUSE_LEFT;
    if(buttons & RightButton)
	button|=MOUSE_RIGHT;
    if(buttons & MidButton)
	button|=MOUSE_MIDDLE;

    //Look for keyboard modifiers
    if(state & ShiftButton)	
	modifiers|=MOUSE_SHIFT;
    if(state & ControlButton)
	modifiers|=MOUSE_CTRL;
    if(state & AltButton)
	modifiers|=MOUSE_ALT;

    gui_send_mouse_event(button,event->x(),event->y(),TRUE,modifiers);	
#if QT_VERSION>=300
    QByteArray params;
    QDataStream stream(params, IO_WriteOnly);
    stream << kapp->dcopClient()->appId() << button << modifiers << gui.row << gui.col;
    kapp->dcopClient()->emitDCOPSignal("mouseDblClickEvent(QCString, int, int,int,int)", params);
#endif
}//}}}

void VimWidget::mouseMoveEvent(QMouseEvent *event){//{{{
    ButtonState state = event->state();	
    int modifiers=0;
    int button=0;

    gui_mch_mousehide(FALSE);

    //Look at button states
    //warning: we use state here, this is important !
    if(state & QMouseEvent::LeftButton || state & QMouseEvent::RightButton || state & QMouseEvent::MidButton)
	button|=MOUSE_DRAG;

    //Look for keyboard modifiers
    if(state & ShiftButton)	
	modifiers|=MOUSE_SHIFT;
    if(state & ControlButton)
	modifiers|=MOUSE_CTRL;
    if(state & AltButton)
	modifiers|=MOUSE_ALT;
    if (button!=MOUSE_DRAG)
	gui_mouse_moved(event->x(),event->y());
    else
	gui_send_mouse_event(MOUSE_DRAG,event->x(),event->y(),FALSE,modifiers);	
}//}}}

void VimWidget::mouseReleaseEvent(QMouseEvent *event)//{{{
{
    ButtonState state = event->state();	
    int modifiers=0;

    //Look for keyboard modifiers
    if(state & ShiftButton)	
	modifiers|=MOUSE_SHIFT;
    if(state & ControlButton)
	modifiers|=MOUSE_CTRL;
    if(state & AltButton)
	modifiers|=MOUSE_ALT;

    gui_send_mouse_event(MOUSE_RELEASE,event->x(),event->y(),FALSE,modifiers);
}//}}}

/*
 *  The main widget (everything but toolbar/menubar)
 */
    VimWidget::VimWidget( QWidget *parent, const char *name, WFlags f )//{{{
:QWidget(parent, name, f)
#ifndef FEAT_GUI_OPIE
    ,DCOPObject("KVim")
#endif
{
    //to be able to show/hide the cursor when moving the mouse
    setMouseTracking(true); 
    painter=new QPainter(this);
    
    setKeyCompression(true);
    setFocusPolicy( QWidget::StrongFocus );
    setAcceptDrops(TRUE); // DND
    blink_state = BLINK_NONE;
    blink_on_time = 700;
    blink_off_time = 400;
    blink_wait_time = 250;
    connect( &blink_timer, SIGNAL( timeout() ), SLOT( blink_cursor() ));
    connect( &wait_timer, SIGNAL( timeout() ), SLOT ( wait_timeout() ));
}//}}}

void VimWidget::execNormal(QString command)//{{{
{
    QString cmd("execute 'normal ");
    cmd+=command;
    cmd+="'";
    QCString unistring = vmw->codec->fromUnicode(cmd);
    do_cmdline_cmd((char_u *)(const char*)unistring);
    gui_update_screen();
}//}}}

void VimWidget::execInsert(QString command)//{{{
{
    QString cmd("execute 'normal i");
    cmd+=command;
    cmd+="'";
    QCString unistring = vmw->codec->fromUnicode(cmd);
    do_cmdline_cmd((char_u *)(const char*)unistring);
    gui_update_screen();
}//}}}

void VimWidget::execRaw(QString command)//{{{
{
    QString cmd("execute '");
    cmd+=command;
    cmd+="'";
    QCString unistring = vmw->codec->fromUnicode(cmd);
    do_cmdline_cmd((char_u *)(const char*)unistring);
    gui_update_screen();
}//}}}

void VimWidget::execCmd(QString command)//{{{
{
    QCString unistring = vmw->codec->fromUnicode(command);
    do_cmdline_cmd((char_u *)(const char*)unistring);
    gui_update_screen();
}//}}}

QString VimWidget::eval(QString expr)//{{{
{
#ifdef FEAT_EVAL
    QCString unistring = vmw->codec->fromUnicode(expr);
    QString val((const char *)eval_to_string((char_u *)(const char*)unistring,NULL));
    return val;
#else
    return QString::null;
#endif
}//}}}

void VimWidget::wait(long wtime)//{{{
{
    if ( wait_timer.isActive() ) wait_timer.stop();
    wait_done = false;
    wait_timer.start( wtime, true);
}//}}}

void VimWidget::wait_timeout() //{{{
{
    wait_done = true;
}//}}}

#ifndef FEAT_GUI_OPIE
void VimWidget::dragEnterEvent (QDragEnterEvent *e)//{{{
{
#if (defined(FEAT_WINDOWS) && defined(HAVE_DROP_FILE)) || defined(PROTO)
    e->accept(QUriDrag::canDecode(e));
#else
    e->ignore();
#endif
}//}}}

void VimWidget::dropEvent (QDropEvent *e) // {{{
{
#if (defined(FEAT_WINDOWS) && defined(HAVE_DROP_FILE)) || defined(PROTO)
    QStrList  urls;

    char_u	**fnames;
    int		redo_dirs = FALSE;
    int		i;
    int		n;
    int		nfiles;
    int		url = FALSE;

    /* Count how many items there may be and normalize delimiters. */

    if (QUriDrag::decode(e, urls)) {
	n = urls.count();
        fnames = (char_u **)lalloc((n+1) * sizeof(char_u *), TRUE);
	nfiles = 0;
#if QT_VERSION>=300
	QPtrListIterator<char> it(urls);
	for( ; it.current(); ++it ) {
	    KURL u(*it);
#else
	    for (i=0;i<urls.count();++i) {
		KURL u(urls.at(i));
#endif
		if ( !u.isLocalFile() )
		    url = TRUE;
		else {
		    fnames[nfiles] = (char_u *)strdup((const char *)u.path());
		    ++nfiles;
		}
	    }
	    /* Real files (i.e. not http and not ftp) */
	    if (url == FALSE)
	    {
		if (nfiles == 1)
		{
		    if (mch_isdir(fnames[0]))
		    {
			/* Handle dropping a directory on Vim. */
			if (mch_chdir((char *)fnames[0]) == 0)
			{
			    free(fnames[0]);
			    fnames[0] = NULL;
			    redo_dirs = TRUE;
			}
		    }
		} else {
		    /* Ignore any directories */
		    for (i = 0; i < nfiles; ++i)
		    {
			if (mch_isdir(fnames[i]))
			{
			    vim_free(fnames[i]);
			    fnames[i] = NULL;
			}
		    }
		}

		if (0) 
		{
		    /* Shift held down, change to first file's directory */
		    if (fnames[0] != NULL && vim_chdirfile(fnames[0]) == OK)
			redo_dirs = TRUE;
		} else {
		    char_u	dirname[MAXPATHL];
		    char_u	*s;
		    if (mch_dirname(dirname, MAXPATHL) == OK)
			for (i = 0; i < nfiles; ++i)
			    if (fnames[i] != NULL)
			    {
				s = shorten_fname(fnames[i], dirname);
				if (s != NULL && (s = vim_strsave(s)) != NULL)
				{
				    vim_free(fnames[i]);
				    fnames[i] = s;
				}
			    }
		}
	    }

	    /* Handle the drop, :edit or :split to get to the file */
	    handle_drop(nfiles, fnames, FALSE);

	    if (redo_dirs)
		shorten_fnames(TRUE);
	}

	/* Update the screen display */
	update_screen(NOT_VALID);
#ifdef FEAT_MENU
	gui_update_menus(0);
#endif
	setcursor();
	out_flush();
	gui_update_cursor(FALSE, FALSE);
	gui_mch_flush();
#endif
} // }}}
#endif //FEAT_GUI_OPIE

void VimWidget::keyPressEvent( QKeyEvent *e ) // {{{
{
    gui_keypress(e);
} // }}}

void gui_keypress(QKeyEvent *e) { // {{{
    int key = (int)e->key();
    int modifiers = 0,i;
    uchar string[256],string2[256];
    uchar *s,*d;

    QCString unistring = vmw->codec->fromUnicode(e->text());
    if (unistring.length()>0) 
	strncpy((char*)string, (const char*)unistring,unistring.length());
    string[unistring.length()] = 0;
    int len=unistring.length();

    // ignore certain keys 
    if (key == Qt::Key_Shift || key == Qt::Key_Alt || key == Qt::Key_Control || key == Qt::Key_Meta 
	    || key == Qt::Key_CapsLock || key == Qt::Key_NumLock || key == Qt::Key_ScrollLock ) {
	e->ignore();
	return;
    }

#ifdef FEAT_MBYTE
    if (input_conv.vc_type != CONV_NONE)
    {
	mch_memmove(string2, string, len);
	len = convert_input(string2, len, sizeof(string2));
	s = string2;
    }
    else
#endif
	s = string;
    d = string;
    for (i = 0; i < len; ++i)
    {
	*d++ = s[i];
	if (d[-1] == CSI && d + 2 < string + sizeof(string))
	{
	    /* Turn CSI into K_CSI. */
	    *d++ = KS_EXTRA;
	    *d++ = (int)KE_CSI;
	}
    }
    len = d - string;


    // change shift-tab (backtab) into S_TAB
    if ( key == Qt::Key_BackTab && e->state() & Qt::ShiftButton) {
	key = Qt::Key_Tab;
    }

    // Change C-@ and C-2 in NUL ? Gtk does this
    if ( (key == Qt::Key_2 || key == Qt::Key_At) 
	    && e->state() & Qt::ControlButton ) {
	string[0] = NUL;
	len = 1;
    } 
    else if (len == 0 && (key == Qt::Key_Space || key == Qt::Key_Tab))
    {
	/* When there are modifiers, these keys get zero length; we need the
	 * original key here to be able to add a modifier below. */
	string[0] = (key & 0xff);
	len = 1;
    }
    /* Check for Alt/Meta key (Mod1Mask), but not for a BS, DEL or character
     * that already has the 8th bit set.
     * Don't do this for <S-M-Tab>, that should become K_S_TAB with ALT. */
    if (len == 1
	    && (e->state() & Qt::AltButton)
	    && !(key == Qt::Key_BackSpace || key == Qt::Key_Delete)
	    && (string[0] & 0x80) == 0
	    && !(key == Qt::Key_Tab && (e->state() & Qt::ShiftButton)))
    {
	string[0] |= 0x80;
#ifdef FEAT_MBYTE
	if (enc_utf8) // convert to utf-8 
	{
	    string[1] = string[0] & 0xbf;
	    string[0] = ((unsigned)string[0] >> 6) + 0xc0;
	    if (string[1] == CSI)
	    {
		string[2] = KS_EXTRA;
		string[3] = (int)KE_CSI;
		len = 4;
	    }
	    else
		len = 2;
	}
#endif
    }

    /* Check for special keys, making sure BS and DEL are recognised. */
    if (len == 0 || key == Qt::Key_BackSpace || key == Qt::Key_Delete) 
    {
	for (i = 0; special_keys[i].qtkey != 0; i++)
	{
	    if (special_keys[i].qtkey == key) 
	    {
		string[0] = CSI;
		string[1] = special_keys[i].code0;
		string[2] = special_keys[i].code1;
		len = -3;

		break;
	    }
	}
    }

    if (len == 0)   {
	//no need to dump that, that's a QT problem, we can't do anything
	//dbf("Unrecognised Key : %X %s", key, e->text().latin1());
	e->ignore();
	return;
    }


    /* Special keys (and a few others) may have modifiers */
    if (len == -3 || key == Qt::Key_Space || key == Qt::Key_Tab ||
	    key == Qt::Key_Return || key == Qt::Key_Enter ||
	    key == Qt::Key_Escape) {

	modifiers = 0;
	if (e->state() & Qt::ShiftButton) modifiers |= MOD_MASK_SHIFT;
	if (e->state() & Qt::ControlButton) modifiers |= MOD_MASK_CTRL;
	if (e->state() & Qt::AltButton) modifiers |= MOD_MASK_ALT;

	/*
	 * For some keys a shift modifier is translated into another key
	 * code. Do we need to handle the case where len != 1 and
	 * string[0] != CSI?
	 */
	if (len == -3)
	    key = TO_SPECIAL(string[1], string[2]);
	else
	    key = string[0];

	key = simplify_key(key, &modifiers);
	if (key == CSI) key=K_CSI;

	if (IS_SPECIAL(key)) {
	    string[0] = CSI;
	    string[1] = K_SECOND(key);
	    string[2] = K_THIRD(key);
	    len = 3;
	} else {
	    string[0] = key;
	    len = 1;
	}


	if (modifiers!=0) {
	    uchar string2[10];
	    string2[0] = CSI;
	    string2[1] = KS_MODIFIER;
	    string2[2] = modifiers;
	    add_to_input_buf(string2, 3);
	}

    } /* special keys */

    if (len == 1 && ((string[0] == Ctrl_C && ctrl_c_interrupts)
		|| (string[0] == intr_char && intr_char != Ctrl_C)))
    {
	trash_input_buf();
	got_int = TRUE;
    }

    add_to_input_buf(string, len);
    if (p_mh) {
	gui_mch_mousehide(TRUE);
    }
    //DCOP Embedding stuff
    //if we are here then the user has type something in the window, thus we can easily imagine that :
    // 1 - text has changed (emit textChanged())
    // 2 - characters were interactively inserted (emit charactersInteractivelyInserted())
    // 3 - cursor position has changed ( emit cursorPositionChanged() )
    // 4 - selection has changed ? dunno yet //XXX
    // 5 - undo changed too ? (each character typed in makes the undo changes anyway)
    // conclusion : this makes a lot of things to send to the vim kpart, maybe too much
    // for now i'll just send : keyboardEvent to the kpart with the event string as parameter, 
    // with current current position
    // i'll do the same for mouseEvents
#if QT_VERSION>=300
    QByteArray params;
    QDataStream stream(params, IO_WriteOnly);
    stream << kapp->dcopClient()->appId() << unistring << gui.row << gui.col;
    kapp->dcopClient()->emitDCOPSignal("keyboardEvent(QCString, QCString,int,int)", params);
#endif
    e->ignore();
} // }}}

#ifdef FEAT_CLIENTSERVER
void VimWidget::serverActivate(WId id) //{{{
{
    if (serverName == NULL && serverDelayedStartName != NULL) {
	commWindow = id;
	(void)serverRegisterName(qt_xdisplay(), serverDelayedStartName);
    } else {
	serverChangeRegisteredWindow( qt_xdisplay(), id );
    }
}//}}}
#endif

#ifdef FEAT_XIM
void VimWidget::imStartEvent(QIMEvent *e) {
    e->accept();
}

void VimWidget::imEndEvent(QIMEvent *e) {
    uchar string[256];

    QCString unistring = vmw->codec->fromUnicode(e->text());
    if (unistring.length()>0) 
	strncpy((char*)string, (const char*)unistring,unistring.length());
    string[unistring.length()] = 0;
    int len=unistring.length();

    add_to_input_buf(string, len);
    e->accept();
}

void VimWidget::imComposeEvent(QIMEvent *e) {
    //i should do something here, displaying the text somewhere ... (status area ?)
    e->accept();
}
#endif


void VimMainWindow::lock() 
{
    locked=true;
}

void VimMainWindow::unlock()
{
    locked=false;
}

bool VimMainWindow::isLocked()
{
    return locked;
}

// ->resize VimWidget if not locked
//
void VimMainWindow::resizeEvent ( QResizeEvent *e ) //{{{
{
    //remove toolbar and menubar height
    int height = e->size().height();
    int width = e->size().width();

    if (vmw->menuBar()->isVisible() && vmw->menuBar()->isEnabled() 
#if QT_VERSION>=300
	    && !vmw->menuBar()->isTopLevelMenu()
#endif
	    )
	height -= vmw->menuBar()->height();
#ifdef FEAT_TOOLBAR
    if (vmw->toolBar()->isVisible() && vmw->toolBar()->isEnabled() &&
	    (vmw->toolBar()->barPos()==KToolBar::Top ||
	     vmw->toolBar()->barPos()==KToolBar::Bottom))
	height -= vmw->toolBar()->height();

    if (vmw->toolBar()->isVisible() && vmw->toolBar()->isEnabled() &&
	    (vmw->toolBar()->barPos()==KToolBar::Left ||
	     vmw->toolBar()->barPos()==KToolBar::Right))
	width -= vmw->toolBar()->width();
#endif
    height = ( ((int)(height/gui.char_height))*gui.char_height );
    if (!vmw->isLocked()) gui_resize_shell(width,height);
}//}}}

void VimWidget::focusInEvent( QFocusEvent * fe ) // {{{
{
    gui_focus_change(true);

    if (blink_state == BLINK_NONE)
	gui_mch_start_blink();
} // }}}

void VimWidget::focusOutEvent( QFocusEvent * fe )//{{{
{
    gui_focus_change(false);

    if (blink_state != BLINK_NONE)
	gui_mch_stop_blink();
}//}}}

void VimWidget::set_blink_time( long wait, long on, long off)//{{{
{
    blink_wait_time = wait;
    blink_on_time = on;
    blink_off_time = off;
}//}}}

void VimWidget::start_cursor_blinking()//{{{
{
    if (blink_timer.isActive()) blink_timer.stop();

    /* Only switch blinking on if none of the times is zero */
    if (blink_wait_time && blink_on_time && blink_off_time && gui.in_focus) {
	blink_state = BLINK_ON;
	gui_update_cursor(TRUE, FALSE);
	// The first blink appears after wait_time
	blink_timer.start( blink_wait_time, true);
    }
}//}}}

void VimWidget::blink_cursor()//{{{
{
    if (blink_state == BLINK_ON) {
	// set cursor off
	gui_undraw_cursor();
	blink_state = BLINK_OFF;
	blink_timer.start( blink_off_time, true);
    } else {
	// set cursor on
	gui_update_cursor(TRUE, FALSE);
	blink_state = BLINK_ON;
	blink_timer.start( blink_on_time, true);
    }
}//}}}

void VimWidget::stop_cursor_blinking()//{{{
{
    if (blink_timer.isActive()) blink_timer.stop();

    if (blink_state == BLINK_OFF)
	gui_update_cursor(TRUE, FALSE);

    blink_state = BLINK_NONE;
}//}}}

void VimWidget::flash()//{{{
{
    QPainter p(this);

    p.setRasterOp(Qt::XorROP);
    p.fillRect(geometry(),QColor(0xFF,0xFF,0xFF));
    p.flush();
    //FIXME: Make this a little smarter. Maybe add a timer or something
    usleep(19000);
    p.fillRect(geometry(),QColor(0xFF,0xFF,0xFF));
    p.flush();
    p.end();
}//}}}


/*
 *  The main Window
 */
    VimMainWindow::VimMainWindow ( const char *name , WFlags f)//{{{
#ifndef FEAT_GUI_OPIE
:KMainWindow(0L, name,f)
#else
:QMainWindow(0L, name,f)
#endif
{
#ifdef FEAT_CLIENTSERVER
    oldFilter = qt_set_x11_event_filter( kvim_x11_event_filter );
#endif
    if (echo_wid_arg== 1) {
	fprintf(stderr, "WID: %d\n", winId());
	fflush(stderr);
    }

    w = new VimWidget(this, "main vim widget");
    gui.w = w;
    setFocusProxy(w);
    w->setFocus();
    have_tearoff=0;

#ifndef FEAT_GUI_OPIE
    finddlg=new KEdFind (this,0,false);
    repldlg=new KEdReplace (this,0,false);
    QObject::connect( finddlg, SIGNAL(search()), this, SLOT(slotSearch()) );
    QObject::connect( repldlg, SIGNAL(find()), this, SLOT(slotFind()) );
    QObject::connect( repldlg, SIGNAL(replace()), this, SLOT(slotReplace()) );
    QObject::connect( repldlg, SIGNAL(replaceAll()), this, SLOT(slotReplaceAll()) );
#endif

    /* we use a hack based on this assertion, so check it.. */
    assert(sizeof(void*)==sizeof(int));
#ifdef FEAT_TOOLBAR
    connect(toolBar(), SIGNAL(clicked(int)), this, SLOT(menu_activated(int)));
#endif
#ifdef FEAT_CLIENTSERVER
    w->serverActivate(winId());

    if (serverName!=NULL)
        kapp->dcopClient()->registerAs(QCString((const char*)serverName),false);
    else if (serverDelayedStartName!=NULL)
        kapp->dcopClient()->registerAs(QCString((const char*)serverDelayedStartName),false);
    else if (argServerName!=NULL)
        kapp->dcopClient()->registerAs(argServerName->utf8(),false);
#else
#ifndef FEAT_GUI_OPIE
    if (argServerName!=NULL)
        kapp->dcopClient()->registerAs(argServerName->utf8(),false);
#endif
#endif

}//{{{

bool VimMainWindow::queryClose()//{{{
{
    gui_shell_closed();
}//}}}

bool VimMainWindow::queryExit()//{{{
{
    return true;
}//}}}

void VimMainWindow::menu_activated(int dx)//{{{
{
#ifdef FEAT_MENU
    if (!dx) { 	// tearoff
	return; 
    }
    gui_mch_set_foreground();
    gui_menu_cb((VimMenu *) dx);
#endif
}//}}}


void VimMainWindow::clipboard_selection_update(){//{{{
#ifndef FEAT_GUI_OPIE
    if(kapp->clipboard()->ownsSelection()) {
	clip_own_selection(&clip_star);
    } else {
	clip_lose_selection(&clip_star);
    }
#endif
}//}}}

void VimMainWindow::clipboard_data_update(){//{{{
#ifndef FEAT_GUI_OPIE
#if QT_VERSION>=300
    if (kapp->clipboard()->ownsClipboard()) {
	clip_own_selection(&clip_plus);
    } else {
	clip_lose_selection(&clip_plus);
    }
#else
    if (kapp->clipboard()->ownsSelection()) {
	clip_own_selection(&clip_star);
    } else {
	clip_lose_selection(&clip_star);
    }
#endif
#endif
}//}}}

void VimMainWindow::slotSearch()//{{{
{
#ifndef FEAT_GUI_OPIE
    QString 	find_text;
    bool	direction_down = TRUE;
    bool	casesensitive = TRUE;
    int 	flags = FRD_FINDNEXT;

    find_text = finddlg->getText();
    direction_down = !(finddlg->get_direction());
    casesensitive = finddlg->case_sensitive();
    //    if (casesensitive) find_text = "\\C" + find_text;
    //    else find_text = "\\c" + find_text;
    if (casesensitive) flags|=FRD_MATCH_CASE;
    QCString unistring = vmw->codec->fromUnicode(find_text);
    gui_do_findrepl(flags, (char_u *)(const char *)unistring, NULL,(int)direction_down);
#endif
}//}}}

void VimMainWindow::slotFind()//{{{
{
#ifndef FEAT_GUI_OPIE
    QString	find_text;
    bool 	direction_down=TRUE;
    bool	casesensitive = TRUE;
    int 	flags = FRD_R_FINDNEXT;

    find_text=repldlg->getText();
    direction_down = !(repldlg->get_direction());
    casesensitive = repldlg->case_sensitive();
    //    if (casesensitive) find_text = "\\C" + find_text;
    //    else find_text = "\\c" + find_text;
    if (casesensitive) flags|=FRD_MATCH_CASE;

    QCString unistring = vmw->codec->fromUnicode(find_text);
    gui_do_findrepl(flags, (char_u *)(const char *)unistring, NULL,(int)direction_down);
#endif
}//}}}

void VimMainWindow::slotReplace()//{{{
{
#ifndef FEAT_GUI_OPIE
    QString	find_text;
    QString     repl_text;
    bool 	direction_down=TRUE;
    bool	casesensitive = TRUE;
    int 	flags = FRD_REPLACE;

    find_text=repldlg->getText();
    repl_text=repldlg->getReplaceText();
    direction_down = !(repldlg->get_direction());
    //if (casesensitive) find_text = "\\C" + find_text;
    //else find_text = "\\c" + find_text;
    if (casesensitive) flags|=FRD_MATCH_CASE;

    QCString unistring = vmw->codec->fromUnicode(find_text);
    QCString unistring2 = vmw->codec->fromUnicode(repl_text);
    gui_do_findrepl(flags, (char_u *)(const char *)unistring,(char_u *)(const char*)unistring2,(int)direction_down);
#endif
}//}}}

void VimMainWindow::slotReplaceAll()//{{{
{
#ifndef FEAT_GUI_OPIE
    QString	find_text;
    QString     repl_text;
    bool 	direction_down=TRUE;
    bool	casesensitive = TRUE;
    int 	flags = FRD_REPLACEALL;

    find_text=repldlg->getText();
    repl_text=repldlg->getReplaceText();
    direction_down = !(repldlg->get_direction());
    casesensitive = repldlg->case_sensitive();
    //    if (casesensitive) find_text = "\\C" + find_text;
    //    else find_text = "\\c" + find_text;
    if (casesensitive) flags|=FRD_MATCH_CASE;
    QCString unistring = vmw->codec->fromUnicode(find_text);
    QCString unistring2 = vmw->codec->fromUnicode(repl_text);
    gui_do_findrepl(flags, (char_u *)(const char *)unistring,(char_u *)(const char*)unistring2,(int)direction_down);
#endif

}//}}}

void VimMainWindow::showAboutKDE()
{
#ifndef FEAT_GUI_OPIE
    KAboutKDE *kde = new KAboutKDE(this);
    kde->show();
#endif
}

void VimMainWindow::showAboutApplication()//{{{
{
#ifndef FEAT_GUI_OPIE
    KAboutData *aboutData = new KAboutData (
	    "kvim"
	    , I18N_NOOP("KVim")
	    , VIM_VERSION_SHORT
	    , I18N_NOOP("Vim in a KDE interface")
	    , 0
	    , "(c) Vim Team, \":help credits\" for more infos.\nType \":help iccf\" to see how you can help the children in Uganda"
	    , 0l
	    , "http://freehackers.org/kvim"
	    , "kvim-dev@freenux.org"
	    );

    aboutData->addAuthor("Bram Moolenaar",
	    I18N_NOOP("Main vim author"),
	    "Bram@vim.org",
	    "http://www.vim.org/");
    aboutData->addAuthor("Thomas Capricelli",
	    I18N_NOOP("KDE porting"),
	    "orzel@freehackers.org",
	    "http://orzel.freehackers.org");
    aboutData->addAuthor("Philippe Fremy",
	    I18N_NOOP("KDE porting"),
	    "pfremy@chez.com",
	    "http://www.freehackers.org/kvim");
    aboutData->addAuthor("Mark Westcott",
	    I18N_NOOP("Qtopia porting, maintainer of the Qtopia part"),
	    "mark@houseoffish.org",
	    "http://houseoffish.org");
    aboutData->addAuthor("Mickael Marchand",
	    I18N_NOOP("KDE porting, maintainer"),
	    "marchand@kde.org",
	    "http://freenux.org");
    aboutData->addAuthor("Many other people",
	    I18N_NOOP("type :help credits for more infos")
	    );
    aboutData->addCredit("Vince Negri",
	    I18N_NOOP("Antialiasing support, Color fixes"),
	    "vnegri@asl-electronics.co.uk");
    aboutData->addCredit("Malte Starostik",
	    I18N_NOOP("Patch for performance improvement"),
	    "malte@kde.org");
    aboutData->addCredit("Mark Stosberg",
	    I18N_NOOP("Provided a FreeBSD box to debug KVim on BSD"),
	    "mark@summersault.com"
	    );
    aboutData->addCredit("Henrik Skott",
	    I18N_NOOP("Font patch when KDE not configured"),
	    "henrik.skott@hem.utfors.se"
	    );
    aboutData->addCredit("Kailash Sethuraman",
	    I18N_NOOP("NetBSD configure/compilation fixes")
	    );
    aboutData->setLicenseText(
"KVim as an extension of Vim follows Vim license : \n\
Vim is Charityware.  You can use and copy it as much as you like, but you are\n\
encouraged to make a donation to orphans in Uganda.  Please read the file\n\
runtime/doc/uganda.txt for details.\n\
\n\
There are no restrictions on distributing an unmodified copy of Vim.  Parts of\n\
Vim may also be distributed, but this text must always be included.  You are\n\
allowed to include executables that you made from the unmodified Vim sources,\n\
your own usage examples and Vim scripts.\n\
\n\
If you distribute a modified version of Vim, you are encouraged to send the\n\
maintainer a copy, including the source code.  Or make it available to the\n\
maintainer through ftp; let him know where it can be found.  If the number of\n\
changes is small (e.g., a modified Makefile) e-mailing the diffs will do.\n\
When the maintainer asks for it (in any way) you must make your changes,\n\
including source code, available to him.\n\
\n\
The maintainer reserves the right to include any changes in the official\n\
version of Vim.  This is negotiable.  You are not allowed to distribute a\n\
modified version of Vim when you are not willing to make the source code\n\
available to the maintainer.\n\
\n\
The current maintainer is Bram Moolenaar <Bram@vim.org>.  If this changes, it\n\
will be announced in appropriate places (most likely www.vim.org and\n\
comp.editors).  When it is completely impossible to contact the maintainer,\n\
the obligation to send him modified source code ceases.\n\
\n\
It is not allowed to remove these restrictions from the distribution of the\n\
Vim sources or parts of it.  These restrictions may also be used for previous\n\
Vim releases instead of the text that was included with it.");

    KAboutApplication *about = new KAboutApplication(aboutData);
    about->show();
#endif
}//}}}

void VimMainWindow::showTipOfTheDay() {
#if QT_VERSION>=300
    KTipDialog::showTip (vmw,QString::null,true);
#endif
}

void VimMainWindow::buffersToolbar() {

}

void VimMainWindow::showBugReport() {
#ifndef FEAT_GUI_OPIE
    KBugReport *bug= new KBugReport(this,true);
    bug->show();
#endif
}
/*
 *   Vim Dialog
 *
 * Returns:
 *  0: Cancel
 *  1- : nb of the pressed button
 */

VimDialog::VimDialog (int type,		/* type of dialog *///{{{
	char_u * title,		/* title of dialog */
	char_u * message,	/* message text */
	char_u * buttons,	/* names of buttons */
	int def_but,		/* default button */
	char_u *textfield )	/* input field */
:QDialog(vmw, "vim generic dialog", true), // true is for "modal"
    mapper(this, "dialog signal mapper")
{
    /*
     * Create Icon
     */
    char ** icon_data;
    switch (type) {
	case VIM_GENERIC:
	    icon_data = generic_xpm;
	    break;
	case VIM_ERROR:
	    icon_data = error_xpm;
	    break;
	case VIM_WARNING:
	    icon_data = alert_xpm;
	    break;
	case VIM_INFO:
	    icon_data = info_xpm;
	    break;
	case VIM_QUESTION:
	    icon_data = quest_xpm;
	    break;
	default:
	    icon_data = generic_xpm;
    };                                                                                                          
    QLabel * icon = new QLabel( this );
    icon->setPixmap( QPixmap( (const char **) icon_data ) );
    icon->setFixedSize( icon->sizeHint() );

    QLabel * text = new QLabel( (const char *)message, this );
    text->setAlignment( AlignHCenter | AlignVCenter | ExpandTabs );

    QStringList buttonText = QStringList::split( DLG_BUTTON_SEP, (char *) buttons);
    int butNb = buttonText.count();

    /*
     *  Layout
     */

    QVBoxLayout * vly = new QVBoxLayout( this, 5, 5 );
    QHBoxLayout * hly1 = new QHBoxLayout( vly, 5);
    hly1->addWidget( icon );
    hly1->addWidget( text );
    QHBoxLayout * hly3 = new QHBoxLayout ( vly , 5);
    if (textfield!=NULL) {
	entry = new QLineEdit((const char *)textfield,this);
	entry->setText((const char *)textfield);
	hly3->addWidget( entry );
	ret=textfield;
    } else entry=NULL;

    QHBoxLayout * hly2 = new QHBoxLayout( vly, 15);
    QString s;
    QPushButton * pushButton = 0L;
    for( int i=0; i<butNb; i++) {
	s = buttonText[i];
	pushButton = new QPushButton(s, this );
	if (s.find('&') != -1) {
	    pushButton->setAccel( s.at(s.find('&')+1).latin1() );
	}

	hly2->addWidget( pushButton );
	if (i == def_but-1) {
	    pushButton->setDefault( true );
	    pushButton->setAutoDefault( true );
	    setResult( i+1 );
	}
	connect(pushButton, SIGNAL(clicked()), &mapper, SLOT(map()));
	mapper.setMapping(pushButton, i+1);
    }
    connect( &mapper, SIGNAL(mapped(int)), this, SLOT(done(int)));

    setCaption((const char *) title);

    vly->activate();
}//}}}

void VimDialog::done(int r) {
    if (entry!=NULL) {
        if (r) {
	   QCString unistring=vmw->codec->fromUnicode(entry->text()); 
	   STRCPY(ret,(const char*)unistring);
	} else 
	    *ret=NUL;
    }
    QDialog::done(r);
}

/*
 * ScrollBar pool handling
 */
SBPool::SBPool(void)//{{{
    :mapper(this, "SBPool signal mapper")
{
    connect(&mapper, SIGNAL(mapped(int)), this, SLOT(sbUsed(int)));
}//}}}


void SBPool::create(GuiScrollbar * sb, int orient)//{{{
{
    switch(orient) {
	case SBAR_HORIZ:
	    sb->w = new QScrollBar(QScrollBar::Horizontal, vmw);
	    break;
	case SBAR_VERT:
	    sb->w = new QScrollBar(QScrollBar::Vertical, vmw);
	    break;
	default:
	    sb->w = 0;
	    return;
    }

    connect(sb->w, SIGNAL(valueChanged(int)), &mapper, SLOT(map()));    
    mapper.setMapping(sb->w, (int)sb);
}//}}}


void SBPool::sbUsed(int who)//{{{
{
    GuiScrollbar *sb = (GuiScrollbar*)who;
    gui_drag_scrollbar( sb, sb->w->value(), FALSE);
}//}}}


void SBPool::destroy(GuiScrollbar * sb)//{{{
{
    if (!sb->w) return;

    delete sb->w;
    sb->w = 0;      
}//}}}

#ifdef FEAT_CLIENTSERVER
static int kvim_x11_event_filter( XEvent* e)//{{{
{
    if (e->xproperty.type == PropertyNotify
	    && e->xproperty.atom == commProperty
	    && e->xproperty.window == commWindow 
	    && e->xproperty.state == PropertyNewValue ) {
	serverEventProc(qt_xdisplay(), e);
    }

    if (oldFilter) return oldFilter( e );
    return FALSE;
}//}}}
#endif

//add some QT 3 fonts usefull functions
#if QT_VERSION<300
QString KVimUtils::toString(QFont *f)
{
    QStringList l;
    l.append(f->family());
    l.append(QString::number(f->pointSize()));
    l.append(QString::number(f->pixelSize()));
    l.append(QString::number((int)f->styleHint()));
    l.append(QString::number(f->weight()));
    l.append(QString::number((int)f->italic()));
    l.append(QString::number((int)f->underline()));
    l.append(QString::number((int)f->strikeOut()));
    l.append(QString::number((int)f->fixedPitch()));
    l.append(QString::number((int)f->rawMode()));
    return l.join(",");
}

bool KVimUtils::fromString(QFont *f, QString descrip)
{
   QStringList l(QStringList::split(',', descrip));

    int count = l.count();
    if (count != 10 && count != 9) {
        return FALSE;
    }

    f->setFamily(l[0]);
    f->setPointSize(l[1].toInt());
    if ( count == 9 ) {
        f->setStyleHint((QFont::StyleHint) l[2].toInt());
        f->setWeight(l[3].toInt());
        f->setItalic(l[4].toInt());
        f->setUnderline(l[5].toInt());
        f->setStrikeOut(l[6].toInt());
        f->setFixedPitch(l[7].toInt());
        f->setRawMode(l[8].toInt());
    } else {
        f->setPixelSize(l[2].toInt());
        f->setStyleHint((QFont::StyleHint) l[3].toInt());
        f->setWeight(l[4].toInt());
        f->setItalic(l[5].toInt());
        f->setUnderline(l[6].toInt());
        f->setStrikeOut(l[7].toInt());
        f->setFixedPitch(l[8].toInt());
        f->setRawMode(l[9].toInt());
    }
    return TRUE;
}
#endif

QString KVimUtils::convertEncodingName(QString name) 
{
    if (name.startsWith("ucs") || name.startsWith("utf-16")) return QString("utf16");
    if (name=="cp950") return QString("Big5");
    return QString();
}
