/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/08
// RCS-ID:      $Id: res_doc.cpp,v 1.21 2001/03/12 15:15:35 aleksandras Exp $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "res_doc.cpp"
    #pragma interface "res_doc.h"
#endif

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

#include "res_doc.h"
#include "menuedit.h"
#include "widgeteditor.h" // FIXME:: HACK:: should not be coupled!

#include "mimetypes.h"

// sorry, 3 .cpp's and .h's of AWT-layouts contrib. needed to compile this .cpp!

#include "wx/awtlay/awt_emul.h"
#include "wx/awtlay/borderlayout.h"
#include "wx/awtlay/flowlayout.h"

#include "wx/choicdlg.h"
#include "wx/textdlg.h"

#include "wx/choicdlg.h"
#include "wx/txtstrm.h"

/***** Implementation for class ResourceDocument *****/

wxDMClipboard* ResDocument::mpClipboard = NULL;
ResTemplateManager* ResDocument::mpGlobalMgr = NULL;

IMPLEMENT_DYNAMIC_CLASS( ResDocument, wxPMDocument )

ResDocument::ResDocument()

	: mpRoot( NULL ),
	  mpCurRec( NULL ),
	  mDocsHash( wxKEY_STRING )
{}

ResDocument::~ResDocument()
{
	if ( mpRoot )
        delete mpRoot;
}

ResTemplateManager& ResDocument::GetTemplateManager()
{
	if ( mpGlobalMgr == NULL )
	{
		mpGlobalMgr = new ResTemplateManager();

		wxString sd = wxPMBootSvc().GetStartupDir();

        wxString fname = sd + wxString(wxFILE_SEP_PATH) + "config_widgets.txt";

		bool ok = mpGlobalMgr->LoadResourceTemplates( fname.c_str() );

        if (!ok)
        {
            wxString errmsgStr;
            errmsgStr.Printf("Widget configuration file '%s'\nwas unable to be loaded.\n\nIt may be missing or corrupt.  Please check that\nthis file is in the wxWorkshop program directory.",fname.c_str());
            wxMessageBox(errmsgStr.c_str(),"ATTENTION!");
        }
	}

	return *mpGlobalMgr;
}

void ResDocument::GetTopLevelRecordsByType( const wxString& type, FieldListT& toLst )
{
	if ( !mpRoot ) return;

	size_t i;
	for( i = 0; i != mpRoot->GetFieldCount(); ++i )

		if ( mpRoot->GetRecordByIndex(i)[ "class" ] == type )

			toLst.Add( &mpRoot->GetFieldByIndex(i) );
}

bool ResDocument::CanHaveFile()
{
	return TRUE;
}

bool ResDocument::SavePMDocument( wxOutputStream& s )
{
	if ( !mpRoot ) 
		return NULL;

	wxTextOutputStream tout( s );
									
	tout << "<?xml version=\"1.0\"?>\n";

	Record* pCopy = (Record*)mpRoot->Clone();

	TranslateFieldRecursively( *pCopy, FALSE );
	tout << pCopy->GetXMLTextContent();

	delete pCopy;

	return TRUE;
}

bool ResDocument::LoadPMDocument( wxInputStream& s )
{
	wxString script;

	bool ok = wxPMReadTextStreamToString( s, script );

	if ( !ok ) 
		return FALSE;

	if ( mpRoot ) 
		delete mpRoot;

	mpRoot = NULL;

	InMemoryXMLRecordSet rset;
	rset.Open( script );

	rset.MoveFirst();

	while( !rset.IsEof() )
	{
		if ( rset.GetRecord().GetName() == "resource" )
		{
			mpRoot = (Record*)rset.GetRecord().Clone();

			TranslateFieldRecursively( *mpRoot, TRUE );
			break;
		}

		rset.MoveNext();
	}

	return mpRoot != NULL;
}

