//
// This file is part of the aMule Project.
//
// Copyright (c) 2004-2006 Angel Vidal Veiga (kry@users.sourceforge.net)
// Copyright (c) 2004-2006 aMule Team ( admin@amule.org / http://www.amule.org )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// 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 <wx/event.h>
#include <wx/app.h>
#include <wx/imaglist.h>
#include <wx/menu.h>
#include <wx/intl.h>

#include "MuleNotebook.h"	// Interface declarations
#include "OPCodes.h"		// Needed for MP_CLOSE_ IDs


DEFINE_LOCAL_EVENT_TYPE(wxEVT_COMMAND_MULENOTEBOOK_PAGE_CLOSED)
DEFINE_LOCAL_EVENT_TYPE(wxEVT_COMMAND_MULENOTEBOOK_ALL_PAGES_CLOSED)

BEGIN_EVENT_TABLE(CMuleNotebook, wxNotebook)
	EVT_RIGHT_DOWN(CMuleNotebook::OnRMButton)

	EVT_MENU(MP_CLOSE_TAB,			CMuleNotebook::OnPopupClose)
	EVT_MENU(MP_CLOSE_ALL_TABS,		CMuleNotebook::OnPopupCloseAll)
	EVT_MENU(MP_CLOSE_OTHER_TABS,	CMuleNotebook::OnPopupCloseOthers)	
	
	// Madcat - tab closing engine
	EVT_LEFT_DOWN(CMuleNotebook::MouseClick)
	EVT_LEFT_DCLICK(CMuleNotebook::MouseClick)
	EVT_MOTION(CMuleNotebook::MouseMotion)
END_EVENT_TABLE()

CMuleNotebook::CMuleNotebook( wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name )
	: wxNotebook(parent, id, pos, size, style, name)
{
	m_popup_enable = true;
	m_popup_widget = NULL;
}


CMuleNotebook::~CMuleNotebook()
{
	// Ensure that all notifications gets sent
	DeleteAllPages();
}


bool CMuleNotebook::DeletePage(int nPage)
{
	// Send out close event
	wxNotebookEvent evt( wxEVT_COMMAND_MULENOTEBOOK_PAGE_CLOSED, GetId(), nPage );
	evt.SetEventObject(this);
	ProcessEvent( evt );

	// and finally remove the actual page
	bool result = wxNotebook::DeletePage( nPage );

	// Ensure a valid selection
	if ( GetPageCount() && (int)GetSelection() >= (int)GetPageCount() ) {
		SetSelection( GetPageCount() - 1 );
	}

	// Send an event when no pages are left open
	if ( !GetPageCount() ) {
		wxNotebookEvent event( wxEVT_COMMAND_MULENOTEBOOK_ALL_PAGES_CLOSED, GetId() );
		event.SetEventObject(this);
		ProcessEvent( event );
	}

	return result;
}


bool CMuleNotebook::DeleteAllPages()
{
	Freeze();

	bool result = true;
	while ( GetPageCount() ) {
		result &= DeletePage( 0 );
	}
	
	Thaw();
		
	return result;
}


void CMuleNotebook::EnablePopup( bool enable )
{
	m_popup_enable = enable;
}


void CMuleNotebook::SetPopupHandler( wxWindow* widget )
{
	m_popup_widget = widget;
}


