/****************************************************************************
** $Id: qseditor.cpp  beta1   edited Dec 17 00:55 $
**
** Implementation of the QSEditor class.
**
** Copyright (C) 2001-2002 Trolltech AS.  All rights reserved.
**
** This file is part of the Qt Script for Applications framework (QSA).
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file.
**
** Licensees holding a valid QSA Beta Evaluation Version license may use
** this file in accordance with the QSA Beta Evaluation Version License
** Agreement provided with the Software.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
**   information about QSA Commercial License Agreements.
** See http://www.trolltech.com/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
*****************************************************************************/

#include "qseditor.h"
#include <private/qpluginmanager_p.h>
#include <editorinterface.h>
#include <qapplication.h>
#include <qobjectlist.h>
#include "qsa.h"
#include <qtextedit.h>
#include <qsizepolicy.h>

// designer
#include <project.h>
#include <formwindow.h>
#include <formfile.h>
#include <sourcefile.h>
#include <mainwindow.h>

static QPluginManager<EditorInterface> *editorPluginManager = 0;

static void setupPluginManagers()
{
    if ( editorPluginManager )
	return;
    editorPluginManager =
	new QPluginManager<EditorInterface>( IID_Editor, QApplication::libraryPaths(), "/qsa" );
}

/*!
  \class QSEditor qseditor.h

  \brief A widget component providing an enhanced editor for editing
  Qt Script source code

  For some applications the \e {QSA Developer} is too complex, even
  with the GUI builder and/or debugger parts turned off (see
  QProject::openDeveloper()). In this case it might be more
  appropriate to embed a simple editor into the application's
  UI. QSEditor provides such a widget component, which offers in
  addition to normal editor functionality also Qt Script syntax
  highlighting, completion, function argument hints and
  auto-indentation.

  You can either edit normal source code (setText()), the source of a
  file in the currently open project (setSource()) or the source of an
  application object (setSource()).

  If the code of a file or object in a project is edited, it can be
  saved back to the project via save(). text() returns the current
  text and object() the object whose code is edited.

  By default the completion of the editor is disabled. To have the
  completion working, the Qt Script interpreter has to be set
  up properly and cannot allow any signal handler connections. To tell
  the interpreter to go into this mode, the editor for which
  completion should be available, has to be activated. This can be
  done using activate(). As long as the editor is active (isActive()),
  no Qt Script signal handler connections work. But it is possible to
  call normal Qt Script functions via QSInterpreter::call() and
  to evaluate code (QSInterpreter::evaluate()). To activate the
  signal handlers again, the editor has to be deactivated or released
  (release()).

  Only one editor at a time can be active and offering
  completion. QSEditor::activeEditor() returns the
  currently active editor.

  If you have multiple QSEditors and want to support completion,
  listen to the focus Out/In events and activate() and release() the
  editor accordingly.

  Note that a QSEditor can only be created if no editable QSA
  Developer is open. A non editable QSA Developer is allowed together
  with QSEditors, as this can be used for debugging.

  Look at the examples/simplescript, examples/scriptbutton and
  examples/console examples to see an example usage of the QSEditor
  class.
*/

/*! \fn void QSEditor::textChanged()
  This signal is emitted if the text of the editor has been changed
*/

/*! Constructs an editor. The \a parent and \a name arguments are
  passed to the QWidget constructor.
*/

QSEditor::QSEditor( QWidget *parent, const char *name )
    : QWidget( parent, name ), editor( 0 ), tEdit( 0 ),
      active( FALSE ), contextObject( 0 ),
      sourceType( NoSource )
{
    setupPluginManagers();
    QtApplicationScript::self()->registerEditor( this );
    eIface = 0;
    if ( QtApplicationScript::self()->designer &&
	 !QtApplicationScript::self()->designer->areEditorsReadOnly() ) {
	qWarning( "QSEditor::QSEditor: Cannot create QSEditor, since an editable QSA Developer is open" );
	return;
    }
    editorPluginManager->queryInterface( "Qt Script", &eIface );
    if ( !eIface )
	return;
    editor = eIface->editor( FALSE, this, 0 );
    if ( !editor )
	return;
    setFocusPolicy( WheelFocus );
    setFocusProxy( editor );
    if ( editor->inherits( "QTextEdit" ) ) {
	tEdit = (QTextEdit*)editor;
	connect( tEdit, SIGNAL( textChanged() ),
		 this, SIGNAL( textChanged() ) );
    }
    editor = editor->parentWidget();
    editor->setGeometry( 0, 0, width(), height() );
    editor->show();
    setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
}

/*! Destructs the editor. */

QSEditor::~QSEditor()
{
    QtApplicationScript::self()->unregisterEditor( this );
    delete editor;
    eIface->release();
}