void ResDocument::OnActivatePMDocument()
{
	if ( GetFileInfo() == NULL )
	{
		// provide resource-file name immediately after creation of document

		if ( !wxPMBootSvc().GetDocManager()->SaveDocument( this ) )
			return;
		
		// and include it into the active project
		wxPMBootSvc().GetWorkplace()->GetActiveProject()->AddFile( this->GetFileInfo() );
	}

	//wxMessageBox( "ResDocument::OnActivatePMDocument() called!" );

	ResSelectionDialog dlg( wxPMBootSvc().GetMainWindow(), this );

	dlg.ShowModal();
}

void ResDocument::OnClosingChildDocument( wxPMDocument* pChild, bool wasSaved )
{
	if ( pChild->IsModified() )
		Modify( this != NULL );

	Record* pRec = (Record*)mRecsHash.Get( (long)pChild );

	mRecsHash.Delete( (long)pChild );
	mDocsHash.Delete( (*pRec)[ "name" ] );
}

bool ResDocument::OnCreate(const wxString& path, long flags)
{
	GetTemplateManager(); // make sure it's loaded

	if ( mpRoot ) delete mpRoot;

	mpRoot = new Record();

	mpRoot->SetName( "resource" );

	return TRUE;
}

wxString ResDocument::GetItemClassById( const wxString& itemId )
{
	size_t i;
	for( i = 0; i != mpRoot->GetFieldCount(); ++i )
	{
		if ( mpRoot->GetRecordByIndex(i)[ "name" ] == itemId )

			return mpRoot->GetRecordByIndex(i)[ "class" ];
	}

	return "unknown_widget_class";
}

void ResDocument::GetSubItemIds( const wxString& parentItem,
								 wxStringList& ids,
								 wxStringList& classes,
								 wxStringList& subclasses )
{
	wxPMDocument* pDoc = (wxPMDocument*)mDocsHash.Get( parentItem );

	if ( pDoc )
	{
		wxDMNode* pRoot = NULL;

		// FIXME:: hacks...

		if ( pDoc->IsKindOf( CLASSINFO( MenuEditorDoc ) ) )
		{
			MenuEditorDoc* pMenuDoc = (MenuEditorDoc*)pDoc;

			pRoot = pMenuDoc->GetRootNode();
		}
		else
		if ( pDoc->IsKindOf( CLASSINFO( WidgetDocument ) ) )
		{
			WidgetDocument* pWidgetDoc = (WidgetDocument*)pDoc;

			pRoot = pWidgetDoc->GetRootNode();
		}
		else
			return;

		size_t i;
		for( i = 0; i != pRoot->GetChildCount(); ++i )
		
			GetSubItemIdsRecursive( *pRoot->GetChild(i), ids, classes, subclasses );
	}
	else
	{
		Record* pRec = FindItemByName( parentItem );

		if ( !pRec ) return;

		size_t i;
		for( i = 0; i != pRec->GetFieldCount(); ++i )
		
			if ( pRec->GetFieldByIndex(i).GetType() == FT_RECORD )

				GetSubItemIdsRecursive( pRec->GetRecordByIndex(i), ids, classes, subclasses );
	}
}

void ResDocument::GetSubItemIdsRecursive( Record& rec,
										  wxStringList& ids,
										  wxStringList& classes,
										  wxStringList& subclasses )
{
	FieldBase* pNameField = rec.FindField( "name" );

	if ( pNameField )
	{
		wxString cl = DoGetWidgetClass( rec["class"] );

		if ( cl != "wxSeparator" &&
			 rec.GetName() == "object" )
		{
			ids.Add( pNameField->GetValue() );
			classes.Add( cl );

			wxString sub = rec[ "subclass" ];

			if ( sub.Length() == 0 )

				sub = cl;

			subclasses.Add( sub );
		}
		else
		if ( rec.GetName() == "object_ref" )
		{
			Record* pRefRec = FindTopLevelItem( pNameField->GetValue() );

			if ( pRefRec )
			{
				cl = DoGetWidgetClass( (*pRefRec)[ "class" ] );
				wxString sub = (*pRefRec)[ "subclass" ];

				ids.Add( pNameField->GetValue() );
				classes.Add( cl );

				if ( sub.Length() == 0 )

					sub = cl;

				subclasses.Add( sub );
			}
		}
	}

	size_t i;
	for( i = 0; i != rec.GetFieldCount(); ++i )

		if ( rec.GetFieldByIndex(i).GetType() == FT_RECORD && 
			 rec.GetFieldByIndex(i).GetName() != "object_ref" )
		
			GetSubItemIdsRecursive( rec.GetRecordByIndex(i), ids, classes, subclasses );
}