#warning wxMac does not support selection by right-clicking on tabs!
void CMuleNotebook::OnRMButton(wxMouseEvent& event)
{
	// Cases where we shouldn't be showing a popup-menu.
	if ( !GetPageCount() || !m_popup_enable ) {
		event.Skip();
		return;
	}


// For some reason, gtk1 does a rather poor job when using the HitTest
#if (!defined(__WXGTK__) || defined(__WXGTK20__))
	wxPoint eventPoint = event.GetPosition();

#ifdef __WXMAC__
	// For some reason, on the Mac clicks on the tabs give positions differing
	// by the client area origin from where HitTest believes the tabs to be.
	eventPoint += GetClientAreaOrigin();
#endif

	int tab = HitTest(eventPoint);
	if (tab != wxNOT_FOUND) {
#ifdef __WXMAC__
		// Due to a wxMac bug, HitTest returns 1-based values.  Should be 0-based.
		tab--;
#endif
		SetSelection(tab);
	} else {
		event.Skip();
		return;
	}

	// Should we send the event to a specific widget?
	if ( m_popup_widget ) {
		wxMouseEvent evt = event;
	
		// Map the coordinates onto the parent
		wxPoint point = evt.GetPosition();
		point = ClientToScreen( point );
		point = m_popup_widget->ScreenToClient( point );
			
		evt.m_x = point.x;
		evt.m_y = point.y;
			
		m_popup_widget->AddPendingEvent( evt );
	} else {
#else
	// Check if this is a new event, generated by the notebook itself.
	if ( event.GetId() != -1 ) {
		wxMouseEvent evt = event;
		
		// Should we send the event to a specific widget?
		if ( m_popup_widget ) {
			// Map the coordinates onto the parent
			wxPoint point = evt.GetPosition();
			point = ClientToScreen( point );
			point = m_popup_widget->ScreenToClient( point );
			
			evt.m_x = point.x;
			evt.m_y = point.y;
			
			m_popup_widget->AddPendingEvent( evt );
		} else {
			// Requeue the event, this allows the right-click to be handled so
			// that the right tab will be selected. We set the ID to an 
			// invalid value, so that we can recognize it as an recursive event.
			evt.SetId( -1 );
			
			AddPendingEvent( evt );
		}

		// Allow the event to propagate further. This means that wxNotebook
		// handles right-clicks and selects the appropriate tab.
		event.Skip();
	} else {
		// We received an requeued event, time to show the popup-menu
		// since we are now sure that the right tab has been selected.
#endif
		wxMenu menu(_("Close"));
		menu.Append(MP_CLOSE_TAB, wxString(_("Close tab")));
		menu.Append(MP_CLOSE_ALL_TABS, wxString(_("Close all tabs")));
		menu.Append(MP_CLOSE_OTHER_TABS, wxString(_("Close other tabs")));

		PopupMenu( &menu, event.GetPosition() );
	}
}


void CMuleNotebook::OnPopupClose(wxCommandEvent& WXUNUSED(evt))
{
	DeletePage( GetSelection() );
}


void CMuleNotebook::OnPopupCloseAll(wxCommandEvent& WXUNUSED(evt))
{
	DeleteAllPages();
}


void CMuleNotebook::OnPopupCloseOthers(wxCommandEvent& WXUNUSED(evt))
{
	wxNotebookPage* current = GetPage( GetSelection() );
	
	for ( int i = GetPageCount() - 1; i >= 0; i-- ) {
		if ( current != GetPage( i ) )
			DeletePage( i );
	}
}

#if !defined(__WXGTK12__)

void CMuleNotebook::MouseClick(wxMouseEvent &event)
{

	if (GetImageList() == NULL) {
		// This Mulenotebook has no images on tabs, so nothing to do.
		event.Skip();
		return; 
	}

	long xpos, ypos;
	event.GetPosition(&xpos, &ypos);
	
	long flags = 0;
	int tab = HitTest(wxPoint(xpos,ypos),&flags);	
	
	if ((tab != -1) &&  (flags == wxNB_HITTEST_ONICON)) {
		// User did click on a 'x'
		DeletePage(tab);
	} else {
		// Is not a 'x'. Send this event up.
		event.Skip();
	}
	
}

void CMuleNotebook::MouseMotion(wxMouseEvent &event)
{

	if (GetImageList() == NULL) {
		// This Mulenotebook has no images on tabs, so nothing to do.
		event.Skip();
		return;
	}
	
	long flags = 0;
	int tab = HitTest(wxPoint(event.m_x,event.m_y),&flags);	

	// Clear the highlight for all tabs.
	for (int i=0;i<(int)GetPageCount();++i) {
		SetPageImage(i, 0);
	}
	
	if ((tab != -1) &&  (flags == wxNB_HITTEST_ONICON)) {
		// Mouse is over a 'x'
		SetPageImage(tab, 1);		
	} else {
		// Is not a 'x'. Send this event up.
		event.Skip();
	}

}
#else
/**
 * Copyright (c) 2004-2006 Alo Sarv <madcat_@users.sourceforge.net>
 * Most important function in this class. Here we do some serious math to figure
 * out where pages are located, where close-buttons are located etc.
 * @widths array contains the width in pixels of each page
 * @begins array contains the tab beginnings locations relative to window
 *         left border
 * @ends array contains the tab ends locations relative to window left border.
 *
 * First we clear all 3 arrays, and then fill with zeroes. The latter is being
 * done because we need to do +='s in loops and we want to start out at zeros.
 * Then we loop through pages list, measure the text label and image label
 * sizes, add the space the underlying platform adds (needs #defining for other
 * platforms), and fill the arrays with the data. Important notice: The FIRST
 * notebook tab is 3 pixels wider than the rest (at least on GTK)!
 */
void CMuleNotebook::CalculatePositions()
{
	int imagesizex, imagesizey;  // Notebookpage image size
	int textsizex, textsizey;    // Notebookpage text size

	if (GetImageList() == NULL) {
		return; // No images
	}

	// Reset the arrays
	widths.Clear();
	begins.Clear();
	ends.Clear();
	widths.Alloc(GetPageCount());
	begins.Alloc(GetPageCount());
	ends.Alloc(GetPageCount());

	// Fill the arrays with zeros
	for (int i=0;i<(int)GetPageCount();++i) {
		widths.Add(0);
		begins.Add(0);
		ends.Add(0);
	}

	// Loop through all pages and calculate their widths.
	// Store all page begins, ends and widths in the arrays.
	for (int i=0;i<(int)GetPageCount();++i) {
		GetImageList()->GetSize(
			GetPageImage(i), imagesizex, imagesizey
		);
		GetTextExtent(GetPageText(i), &textsizex, &textsizey);
		widths[i] = 17+imagesizex+textsizex;
		if (i==0) {                              // first page
			begins[i]=0;
			ends[i]=widths[i];
		} else {                                 // other pages
			// Pages after first one are 3 pixels shorter
			widths[i]-=3;
			// Start 1 pixel after previous one
			begins[i]=ends[i-1]+1;
			// End is beginning + width
			ends[i]=begins[i]+widths[i];
		}
	}
}

/**
 * Copyright (c) 2004-2006 Alo Sarv <madcat_@users.sourceforge.net>
 * This method handles mouse clicks on tabs. We need to detect here if the
 * click happened to be on our close button, thus we first request positions
 * recalculation, and then compare the event position to our known close
 * buttons locations. If found, close the neccesery tab.
 */
void CMuleNotebook::MouseClick(wxMouseEvent &event)
{
	long posx, posy;             // Mouse position at the time of the event

	if (GetImageList() == NULL) {
		event.Skip();
		return; // No images
	}

	CalculatePositions();

	event.GetPosition(&posx, &posy);

	// Determine which page was under the mouse
	for (int i=0;i<(int)GetPageCount();++i) {
		if (posx >= begins[i] && posx <= ends[i]) {
			// Found it, check if image was hit
			// Notice: (GTK) First tab is 3 pixels wider, thus the
			//         inline ifs.
			// TODO: Use #defines instead of hardcoded constants and
			//       correct values for GTK2, Mac and MSW.
			if (
				// Horizontal positioning
				posx >= begins[i]+(i?6:9) &&
				posx <= begins[i]+(i?6:9)+12 &&
				// Vertical positioning
				posy >= 11 &&
				posy <= 22
			) {
				// Image was hit, close the page
				// and return w/o passing event to wx
				DeletePage(i);
				return;
			}
		}
	}
	event.Skip();
}

/**
 * Copyright (c) 2004-2006 Alo Sarv <madcat_@users.sourceforge.net>
 * This method handles mouse moving events. Since we can't recalculate positions
 * in EVT_MOUSE_ENTER (for some reason, wxNotebook doesn't receive those events)
 * we have to request recalculation here also, which is rather CPU-heavy.
 * Nonetheless, after we have updated positions in arrays, we can compare the
 * event position to our known close button locations, and if found, highlight
 * the neccesery button.
 */
void CMuleNotebook::MouseMotion(wxMouseEvent &event)
{
	long posx, posy;                        // Event X and Y positions
	if (GetImageList() == NULL) {
		event.Skip();
		return; // No images
	}

	CalculatePositions();

	posx = event.m_x;
	posy = event.m_y;

	// Determine which page was under the mouse
	for (int i=0;i<(int)GetPageCount();++i) {
		SetPageImage(i, 0);
		if (posx >= begins[i] && posx <= ends[i]) {
			// Found it, check if image was hit
			// Notice: First tab is 3 pixels wider, thus the inline ifs
			if (
				// Horizontal positioning
				posx >= begins[i]+(i?6:9) &&
				posx <= begins[i]+(i?6:9)+12 &&
				// Vertical positioning
				posy >= 11 &&
				posy <= 22
			) {
				// Image is under mouse, change to highlight
				SetPageImage(i, 1);
			}
		}
	}
	event.Skip();
}
#endif