/*! Sets the editor contents to \a text. In case completion is used
  and this code should be interpreted as code in the context of an
  object, pass this object as \a context. If the code is global or no
  completion is requried, pass 0 as \a context.
*/

void QSEditor::setText( const QString &text, QObject *context )
{
    if ( !eIface )
	return;
    sourceType = PlainSource;
    eIface->setText( text );
    setContext( context );
}

/*! Sets the editor to edit the code of the file \a fileInProject in
  the currently opened project.
*/

void QSEditor::setSource( const QString &fileInProject )
{
    if ( !eIface )
	return;
    if ( !QtApplicationScript::self()->project ) {
	qWarning( "QSEditor::setSource: No project open" );
	return;
    }

    ::SourceFile *sf = QtApplicationScript::self()->project->findSourceFile( fileInProject );
    if ( !sf ) {
	qWarning( "QSEditor::setSource: file %s does not exist in project", fileInProject.latin1() );
	return;
    }

    if ( sourceType == SourceFile )
	delete sourceFile;
    else
	sourceType = SourceFile;
    sourceFile = new QString( fileInProject );

    eIface->setText( sf->text() );
    setContext( 0 );
}

/*! Sets the editor to edit the code of the application object \a
  appObject of the currently opened project.
*/

void QSEditor::setSource( QObject *appObject )
{
    if ( !eIface )
	return;
    if ( !QtApplicationScript::self()->project ) {
	qWarning( "QSEditor::setSource: No project open" );
	return;
    }

    FormFile *ff = 0;
    ff = QtApplicationScript::self()->project->fakeFormFileFor( appObject );
    if ( !ff ) {
	qWarning( "QSEditor::setSource: object %s (%s) does not exist in project",
		  appObject->name(), appObject->className() );
	return;
    }

    if ( sourceType == SourceFile )
	delete sourceFile;
    sourceType = AppObject;
    aObject = appObject;
    eIface->setText( ff->code() );
    setContext( appObject );
}

/*! Returns the text of the editor */

QString QSEditor::text() const
{
    if ( !eIface )
	return QString::null;
    return eIface->text();
}

/*! If a file or application object is edited, saves the code changes
  back to the project */

void QSEditor::save()
{
    if ( !eIface )
	return;
    switch ( sourceType ) {
    case SourceFile:
	QtApplicationScript::self()->addSource( eIface->text(), *sourceFile, FALSE );
	break;
    case AppObject:
	QtApplicationScript::self()->setObjectSource( aObject, eIface->text(), FALSE );
	break;
    default:
	break;
    }

    if ( QtApplicationScript::self()->project )
	QtApplicationScript::self()->project->save();
}

/*! Tries to activate this editor. Returns TRUE if it succeeds,
  otherwise FALSE.

  See the class description for the concept of active editors.
*/

bool QSEditor::activate()
{
    if ( !eIface )
	return FALSE;
    if ( active )
	return TRUE;
    QPtrListIterator<QSEditor> it( *QtApplicationScript::self()->editors() );
    while ( it.current() ) {
	if ( it.current()->isActive() ) {
	    qWarning( "QSEditor::activate: Another editor (%s) is active. Can't activate this editor.",
		      it.current()->name() );
	    return FALSE;
	}
	++it;
    }
    active = TRUE;
    QtApplicationScript::self()->stopProject( TRUE );
    setContext( contextObject );
    return TRUE;
}

/*! Releases (deactivates) this editor.

  See the class description for the concept of active editors.
*/

void QSEditor::release()
{
    if ( !eIface )
	return;
    if ( !active )
	return;
    active = FALSE;
    QtApplicationScript::self()->checkProject();
}

/*! \fn bool QSEditor::isActive() const
  Returns whether this editor is active or not.

  See the class description for the concept of active editors.
*/

/*! Returns whether undo is available */

bool QSEditor::isUndoAvailable() const
{
    if ( !eIface )
	return FALSE;
    return eIface->isUndoAvailable();
}

/*! Returns whether redo is available */

bool QSEditor::isRedoAvailable() const
{
    if ( !eIface )
	return FALSE;
    return eIface->isRedoAvailable();
}

/*! Undoes the last editor operation */

void QSEditor::undo()
{
    if ( !eIface )
	return;
    eIface->undo();
}

/*! Redoes the last editor operation */

void QSEditor::redo()
{
    if ( !eIface )
	return;
    eIface->redo();
}

/*! Cuts the selected text to the clipboard */

void QSEditor::cut()
{
    if ( !eIface )
	return;
    eIface->cut();
}

/*! Copies the selected text to the clipboard */

void QSEditor::copy()
{
    if ( !eIface )
	return;
    eIface->copy();
}

/*! Pastes text from the clipboard into the editor */

void QSEditor::paste()
{
    if ( !eIface )
	return;
    eIface->paste();
}

/*! Selects all the text in the editor */