void ResDocument::GetSubItemIdsRecursive( wxDMNode& node,
										  wxStringList& ids,
										  wxStringList& classes,
										  wxStringList& subclasses )
{
	// FIXME:: HACK:: should be tag-based class identification

	wxDMAttrGroupInfo* pGroup = node.GetAttrGroupInfo(0);
	
	wxString id = 
		
		pGroup->GetAttrByName("name")->GetStringValue( node.GetAttrGroupData((size_t)0) );


	if ( node.IsKindOf( CLASSINFO( wxME_Node ) ) )
	{
		wxString isSepartor = 
		
			pGroup->GetAttrByName("separator")->GetStringValue( node.GetAttrGroupData((size_t)0) );

		if ( isSepartor != "true" )
		{
			classes.Add( "wxMenuItem" );
			subclasses.Add( "wxMenuItem" );
			ids.Add( id );
		}
	}
	else
	if ( node.IsKindOf( CLASSINFO( WidgetNode ) ) )
	{
		WidgetNode* wNode = (WidgetNode*)&node;

		if ( wNode->IsLinked() )
			return;
		
		classes.Add( wNode->mpTempl->mWidgetClass );

		wxString sub = wNode->GetNodeAttrValue( "subclass" );

		if ( sub.Length() == 0 )

			sub = wNode->mpTempl->mWidgetClass;

		subclasses.Add( sub );

		ids.Add( id );
	}
	else
	{
		classes.Add( "wx__Unknown__Widget" );
		subclasses.Add( "wx__Unknown__Widget" );
		ids.Add( id );
	}

	size_t i;
	for( i = 0; i != node.GetChildCount(); ++i )

		GetSubItemIdsRecursive( *node.GetChild(i), ids, classes, subclasses );
}

void ResDocument::SetItemSubclass( const wxString& itemId, const wxString& subclass )
{
	size_t i;
	for( i = 0; i != mpRoot->GetFieldCount(); ++i )
	{
		Record& rec = mpRoot->GetRecordByIndex(i);

		if ( rec[ "name" ] == itemId )
		{
			FieldBase* pField = rec.FindField( "subclass" );

			if ( subclass.Length() == 0 )
			{
				if ( pField )

					rec.RemoveField( pField );
			}
			else
			{
				if ( pField )
				
					((StringValue*)pField)->SetValue( subclass );
				else
				{
					pField = new StringValue( "subclass", subclass );

					rec.AddField( pField );
				}
			}

			this->Modify( TRUE );
		}
	}

	mDocsHash.BeginFind();

	wxNode* pNode = mDocsHash.Next();

	while( pNode )
	{
		ResItemDocumentBase* pItemDoc = (ResItemDocumentBase*)pNode->GetData();

		wxDMNode* root = pItemDoc->GetRootNode();

		// FIXME:: extra coupling to WidgetNode!

		if ( root->IsKindOf( CLASSINFO( WidgetNode ) ) )

			SetItemSubclassRecursive( *root, itemId, subclass );

		pNode = mDocsHash.Next();
	}
}

void ResDocument::SetItemSubclassRecursive( wxDMNode& node, 
											   const wxString& itemId,
											   const wxString& subclass )
{
	WidgetNode& wNode = *((WidgetNode*)&node);

	if ( wNode.GetNodeAttrValue( "name" ) == itemId )
	{
		wNode.NotifyWillChange( &wNode );

		wNode.SetNodeAttrValue( "subclass", subclass );

		wNode.OnFinishEdit();
	}
	else
		for( size_t i = 0; i != wNode.GetChildCount(); ++i )
		
			SetItemSubclassRecursive( *wNode.GetChild(i), itemId, subclass );
}

const wxString& ResDocument::DoGetWidgetClass( const wxString& declClass )
{
	WidgetResTemplate* pTempl = 
		GetTemplateManager().FindWidgetTemplateForClass( declClass );

	if ( !pTempl )

		return declClass;
	else
		return pTempl->mBasedOnClass;
}

void tiny_xml_translate_value( wxString& value, bool afterLoading );

void ResDocument::TranslateFieldRecursively( FieldBase& fld, bool afterLoading )
{
	tiny_xml_translate_value( (wxString&)fld.GetValue(), // casting away constantness
							  afterLoading );

	if ( fld.GetType() == FT_RECORD )
	{
		Record& rec = *(Record*)&fld;

		size_t cnt = rec.GetFieldCount();

		for( size_t i = 0; i != cnt; ++i )

			TranslateFieldRecursively( rec.GetFieldByIndex(i), afterLoading );
	}
}

Record* ResDocument::GetRootRecord()
{
	return mpRoot;
}

Record* ResDocument::GetCurrentRecord()
{
	return mpCurRec;
}

Record* ResDocument::FindTopLevelItem( const wxString& itemId )
{
	return FindItemByName( itemId );
}

bool ResDocument::ReplaceTopLevelItem( const wxString& itemId, Record* pWithRec )
{
	if ( RemoveItemByName( itemId ) )
	{	
		mpRoot->AddField( pWithRec );

		return TRUE;
	}

	return FALSE;
}

wxString ResDocument::SelectItem( const wxString& dlgCaption )
{
/* glt - THIS VERSION OF wxSingleChoiceDialog IS NOW DEPRECATED WITH
         CURRENT WX2.  I'VE REPLACED IT WITH CODE THAT WILL WORK WITH 
			ALL VERSIONS OF WXWINDOWS V2.X
	wxStringList lst;

	size_t i;
	for( i = 0; i != mpRoot->GetFieldCount(); ++i )
	
		lst.Add( (mpRoot->GetRecordByIndex(i))[ "name" ] );

	wxSingleChoiceDialog dlg( wxPMBootSvc().GetMainWindow(), dlgCaption, 
							  "Select resource item", mpRoot->GetFieldCount(), lst );
*/
	wxString* choices = new wxString[mpRoot->GetFieldCount()];

	size_t i;
	for( i = 0; i != mpRoot->GetFieldCount(); ++i )
	
		choices[i] = (mpRoot->GetRecordByIndex(i))[ "name" ];

	wxSingleChoiceDialog dlg( wxPMBootSvc().GetMainWindow(), dlgCaption, 
							  "Select resource item", mpRoot->GetFieldCount(), choices );


	if ( dlg.ShowModal() == wxID_OK )
	
		return dlg.GetStringSelection();

	return "";
}

bool ResDocument::IsActivated( const wxString& itemName )
{
	return mDocsHash.Get( itemName ) != NULL;
}

bool ResDocument::RemoveItemByName( const wxString& itemName )
{
	Record* pItem = FindItemByName( itemName );

	if ( pItem )
	{
		mpRoot->RemoveField( pItem );

		Modify( TRUE );

		return TRUE;
	}

	return FALSE;
}

Record* ResDocument::FindItemByName( const wxString& itemName )
{
	size_t i;
	for( i = 0; i != mpRoot->GetFieldCount(); ++i )
	{
		Record& rec = mpRoot->GetRecordByIndex(i);
								
		FieldBase* pField = rec.FindField( "name" );

		if ( pField && ((StringValue*)pField)->GetValue() == itemName )
		
			return &rec;
	}

	return NULL;
}