void QSEditor::selectAll()
{
    if ( !eIface )
	return;
    eIface->selectAll();
}

/*! Finds the expression \a expr in the editor and selects the
  result. If \a cs is TRUE the search is done case sensitive. If \a wo
  is TRUE, only whole words are searched. If \a forward is TRUE, the
  search is done formward, otherwise backwards. If \a startAtCursor is
  TRUE, the search starts at the current cursor position, otherwise it
  starts at the beginning of the document.

  Returns TRUE, of \a expr was found, FALSE otherwise.
*/

bool QSEditor::find( const QString &expr, bool cs, bool wo, bool forward, bool startAtCursor )
{
    if ( !eIface )
	return FALSE;
    return eIface->find( expr, cs, wo, forward, startAtCursor );
}

/*! Replaces the expression \a find in the editor with \a replace. If
  \a cs is TRUE the search is done case sensitive. If \a wo is TRUE,
  only whole words are searched. If \a forward is TRUE, the search is
  done formward, otherwise backwards. If \a startAtCursor is TRUE, the
  search starts at the current cursor position, otherwise it starts at
  the beginning of the document. If \a replaceAll is TRUE, all found
  occurances of \a find are replaced by \a replace. Otherwise only the
  first occurance is replaced.
*/

bool QSEditor::replace( const QString &find, const QString &replace, bool cs, bool wo,
			bool forward, bool startAtCursor, bool replaceAll )
{
    if ( !eIface )
	return FALSE;
    return eIface->replace( find, replace, cs, wo, forward, startAtCursor, replaceAll );
}

/*! Moves the cursor to the line \a line in the editor */

void QSEditor::gotoLine( int line )
{
    if ( !eIface )
	return;
    eIface->gotoLine( line );
}

/*! Indents the current line or selection */

void QSEditor::indent()
{
    if ( !eIface )
	return;
    eIface->indent();
}

/*! Re-reads settings for syntax highlighting, indentation, etc. The
  settings are read from the QSettings path
  \c /Trolltech/QuickScriptEditor/

  Following settings are read for syntax highlighting:

  \list
  \i \c /Comment
  \i \c /Number
  \i \c /String
  \i \c /Type
  \i \c /Label
  \i \c /Standard
  \endlist

  For each of those following settings are read:

  \list
  \i \c /family (string)
  \i \c /size (number)
  \i \c /bold (bool)
  \i \c /italic (bool)
  \i \c /underline (bool)
  \i \c /red (number)
  \i \c /green (number)
  \i \c /blue (number)
  \endlist

  So, the font family for strings is e.g. saved in
  \c /Trolltech/QuickScriptEditor/String/family.

  Following settings for indentation are read:

  \list
  \i \c /indentAutoIndent (bool) - whether auto indentation should be done
  \i \c /indentTabSize (number) - the tab size for indentation
  \i \c /indentIndentSize (number) - the indentation width
  \i \c /indentKeepTabs (bool) - whether to keep tabs or to replace them with spaces
  \endlist

  Code complection can be switched on or off via \c /completion,
  wordwrapping via \c /wordWrap and paren matching via \c /parenMatching
*/

void QSEditor::readSettings()
{
    if ( !eIface )
	return;
    eIface->readSettings();
}

/*! Highlights the line \a line. This can be used e.g. to show and
  error or warning */

void QSEditor::highlightLine( int line )
{
    if ( !eIface )
	return;
    eIface->setError( line );
}

/*!
  \reimp
*/

QSize QSEditor::sizeHint() const
{
    if ( editor )
	return editor->sizeHint();
    return QWidget::sizeHint();
}

/*!
  \reimp
*/

QSize QSEditor::minimumSizeHint() const
{
    if ( editor )
	return editor->minimumSizeHint();
    return QWidget::minimumSizeHint();
}

/*!
  \reimp
*/

void QSEditor::resizeEvent( QResizeEvent *e )
{
    QWidget::resizeEvent( e );
    if ( editor )
	editor->setGeometry( 0, 0, width(), height() );
}

/*! Returns the pointer to the real editor widget, which is a
  QTextEdit. This might be 0 if an error occured while initializing
  the editor.
*/

QTextEdit *QSEditor::textEdit() const
{
    return tEdit;
}

bool QSEditor::setContext( QObject *o )
{
    contextObject = o;
    if ( !active )
	return FALSE;
    eIface->setContext( contextObject );
    return TRUE;
}

/*! Returns the currently active QSEditor, or NULL if there isn't an
  active one. See the documentation of QSEditor for the concept of an
  active editor.
*/

QSEditor *QSEditor::activeEditor()
{
    return QtApplicationScript::self()->activeEditor();
}

/*! Returns all open QSEditors */

const QPtrList<QSEditor> *QSEditor::editors()
{
    return QtApplicationScript::self()->editors();
}