ResItemDocumentBase* ResDocument::ActivateEditorForItem( const wxString& itemName )
{
	wxPMDocument* pDoc = (wxPMDocument*)mDocsHash.Get( itemName );

	wxPMDocManager& mgr = *wxPMBootSvc().GetDocManager();

	if ( pDoc )
	{
		mgr.ActivateDocument( pDoc );

		return (ResItemDocumentBase*)pDoc;
	}

	Record* pRec = FindItemByName( itemName );

	if ( pRec )
	{
		wxDocTemplate* pTempl = NULL;
		wxPMDocument* pDoc = NULL;
		mpCurRec = pRec;

		if ( (*pRec)[ "class" ] == "wxMenuBar" )

			pTempl = mgr.FindTemplateForDocType( wxwMIME_MENUBAR_RESOURCE );
		else
			pTempl = mgr.FindTemplateForDocType( wxwMIME_WIDGET_RESOURCE );

		if ( pTempl )
		{
			pDoc = mgr.CreatePMDocument( wxPMFileInfo(), wxDOC_SILENT, pTempl, this );

			wxPMView* pView = (wxPMView*)pDoc->GetFirstView();

			pView->GetViewFrame()->SetTitle( itemName );
			pDoc->SetTitle( itemName );

			mDocsHash.Put( itemName, pDoc );
			mRecsHash.Put( (long)pDoc, (wxObject*)pRec );
		}

		return (ResItemDocumentBase*)pDoc;
	}

//glt - There was no return value in this situation!!!
	return NULL;
}

wxDMClipboard& ResDocument::GetClipboard()
{
	if ( !mpClipboard )

		mpClipboard = new wxDMClipboard();

	return *mpClipboard;
}

/***** Implementation for class ResSelectionDialog *****/

BEGIN_EVENT_TABLE( ResSelectionDialog, wxDialog )

	EVT_BUTTON( wxID_OK,   ResSelectionDialog::OnOk )
	EVT_BUTTON( ID_NEW,    ResSelectionDialog::OnNew )
	EVT_BUTTON( ID_REMOVE, ResSelectionDialog::OnRemove )

	EVT_LISTBOX( ID_LIST_BOX, ResSelectionDialog::OnSelected )
	EVT_LISTBOX_DCLICK( ID_LIST_BOX, ResSelectionDialog::OnDblClick )

END_EVENT_TABLE()

ResSelectionDialog::ResSelectionDialog( wxWindow* parent, ResDocument* pDoc )

	: wxDialog( parent, -1, "Select resource", wxDefaultPosition, wxSize( 300, 250 ),
			    wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER ),
	  mpDoc( pDoc )
{
	Container& cont = *new Container( this );  // glt - this container does not get deleted, creating a memory leak
	cont.setInsets( Insets(10,10,10,10) );
	cont.setLayout( new BorderLayout( 10, 10 ) );

	mpBox = new wxListBox( this, ID_LIST_BOX );
	mpOkBtn = new wxButton( this, wxID_OK, "&Edit", wxDefaultPosition );
	mpNewBtn = new wxButton( this, ID_NEW, "&New", wxDefaultPosition );
	mpRemoveBtn = new wxButton( this, ID_REMOVE, "&Remove", wxDefaultPosition );
	mpCancelBtn = new wxButton( this, wxID_CANCEL, "C&ancel", wxDefaultPosition );

	Container* btnPanel = new Container();  // glt - this container does not get deleted, creating a memory leak
	btnPanel->setInsets( Insets(5,5,5,5) );
	btnPanel->setLayout( new FlowLayout() ); 

	btnPanel->add( mpOkBtn );
	btnPanel->add( mpNewBtn );
	btnPanel->add( mpRemoveBtn );
	btnPanel->add( mpCancelBtn );

	cont.add( mpBox,	BorderLayout::CENTER );
	cont.add( btnPanel, BorderLayout::SOUTH );

	mpOkBtn->SetDefault();

	FillListBox();

	cont.doLayout();
	Center( wxBOTH );

	mpBox->SetFocus();

	wxCommandEvent dummy;
	OnSelected( dummy ); // enable/disable buttons
}

void ResSelectionDialog::FillListBox()
{
	Record& rec = *mpDoc->GetRootRecord();

	size_t i;
	for( i = 0; i != rec.GetFieldCount(); ++i )
	{
		wxString name = rec.GetRecordByIndex( i )[ "name" ];

		mpBox->Append( name );
	}

	if ( mpBox->Number() )

		mpBox->SetSelection( 0, TRUE );
}

void ResSelectionDialog::OnOk( wxCommandEvent& event )
{
	wxString name = mpBox->GetStringSelection();

	EndModal( wxID_OK );

	mpDoc->ActivateEditorForItem( name );
}

void ResSelectionDialog::OnNew( wxCommandEvent& event )
{
	WidgetResTemplateListT& lst = mpDoc->GetTemplateManager().GetTemplates();

	wxString* choices = new wxString[ lst.Count() + 1 ];

	wxArrayString sortedList;
    wxArrayString items;

    unsigned int count = lst.Count();
	sortedList.Add("menubar");

	size_t i;
	for( i = 0; i != count; i++ )
		sortedList.Add(lst[i]->mWidgetClass);

	sortedList.Sort();

	for( i = 0; i != lst.Count(); ++i )
		choices[i] = sortedList[i];

	wxSingleChoiceDialog dlg( wxPMBootSvc().GetMainWindow(), "Select item to create", 
						      "New Item", count+1, choices );

	if ( dlg.ShowModal() == wxID_OK )
	{
		wxString wClass = dlg.GetStringSelection();

		wxTextEntryDialog dlg1( wxPMBootSvc().GetMainWindow(), 
							    "Enter name for the new item:", "Item Name",
							    wClass + "1" );

		// TBD:: check for name-collisions

		if ( dlg1.ShowModal() == wxID_OK )
		{
			EndModal( wxID_OK );

			Record* pItemRec = new Record();

			pItemRec->SetName( "object" );
			pItemRec->AddStringField( "class", wClass );
			wxString name = dlg1.GetValue();

			pItemRec->AddStringField( "name", name );

			mpDoc->GetRootRecord()->AddField( pItemRec );

			if ( wClass == "wxMenuBar" )
			{
				Record* pFirstItem = new Record();
				pFirstItem->SetName( "object" );
				pFirstItem->AddStringField( "class", "wxMenu" );
				pFirstItem->AddStringField( "name", "menu1" ); 
				pFirstItem->SetStringValue( "First Menu" );

				pItemRec->AddField( pFirstItem );
			}
			else
			{
				pItemRec->AddStringField( "pos", "0,0" ); 
				pItemRec->AddStringField( "size", "300,200" ); 
			}

			mpDoc->Modify( TRUE );

			mpDoc->ActivateEditorForItem( name );
		}
	}
}

void ResSelectionDialog::OnRemove( wxCommandEvent& event )
{
	if ( mpBox->GetSelection() == -1 )
	{
		wxMessageBox( "Please, select item first" );

		return;
	}

	if ( mpDoc->IsActivated( mpBox->GetStringSelection() ) )

		return;

	mpDoc->RemoveItemByName( mpBox->GetStringSelection() );

	mpBox->Clear();

	FillListBox();

	if ( mpBox->Number() == 0 )

		mpRemoveBtn->Enable( FALSE );
}

void ResSelectionDialog::OnSelected( wxCommandEvent& event )
{
	if ( mpDoc->IsActivated( mpBox->GetStringSelection() ) )

		mpRemoveBtn->Enable( FALSE );
	else
		mpRemoveBtn->Enable( TRUE );
}

void ResSelectionDialog::OnDblClick( wxCommandEvent& event )
{
	OnOk( event );
}

/***** Implementation for class ResItemDocumentBase *****/

IMPLEMENT_ABSTRACT_CLASS( ResItemDocumentBase, wxPMDocument )
