Browse Source

moved classes to own files

git-svn-id: svn://svn.code.sf.net/p/ditto-cp/code/trunk@190 595ec19a-5cb4-439b-94a8-42fb3063c22c
sabrogden 20 years ago
parent
commit
8802edc57c
8 changed files with 1699 additions and 20 deletions
  1. 19 19
      CP_Main.h
  2. 874 0
      Clip.cpp
  3. 127 0
      Clip.h
  4. 236 0
      ClipboardViewer.cpp
  5. 72 0
      ClipboardViewer.h
  6. 250 0
      CopyThread.cpp
  7. 120 0
      CopyThread.h
  8. 1 1
      ProcessPaste.h

+ 19 - 19
CP_Main.h

@@ -13,7 +13,7 @@
 #endif
 #endif
 
 
 #include "resource.h"       // main symbols
 #include "resource.h"       // main symbols
-#include "ProcessCopy.h"
+#include "Clip.h"
 #include "DatabaseUtilities.h"
 #include "DatabaseUtilities.h"
 #include "Misc.h"
 #include "Misc.h"
 #include "DataTable.h"
 #include "DataTable.h"
@@ -23,6 +23,7 @@
 #include "MainFrm.h"
 #include "MainFrm.h"
 #include "ProcessPaste.h"
 #include "ProcessPaste.h"
 #include "MultiLanguage.h"
 #include "MultiLanguage.h"
+#include "CopyThread.h"
 
 
 #define MAIN_WND_TITLE		"Ditto MainWnd"
 #define MAIN_WND_TITLE		"Ditto MainWnd"
 //#define GET_APP    ((CCP_MainApp *)AfxGetApp())	
 //#define GET_APP    ((CCP_MainApp *)AfxGetApp())	
@@ -70,9 +71,8 @@ public:
 	CHotKey*	m_pPosTen;
 	CHotKey*	m_pPosTen;
 
 
 // Focus Tracking
 // Focus Tracking
-	HWND	m_hTargetWnd;
-//	HWND	m_hTargetFocus;
-	bool	TargetActiveWindow();
+	HWND m_hTargetWnd;
+	bool TargetActiveWindow();
 	bool ActivateTarget();
 	bool ActivateTarget();
 	bool ReleaseFocus(); // activate the target only if we are the active window
 	bool ReleaseFocus(); // activate the target only if we are the active window
 	CString GetTargetName();
 	CString GetTargetName();
@@ -85,15 +85,15 @@ public:
 	void StartCopyThread();
 	void StartCopyThread();
 	void StopCopyThread();
 	void StopCopyThread();
 	// for posting messages
 	// for posting messages
-	HWND GetClipboardViewer() { return m_CopyThread.m_pClipboardViewer->m_hWnd; }
+	HWND GetClipboardViewer()			{ return m_CopyThread.m_pClipboardViewer->m_hWnd; }
 	// enables or disables copying the clipboard when it changes
 	// enables or disables copying the clipboard when it changes
-	bool EnableCbCopy(bool bState)  { return m_CopyThread.SetCopyOnChange(bState); }
-	bool IsClipboardViewerConnected() { return m_CopyThread.IsClipboardViewerConnected(); }
+	bool EnableCbCopy(bool bState)		{ return m_CopyThread.SetCopyOnChange(bState); }
+	bool IsClipboardViewerConnected()	{ return m_CopyThread.IsClipboardViewerConnected(); }
 	// user control over being in the clipboard viewer chain.
 	// user control over being in the clipboard viewer chain.
-	bool GetConnectCV() { return m_CopyThread.GetConnectCV(); }
-	void SetConnectCV( bool bConnect );
+	bool GetConnectCV()					{ return m_CopyThread.GetConnectCV(); }
+	void SetConnectCV(bool bConnect);
 	bool ToggleConnectCV();
 	bool ToggleConnectCV();
-	void UpdateMenuConnectCV( CMenu* pMenu, UINT nMenuID );
+	void UpdateMenuConnectCV(CMenu* pMenu, UINT nMenuID);
 
 
 //	CClipList	m_SaveClipQueue; 
 //	CClipList	m_SaveClipQueue; 
 	// Retrieves all clips from CopyThread and Saves them.
 	// Retrieves all clips from CopyThread and Saves them.
@@ -109,8 +109,8 @@ public:
 // Internal Clipboard for cut/copy/paste items between Groups
 // Internal Clipboard for cut/copy/paste items between Groups
 	bool		m_IC_bCopy;   // true to copy the items, false to move them
 	bool		m_IC_bCopy;   // true to copy the items, false to move them
 	CClipIDs	m_IC_IDs; // buffer
 	CClipIDs	m_IC_IDs; // buffer
-	void IC_Cut( ARRAY* pIDs = NULL ); // if NULL, this uses the current QPaste selection
-	void IC_Copy( ARRAY* pIDs = NULL ); // if NULL, this uses the current QPaste selection
+	void IC_Cut(ARRAY* pIDs = NULL); // if NULL, this uses the current QPaste selection
+	void IC_Copy(ARRAY* pIDs = NULL); // if NULL, this uses the current QPaste selection
 	void IC_Paste();
 	void IC_Paste();
 
 
 // Groups
 // Groups
@@ -119,9 +119,9 @@ public:
 	long		m_GroupParentID;  // current group's parent
 	long		m_GroupParentID;  // current group's parent
 	CString		m_GroupText;      // current group's description
 	CString		m_GroupText;      // current group's description
 
 
-	BOOL EnterGroupID( long lID );
+	BOOL EnterGroupID(long lID);
 	long GetValidGroupID(); // returns a valid id (not negative)
 	long GetValidGroupID(); // returns a valid id (not negative)
-	void SetGroupDefaultID( long lID ); // sets a valid id
+	void SetGroupDefaultID(long lID); // sets a valid id
 
 
 // Window States
 // Window States
 	// the ID given focus by CQPasteWnd::FillList
 	// the ID given focus by CQPasteWnd::FillList
@@ -133,15 +133,15 @@ public:
 
 
 	CString m_Status;
 	CString m_Status;
 	CQPasteWnd* QPasteWnd() { return m_pMainFrame->QuickPaste.m_pwndPaste; }
 	CQPasteWnd* QPasteWnd() { return m_pMainFrame->QuickPaste.m_pwndPaste; }
-	void SetStatus( const char* status = NULL, bool bRepaintImmediately = false );
+	void SetStatus(const char* status = NULL, bool bRepaintImmediately = false);
 
 
-	void ShowPersistent( bool bVal );
+	void ShowPersistent(bool bVal);
 
 
-	bool	m_bShowCopyProperties;
-	void ShowCopyProperties( long lID );
+	bool m_bShowCopyProperties;
+	void ShowCopyProperties(long lID);
 
 
 	bool	m_bRemoveOldEntriesPending;
 	bool	m_bRemoveOldEntriesPending;
-	void Delayed_RemoveOldEntries( UINT delay );
+	void Delayed_RemoveOldEntries(UINT delay);
 
 
 // Database
 // Database
 	CDaoDatabase*	m_pDatabase;
 	CDaoDatabase*	m_pDatabase;

+ 874 - 0
Clip.cpp

@@ -0,0 +1,874 @@
+// ProcessCopy.cpp: implementation of the CProcessCopy class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "stdafx.h"
+#include "CP_Main.h"
+#include "Clip.h"
+#include "DatabaseUtilities.h"
+
+#include <Mmsystem.h>
+
+#ifdef _DEBUG
+#undef THIS_FILE
+static char THIS_FILE[]=__FILE__;
+#define new DEBUG_NEW
+#endif
+
+
+/*----------------------------------------------------------------------------*\
+COleDataObjectEx
+\*----------------------------------------------------------------------------*/
+
+HGLOBAL COleDataObjectEx::GetGlobalData(CLIPFORMAT cfFormat, LPFORMATETC lpFormatEtc)
+{
+    HGLOBAL hGlobal = COleDataObject::GetGlobalData(cfFormat, lpFormatEtc);
+	if(hGlobal)
+	{
+		if(!::IsValid(hGlobal))
+		{
+			LOG( StrF(
+				"COleDataObjectEx::GetGlobalData(\"%s\"): ERROR: Invalid (NULL) data returned.",
+				GetFormatName(cfFormat) ) );
+			::GlobalFree( hGlobal );
+			hGlobal = NULL;
+		}
+		return hGlobal;
+	}
+	
+	// The data isn't in global memory, so try getting an IStream interface to it.
+	STGMEDIUM stg;
+	
+	if(!GetData(cfFormat, &stg))
+	{
+		return 0;
+	}
+	
+	switch(stg.tymed)
+	{
+	case TYMED_HGLOBAL:
+		hGlobal = stg.hGlobal;
+		break;
+		
+	case TYMED_ISTREAM:
+		{
+			UINT            uDataSize;
+			LARGE_INTEGER	li;
+			ULARGE_INTEGER	uli;
+			
+			li.HighPart = li.LowPart = 0;
+			
+			if ( SUCCEEDED( stg.pstm->Seek ( li, STREAM_SEEK_END, &uli )))
+			{
+				hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, uli.LowPart );
+				
+				void* pv = GlobalLock(hGlobal);
+				stg.pstm->Seek(li, STREAM_SEEK_SET, NULL);
+				HRESULT result = stg.pstm->Read(pv, uli.LowPart, (PULONG)&uDataSize);
+				GlobalUnlock(hGlobal);
+				
+				if( FAILED(result) )
+					hGlobal = GlobalFree(hGlobal);
+			}
+			break;  // case TYMED_ISTREAM
+		}
+	} // end switch
+	
+	ReleaseStgMedium(&stg);
+	
+	if(hGlobal && !::IsValid(hGlobal))
+	{
+		LOG( StrF(
+			"COleDataObjectEx::GetGlobalData(\"%s\"): ERROR: Invalid (NULL) data returned.",
+			GetFormatName(cfFormat)));
+		::GlobalFree(hGlobal);
+		hGlobal = NULL;
+	}
+	
+	return hGlobal;
+}
+
+/*----------------------------------------------------------------------------*\
+CClipFormat - holds the data of one clip format.
+\*----------------------------------------------------------------------------*/
+CClipFormat::CClipFormat(CLIPFORMAT cfType, HGLOBAL hgData)
+{
+	m_cfType = cfType;
+	m_hgData = hgData;
+	bDeleteData = true;
+}
+
+CClipFormat::~CClipFormat() 
+{ 
+	Free(); 
+}
+
+void CClipFormat::Clear()
+{
+	m_cfType = 0;
+	m_hgData = 0;
+}
+
+void CClipFormat::Free()
+{
+	if(bDeleteData)
+	{
+		if(m_hgData)
+		{
+			m_hgData = ::GlobalFree( m_hgData );
+			m_hgData = NULL;
+		}
+	}
+}
+
+
+
+/*----------------------------------------------------------------------------*\
+CClipFormats - holds an array of CClipFormat
+\*----------------------------------------------------------------------------*/
+// returns a pointer to the CClipFormat in this array which matches the given type
+//  or NULL if that type doesn't exist in this array.
+CClipFormat* CClipFormats::FindFormat(UINT cfType)
+{
+	CClipFormat* pCF;
+	int count = GetSize();
+
+	for(int i=0; i < count; i++)
+	{
+		pCF = &ElementAt(i);
+		if(pCF->m_cfType == cfType)
+			return pCF;
+	}
+	return NULL;
+}
+
+
+/*----------------------------------------------------------------------------*\
+CClip - holds multiple CClipFormats and CopyClipboard() statistics
+\*----------------------------------------------------------------------------*/
+
+CClip::CClip() : 
+	m_ID(0), 
+	m_DataID(0), 
+	m_lTotalCopySize(0)
+{
+}
+
+CClip::~CClip()
+{
+	EmptyFormats();
+}
+
+void CClip::Clear()
+{
+	m_ID = 0;
+	m_Time = 0;
+	m_Desc = "";
+	m_lTotalCopySize = 0;
+	m_DataID = 0;
+	EmptyFormats();
+}
+
+const CClip& CClip::operator=(const CClip &clip)
+{
+	const CClipFormat* pCF;
+
+	m_ID = clip.m_ID;
+	m_DataID = clip.m_DataID;
+	m_Time = clip.m_Time;
+	m_lTotalCopySize = clip.m_lTotalCopySize;
+
+	int nCount = clip.m_Formats.GetSize();
+	
+	for(int i = 0; i < nCount; i++)
+	{
+		pCF = &clip.m_Formats.GetData()[i];
+
+		LPVOID pvData = GlobalLock(pCF->m_hgData);
+		if(pvData)
+		{
+			AddFormat(pCF->m_cfType, pvData, GlobalSize(pCF->m_hgData));
+		}
+		GlobalUnlock(pCF->m_hgData);
+	}
+
+	//Set this after since in could get the wrong description in AddFormat
+	m_Desc = clip.m_Desc;
+
+	return *this;
+}
+
+void CClip::EmptyFormats()
+{
+	// free global memory in m_Formats
+	for(int i = m_Formats.GetSize()-1; i >= 0; i--)
+	{
+		m_Formats[i].Free();
+		m_Formats.RemoveAt( i );
+	}
+}
+
+// Adds a new Format to this Clip by copying the given data.
+bool CClip::AddFormat(CLIPFORMAT cfType, void* pData, UINT nLen)
+{
+	ASSERT(pData && nLen);
+	HGLOBAL hGlobal = ::NewGlobalP(pData, nLen);
+	ASSERT(hGlobal);
+
+	// update the Clip statistics
+	m_Time = m_Time.GetCurrentTime();
+	m_lTotalCopySize += nLen;
+	if(!SetDescFromText(hGlobal))
+		SetDescFromType();
+	
+	CClipFormat format(cfType,hGlobal);
+	CClipFormat *pFormat;
+	
+	pFormat = m_Formats.FindFormat(cfType);
+	// if the format type already exists as part of this clip, replace the data
+	if(pFormat)
+	{
+		pFormat->Free();
+		pFormat->m_hgData = format.m_hgData;
+	}
+	else
+	{
+		m_Formats.Add(format);
+	}
+	
+	format.m_hgData = 0; // now owned by m_Formats
+	return true;
+}
+
+// Fills this CClip with the contents of the clipboard.
+bool CClip::LoadFromClipboard(CClipTypes* pClipTypes)
+{
+	COleDataObjectEx oleData;
+	CClipTypes defaultTypes;
+	CClipTypes* pTypes = pClipTypes;
+	
+	// m_Formats should be empty when this is called.
+	ASSERT(m_Formats.GetSize() == 0);
+	
+	// If the data is supposed to be private, then return
+	if(::IsClipboardFormatAvailable(theApp.m_cfIgnoreClipboard))
+	{
+		return false;
+	}
+	
+	//Attach to the clipboard
+	if(!oleData.AttachClipboard())
+	{
+		ASSERT(0); // does this ever happen?
+		return false;
+	}
+	
+	oleData.EnsureClipboardObject();
+	
+	// if no types were given, get only the first (most important) type.
+	//  (subsequent types could be synthetic due to automatic type conversions)
+	if(pTypes == NULL || pTypes->GetSize() == 0)
+	{
+		ASSERT(0); // this feature is not currently used... it is an error if it is.
+		
+		FORMATETC formatEtc;
+		oleData.BeginEnumFormats();
+		oleData.GetNextFormat(&formatEtc);
+		defaultTypes.Add(formatEtc.cfFormat);
+		pTypes = &defaultTypes;
+	}
+	
+	// reset copy stats
+	m_lTotalCopySize = 0;
+	m_Desc = "[Ditto Error] BAD DESCRIPTION";
+	
+	// Get Description String
+	// NOTE: We make sure that the description always corresponds to the
+	//  data saved by using the exact same globalmem instance as the source
+	//  for both... i.e. we only fetch the description format type once.
+	CClipFormat cfDesc;
+	bool bIsDescSet = false;
+	cfDesc.m_cfType = CF_TEXT;
+	if(oleData.IsDataAvailable(cfDesc.m_cfType))
+	{
+		cfDesc.m_hgData = oleData.GetGlobalData(cfDesc.m_cfType);
+		bIsDescSet = SetDescFromText(cfDesc.m_hgData);
+	}
+	
+	// Get global data for each supported type on the clipboard
+	UINT nSize;
+	CClipFormat cf;
+	int numTypes = pTypes->GetSize();
+	for(int i = 0; i < numTypes; i++)
+	{
+		cf.m_cfType = pTypes->ElementAt(i);
+		
+		// is this the description we already fetched?
+		if(cf.m_cfType == cfDesc.m_cfType)
+		{
+			cf = cfDesc;
+			cfDesc.m_hgData = 0; // cf owns it now (to go into m_Formats)
+		}
+		else if(!oleData.IsDataAvailable(cf.m_cfType))
+		{
+			continue;
+		}
+		else
+		{
+			cf.m_hgData = oleData.GetGlobalData(cf.m_cfType);
+		}
+		
+		if(cf.m_hgData)
+		{
+			nSize = GlobalSize(cf.m_hgData);
+			if(nSize > 0)
+			{
+				if(g_Opt.m_lMaxClipSizeInBytes > 0 && nSize > g_Opt.m_lMaxClipSizeInBytes)
+				{
+					CString cs;
+					cs.Format("Maximum clip size reached max size = %d, clip size = %d", g_Opt.m_lMaxClipSizeInBytes, nSize);
+					Log(cs);
+
+					oleData.Release();
+					return false;
+				}
+
+				ASSERT(::IsValid(cf.m_hgData));
+				
+				m_Formats.Add(cf);
+				m_lTotalCopySize += nSize;
+			}
+			else
+			{
+				ASSERT(FALSE); // a valid GlobalMem with 0 size is strange
+				cf.Free();
+			}
+			cf.m_hgData = 0; // m_Formats owns it now
+		}
+	}
+	
+	m_Time = CTime::GetCurrentTime();
+	
+	if(!bIsDescSet)
+	{
+		SetDescFromType();
+	}
+	
+	// if the description was in a type that is not supported,
+	//we have to free it since it wasn't added to m_Formats
+	if(cfDesc.m_hgData)
+	{
+		cfDesc.Free();
+	}
+	
+	oleData.Release();
+	
+	if(m_Formats.GetSize() == 0)
+	{
+		return false;
+	}
+	
+	return true;
+}
+
+bool CClip::SetDescFromText(HGLOBAL hgData)
+{
+	if(hgData == 0)
+		return false;
+	
+	bool bRet = false;
+	char* text = (char *) GlobalLock(hgData);
+	long ulBufLen = GlobalSize(hgData);
+	
+	ASSERT(text != NULL);
+	
+	if(ulBufLen > g_Opt.m_bDescTextSize)
+	{
+		ulBufLen = g_Opt.m_bDescTextSize;
+	}
+	
+	if(ulBufLen > 0)
+	{
+		char* buf = m_Desc.GetBuffer(ulBufLen);
+		memcpy(buf, text, ulBufLen); // in most cases, last char == null
+		buf[ulBufLen-1] = '\0'; // just in case not null terminated
+		m_Desc.ReleaseBuffer(); // scans for the null
+		bRet = m_Desc.GetLength() > 0;
+	}
+	
+	//Unlock the data
+	GlobalUnlock(hgData);
+	
+	return bRet;
+}
+
+bool CClip::SetDescFromType()
+{
+	if(m_Formats.GetSize() <= 0)
+	{
+		return false;
+	}
+
+	m_Desc = GetFormatName(m_Formats[0].m_cfType);
+
+	return m_Desc.GetLength() > 0;
+}
+
+bool CClip::AddToDB(bool bCheckForDuplicates)
+{
+	bool bResult;
+	try
+	{
+		if(bCheckForDuplicates)
+		{
+			CMainTable recset;
+			
+			if(FindDuplicate(recset, g_Opt.m_bAllowDuplicates))
+			{
+				m_ID = recset.m_lID;
+				recset.Edit();
+				recset.m_lDate = (long) m_Time.GetTime(); // update the copy Time
+				recset.Update();
+				recset.Close();
+				EmptyFormats(); // delete this clip's data from memory.
+				return true;
+			}
+			
+			if(recset.IsOpen())
+				recset.Close();
+		}
+	}
+	CATCHDAO
+		
+	// AddToDataTable must go first in order to assign m_DataID
+	bResult = AddToDataTable() && AddToMainTable();
+
+	if(bResult)
+	{
+		if(g_Opt.m_csPlaySoundOnCopy.GetLength() > 0)
+			PlaySound(g_Opt.m_csPlaySoundOnCopy, NULL, SND_FILENAME|SND_ASYNC);
+	}
+	
+	// should be emptied by AddToDataTable
+	ASSERT(m_Formats.GetSize() == 0);
+	
+	return bResult;
+}
+
+// if a duplicate exists, set recset to the duplicate and return true
+bool CClip::FindDuplicate(CMainTable& recset, BOOL bCheckLastOnly)
+{
+	ASSERT(m_lTotalCopySize > 0);
+	try
+	{
+		recset.m_strSort = "lDate DESC";
+		
+		if(bCheckLastOnly)
+		{
+			recset.Open("SELECT * FROM Main");
+			if(recset.IsEOF())
+			{
+				return false;
+			}
+
+			recset.MoveFirst();
+			// if an entry exists and they are the same size and the format data matches
+			if(!recset.IsBOF() && !recset.IsEOF() &&
+				m_lTotalCopySize == recset.m_lTotalCopySize &&
+				(CompareFormatDataTo(recset.m_lDataID) == 0))
+			{	
+				return true; 
+			}
+			return false;
+		}
+		
+		// Look for any other entries that have the same size
+		recset.Open("SELECT * FROM Main WHERE lTotalCopySize = %d", m_lTotalCopySize);
+		while(!recset.IsEOF())
+		{
+			//if there is any then look if it is an exact match
+			if(CompareFormatDataTo(recset.m_lDataID) == 0)
+			{
+				return true;
+			}
+			
+			recset.MoveNext();
+		}
+	}
+	CATCHDAO
+		
+	return false;
+}
+
+int CClip::CompareFormatDataTo(long lDataID)
+{
+	int nRet = 0;
+	int nRecs=0, nFormats=0;
+	CClipFormat* pFormat = NULL;
+	try
+	{
+		CDataTable recset;
+		recset.Open("SELECT * FROM Data WHERE lDataID = %d", lDataID);
+		
+		if( !recset.IsBOF() && !recset.IsEOF() )
+		{
+			// Verify the same number of saved types
+			recset.MoveLast();
+			nRecs = recset.GetRecordCount();
+		}
+
+		nFormats = m_Formats.GetSize();
+		nRet = nFormats - nRecs;
+		if(nRet != 0 || nRecs == 0)
+		{	
+			recset.Close();	
+			return nRet; 
+		}
+		
+		// For each format type in the db
+		
+		for(recset.MoveFirst(); !recset.IsEOF(); recset.MoveNext())
+		{
+			pFormat = m_Formats.FindFormat(GetFormatID(recset.m_strClipBoardFormat));
+			
+			// Verify the format exists
+			if(!pFormat)
+			{	
+				recset.Close(); 
+				return -1; 
+			}
+			
+			// Compare the size
+			nRet = ::GlobalSize(pFormat->m_hgData) - recset.m_ooData.m_dwDataLength;
+			if( nRet != 0 )
+			{	
+				recset.Close(); 
+				return nRet; 
+			}
+			
+			// Binary compare
+			nRet = CompareGlobalHH(recset.m_ooData.m_hData,	pFormat->m_hgData, recset.m_ooData.m_dwDataLength);
+			if(nRet != 0)
+			{	
+				recset.Close(); 
+				return nRet; 
+			}
+		}
+		recset.Close();
+	}
+	CATCHDAO
+		
+	return 0;
+}
+
+// assigns m_ID
+bool CClip::AddToMainTable()
+{
+	try
+	{
+		CMainTable recset;
+		
+		//		recset.m_strSort = "lDate DESC";
+		recset.Open("SELECT * FROM Main");
+		
+		long lDate = (long) m_Time.GetTime();
+		
+		recset.AddNew();  // overridden to set m_lID to the new autoincr number
+		
+		m_ID = recset.m_lID;
+		
+		recset.m_lDate = lDate;
+		recset.m_strText = m_Desc;
+		recset.m_lTotalCopySize = m_lTotalCopySize;
+		
+		recset.m_bIsGroup = FALSE;
+		recset.m_lParentID = theApp.m_GroupDefaultID;
+		
+		VERIFY(m_DataID > 0); // AddToDataTable must be called first to assign this
+		recset.m_lDataID = m_DataID;
+		
+		recset.Update();
+		
+		//		recset.SetBookmark( recset.GetLastModifiedBookmark() );
+		//		m_ID = recset.m_lID;
+		
+		recset.Close();
+	}
+	catch(CDaoException* e)
+	{
+		ASSERT(FALSE);
+		e->Delete();
+		return false;
+	}
+	
+	return true;
+}
+
+// Empties m_Formats as it saves them to the Data Table.
+bool CClip::AddToDataTable()
+{
+	VERIFY( m_DataID <= 0 ); // this func will assign m_DataID
+	try
+	{
+		CClipFormat* pCF;
+		CDataTable recset;
+		recset.Open(dbOpenTable,"Data");
+		
+		for(int i = m_Formats.GetSize()-1; i >= 0 ; i--)
+		{
+			pCF = & m_Formats.ElementAt(i);
+			
+			recset.AddNew(); // overridden to assign new autoincr ID to m_lID
+			
+			if( m_DataID <= 0 )
+			{
+				VERIFY( recset.m_lID > 0 );
+				m_DataID = recset.m_lID;
+			}
+			
+			recset.m_lDataID = m_DataID;
+			recset.m_strClipBoardFormat = GetFormatName(pCF->m_cfType);
+			// the recset takes ownership of the HGLOBAL
+			recset.ReplaceData(pCF->m_hgData, GlobalSize(pCF->m_hgData));
+			
+			recset.Update();
+			
+			m_Formats.RemoveAt(i); // the recset now owns the global
+		}
+		
+		recset.Close();
+		return true;
+	}
+	CATCHDAO
+		
+	return false;
+}
+
+// changes m_Time to be later than the latest clip entry in the db
+// ensures that pClip's time is not older than the last clip added
+// old times can happen on fast copies (<1 sec).
+void CClip::MakeLatestTime()
+{
+	long lDate;
+	try
+	{
+		CMainTable recset;
+		
+		recset.m_strSort = "lDate DESC";
+		recset.Open("SELECT * FROM Main");
+		if(!recset.IsEOF())
+		{
+			recset.MoveFirst();
+			
+			lDate = (long) m_Time.GetTime();
+			if( lDate <= recset.m_lDate )
+			{
+				lDate = recset.m_lDate + 1;
+				m_Time = lDate;
+			}
+		}
+		recset.Close();
+	}
+	CATCHDAO
+}
+
+// STATICS
+
+// Allocates a Global containing the requested Clip Format Data
+HGLOBAL CClip::LoadFormat(long lID, UINT cfType)
+{
+	HGLOBAL hGlobal = 0;
+	try
+	{
+		CDataTable recset;
+		CString csSQL;
+		
+		csSQL.Format(
+			"SELECT Data.* FROM Data "
+			"INNER JOIN Main ON Main.lDataID = Data.lDataID "
+			"WHERE Main.lID = %d "
+			"AND Data.strClipBoardFormat = \'%s\'",
+			lID,
+			GetFormatName(cfType));
+		
+		recset.Open(AFX_DAO_USE_DEFAULT_TYPE, csSQL);
+		
+		if( !recset.IsBOF() && !recset.IsEOF() )
+		{
+			// create a new HGLOBAL duplicate
+			hGlobal = NewGlobalH( recset.m_ooData.m_hData, recset.m_ooData.m_dwDataLength );
+			// XOR take the recset's HGLOBAL... is this SAFE??
+			//			hGlobal = recset.TakeData();
+			if( !hGlobal || ::GlobalSize(hGlobal) == 0 )
+			{
+				TRACE0( GetErrorString(::GetLastError()) );
+				//::_RPT0( _CRT_WARN, GetErrorString(::GetLastError()) );
+				ASSERT(FALSE);
+			}
+		}
+		
+		recset.Close();
+	}
+	CATCHDAO
+		
+	return hGlobal;
+}
+
+bool CClip::LoadFormats(long lID, CClipFormats& formats, bool bOnlyLoad_CF_TEXT)
+{
+	CClipFormat cf;
+	HGLOBAL hGlobal = 0;
+	
+	formats.RemoveAll();
+	
+	try
+	{
+		CDataTable recset;
+		
+		//Open the data table for all that have the parent id
+		CString csSQL;
+		csSQL.Format(
+			"SELECT Data.* FROM Data "
+			"INNER JOIN Main ON Main.lDataID = Data.lDataID "
+			"WHERE Main.lID = %d", lID);
+		
+		recset.Open(AFX_DAO_USE_DEFAULT_TYPE, csSQL);
+		
+		while( !recset.IsEOF() )
+		{
+			cf.m_cfType = GetFormatID( recset.m_strClipBoardFormat );
+			
+			if(bOnlyLoad_CF_TEXT)
+			{
+				if(cf.m_cfType != CF_TEXT)
+				{
+					recset.MoveNext();
+					continue;
+				}
+			}
+			
+			// create a new HGLOBAL duplicate
+			hGlobal = NewGlobalH( recset.m_ooData.m_hData, recset.m_ooData.m_dwDataLength );
+			// XOR take the recset's HGLOBAL... is this SAFE??
+			//			hGlobal = recset.TakeData();
+			if( !hGlobal || ::GlobalSize(hGlobal) == 0 )
+			{
+				TRACE0( GetErrorString(::GetLastError()) );
+				//::_RPT0( _CRT_WARN, GetErrorString(::GetLastError()) );
+				ASSERT(FALSE);
+			}
+			
+			cf.m_hgData = hGlobal;
+			formats.Add( cf );
+			recset.MoveNext();
+		}
+		cf.m_hgData = 0; // formats owns all the data
+		
+		recset.Close();
+	}
+	CATCHDAO
+		
+	return formats.GetSize() > 0;
+}
+
+void CClip::LoadTypes(long lID, CClipTypes& types)
+{
+	types.RemoveAll();
+	try
+	{
+		CDataTable recset;
+		CString csSQL;
+		// get formats for Clip "lID" (Main.lID) using the corresponding Main.lDataID
+		csSQL.Format(
+			"SELECT Data.* FROM Data "
+			"INNER JOIN Main ON Main.lDataID = Data.lDataID "
+			"WHERE Main.lID = %d", lID);
+		
+		recset.Open(AFX_DAO_USE_DEFAULT_TYPE, csSQL);
+		
+		while(!recset.IsEOF())
+		{
+			types.Add( GetFormatID( recset.m_strClipBoardFormat ) );
+			recset.MoveNext();
+		}
+		
+		recset.Close();
+	}
+	CATCHDAO
+}
+
+
+/*----------------------------------------------------------------------------*\
+CClipList
+\*----------------------------------------------------------------------------*/
+
+CClipList::~CClipList()
+{
+	CClip* pClip;
+	while(GetCount())
+	{
+		pClip = RemoveHead();
+		DELETE_PTR( pClip );
+	}
+}
+
+// returns the number of clips actually saved
+// while this does empty the Format Data, it does not delete the Clips.
+int CClipList::AddToDB(bool bLatestTime, bool bShowStatus)
+{
+	int savedCount = 0;
+	int nRemaining = 0;
+	CClip* pClip;
+	POSITION pos;
+	bool bResult;
+	
+	nRemaining = GetCount();
+	pos = GetHeadPosition();
+	while(pos)
+	{
+		if(bShowStatus)
+		{
+			theApp.SetStatus(StrF("%d",nRemaining), true);
+			nRemaining--;
+		}
+		
+		pClip = GetNext(pos);
+		ASSERT(pClip);
+		
+		if(bLatestTime)
+			pClip->MakeLatestTime();
+		
+		bResult = pClip->AddToDB();
+		if( bResult )
+			savedCount++;
+	}
+	
+	if(bShowStatus)
+		theApp.SetStatus(NULL, true);
+	
+	return savedCount;
+}
+
+const CClipList& CClipList::operator=(const CClipList &cliplist)
+{
+	POSITION pos;
+	CClip* pClip;
+	
+	pos = cliplist.GetHeadPosition();
+	while(pos)
+	{
+		pClip = cliplist.GetNext(pos);
+		ASSERT(pClip);
+
+		CClip *pNewClip = new CClip;
+		if(pNewClip)
+		{
+			*pNewClip = *pClip;
+			
+			AddTail(pNewClip);
+		}
+	}
+	
+	return *this;
+}
+

+ 127 - 0
Clip.h

@@ -0,0 +1,127 @@
+// ProcessCopy.h: classes for saving the clipboard to db
+//
+//////////////////////////////////////////////////////////////////////
+
+#if !defined(AFX_PROCESSCOPY_H__185CBB6F_4B63_4397_8FF9_E18D777DA506__INCLUDED_)
+#define AFX_PROCESSCOPY_H__185CBB6F_4B63_4397_8FF9_E18D777DA506__INCLUDED_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+#include <afxole.h>
+#include <afxtempl.h>
+#include "MainTable.h"
+
+class CClip;
+class CCopyThread;
+
+typedef CArray<CLIPFORMAT, CLIPFORMAT> CClipTypes;
+
+/*----------------------------------------------------------------------------*\
+	COleDataObjectEx
+\*----------------------------------------------------------------------------*/
+class COleDataObjectEx : public COleDataObject
+{
+public:
+	// creates global from IStream if necessary
+	HGLOBAL GetGlobalData(CLIPFORMAT cfFormat, LPFORMATETC lpFormatEtc = NULL);
+};
+
+/*----------------------------------------------------------------------------*\
+	CClipFormat - holds the data of one clip format.
+\*----------------------------------------------------------------------------*/
+class CClipFormat
+{
+public:
+	CLIPFORMAT	m_cfType;
+    HGLOBAL		m_hgData;
+	bool		bDeleteData;
+
+	CClipFormat(CLIPFORMAT cfType = 0, HGLOBAL hgData = 0);
+	~CClipFormat();
+
+	void Clear();
+	void Free();
+};
+
+/*----------------------------------------------------------------------------*\
+	CClipFormats - holds an array of CClipFormat
+\*----------------------------------------------------------------------------*/
+class CClipFormats : public CArray<CClipFormat,CClipFormat&>
+{
+public:
+	// returns a pointer to the CClipFormat in this array which matches the given type
+	//  or NULL if that type doesn't exist in this array.
+	CClipFormat* FindFormat(UINT cfType); 
+};
+
+
+/*----------------------------------------------------------------------------*\
+	CClip - holds multiple CClipFormats and clip statistics
+	- provides static functions for manipulating a Clip as a single unit.
+\*----------------------------------------------------------------------------*/
+class CClip
+{
+public:
+	long			m_ID; // 0 if it hasn't yet been saved or is unknown
+	long			m_DataID;
+	CClipFormats	m_Formats; // actual format data
+
+	const CClip& operator=(const CClip &clip);
+
+	// statistics assigned by LoadFromClipboard
+	CTime	m_Time;	 // time copied from clipboard
+	CString m_Desc;
+	ULONG	m_lTotalCopySize;
+
+	CClip();
+	~CClip();
+
+	void Clear();
+	void EmptyFormats();
+	
+	// Adds a new Format to this Clip by copying the given data.
+	bool AddFormat(CLIPFORMAT cfType, void* pData, UINT nLen);
+	// Fills this CClip with the contents of the clipboard.
+	bool LoadFromClipboard(CClipTypes* pClipTypes);
+	bool SetDescFromText(HGLOBAL hgData);
+	bool SetDescFromType();
+
+	// Immediately save this clip to the db (empties m_Formats due to AddToDataTable).
+	bool AddToDB(bool bCheckForDuplicates = true);
+	bool AddToMainTable();
+	bool AddToDataTable();
+
+	// if a duplicate exists, set recset to the duplicate and return true
+	bool FindDuplicate(CMainTable& recset, BOOL bCheckLastOnly = FALSE);
+	int  CompareFormatDataTo(long lDataID);
+
+	// changes m_Time to be later than the latest clip entry in the db
+	void MakeLatestTime();
+
+// STATICS
+	// Allocates a Global containing the requested Clip's Format Data
+	static HGLOBAL LoadFormat(long lID, UINT cfType);
+	// Fills "formats" with the Data of all Formats in the db for the given Clip ID
+	static bool LoadFormats(long lID, CClipFormats& formats, bool bOnlyLoad_CF_TEXT = false);
+	// Fills "types" with all Types in the db for the given Clip ID
+	static void LoadTypes(long lID, CClipTypes& types);
+};
+
+
+/*----------------------------------------------------------------------------*\
+	CClipList
+\*----------------------------------------------------------------------------*/
+
+class CClipList : public CList<CClip*,CClip*>
+{
+public:
+	~CClipList();
+	// returns the number of clips actually saved
+	// while this does empty the Format Data, it does not delete the Clips.
+	int AddToDB( bool bLatestTime = false, bool bShowStatus = true );
+
+	const CClipList& operator=(const CClipList &cliplist);
+};
+
+#endif // !defined(AFX_PROCESSCOPY_H__185CBB6F_4B63_4397_8FF9_E18D777DA506__INCLUDED_)

+ 236 - 0
ClipboardViewer.cpp

@@ -0,0 +1,236 @@
+// ClipboardViewer.cpp : implementation file
+//
+
+#include "stdafx.h"
+#include "cp_main.h"
+#include "ClipboardViewer.h"
+
+#ifdef _DEBUG
+#define new DEBUG_NEW
+#undef THIS_FILE
+static char THIS_FILE[] = __FILE__;
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// CClipboardViewer
+
+CClipboardViewer::CClipboardViewer(CCopyThread* pHandler)
+{
+	m_hNextClipboardViewer = 0;
+	m_bCalling_SetClipboardViewer = false;
+	m_lReconectCount = 0;
+	m_bIsConnected = false;
+	m_bConnect = false;
+	m_pHandler = pHandler;
+	ASSERT(m_pHandler);
+	m_bPinging = false;
+	m_bPingSuccess = false;
+}
+
+CClipboardViewer::~CClipboardViewer()
+{
+}
+
+
+BEGIN_MESSAGE_MAP(CClipboardViewer, CWnd)
+	//{{AFX_MSG_MAP(CClipboardViewer)
+	ON_WM_CREATE()
+	ON_WM_CHANGECBCHAIN()
+	ON_WM_DRAWCLIPBOARD()
+	ON_WM_TIMER()
+	ON_WM_DESTROY()
+	//}}AFX_MSG_MAP
+	ON_MESSAGE(WM_CV_GETCONNECT, OnCVGetConnect)
+	ON_MESSAGE(WM_CV_SETCONNECT, OnCVSetConnect)
+	ON_MESSAGE(WM_CV_IS_CONNECTED, OnCVIsConnected)
+END_MESSAGE_MAP()
+
+
+/////////////////////////////////////////////////////////////////////////////
+// CClipboardViewer message handlers
+void CClipboardViewer::Create()
+{
+	CString strParentClass = AfxRegisterWndClass(0);
+	CWnd::CreateEx(0, strParentClass, "Ditto Clipboard Viewer", 0, -1, -1, 0, 0, 0, 0);
+
+	SetConnect( true );
+}
+
+// connects as a clipboard viewer
+void CClipboardViewer::Connect()
+{
+	//Set up the clip board viewer
+	m_bCalling_SetClipboardViewer = true;
+	m_hNextClipboardViewer = CWnd::SetClipboardViewer();
+	m_bCalling_SetClipboardViewer = false;
+	m_bIsConnected = SendPing();
+
+	// verify that we are in the chain every minute
+	SetTimer(TIMER_ENSURE_VIEWER_IN_CHAIN, ONE_MINUTE, 0);
+}
+
+// disconnects as a clipboard viewer
+void CClipboardViewer::Disconnect()
+{
+	KillTimer(TIMER_ENSURE_VIEWER_IN_CHAIN);
+
+	CWnd::ChangeClipboardChain( m_hNextClipboardViewer );
+	m_hNextClipboardViewer = 0;
+	m_bIsConnected = false;
+}
+
+bool CClipboardViewer::SendPing()
+{
+	HWND hWnd;
+	bool bResult = false;
+	
+	hWnd = ::GetClipboardViewer();
+	// if there is a chain
+	if(::IsWindow(hWnd))
+	{
+		m_bPingSuccess = false;
+		m_bPinging = true;
+		::SendMessage(hWnd, WM_DRAWCLIPBOARD, 0, 0);
+		m_bPinging = false;
+		bResult = m_bPingSuccess;
+	}
+	
+	m_bIsConnected = bResult;
+	
+	return bResult;
+}
+
+bool CClipboardViewer::EnsureConnected()
+{
+	if(!SendPing())
+		Connect();
+	
+	return m_bIsConnected;
+}
+
+// puts format "Clipboard Viewer Ignore" on the clipboard
+void CClipboardViewer::SetCVIgnore()
+{
+	if(::OpenClipboard(m_hWnd))
+	{
+		::SetClipboardData(theApp.m_cfIgnoreClipboard, NULL);
+		::CloseClipboard();
+	}
+}
+
+void CClipboardViewer::SetConnect( bool bConnect )
+{
+	m_bConnect = bConnect;
+	if(m_bConnect)
+		EnsureConnected();
+	else
+		Disconnect();
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// CClipboardViewer message handlers
+
+int CClipboardViewer::OnCreate(LPCREATESTRUCT lpCreateStruct)
+{
+	if(CWnd::OnCreate(lpCreateStruct) == -1)
+		return -1;
+	
+	//Set up the clip board viewer
+	Connect();
+	
+	return 0;
+}
+
+void CClipboardViewer::OnDestroy()
+{
+	Disconnect();
+	CWnd::OnDestroy();
+}
+
+void CClipboardViewer::OnChangeCbChain(HWND hWndRemove, HWND hWndAfter) 
+{
+	// If the next window is closing, repair the chain. 
+	if(m_hNextClipboardViewer == hWndRemove)
+    {
+		m_hNextClipboardViewer = hWndAfter;
+    }
+    // Otherwise, pass the message to the next link.
+	else if (m_hNextClipboardViewer != NULL)
+    {
+		if(m_hNextClipboardViewer != m_hWnd)
+		{
+			::SendMessage(m_hNextClipboardViewer, WM_CHANGECBCHAIN, (WPARAM) hWndRemove, (LPARAM) hWndAfter);
+		}
+		else
+		{
+			m_hNextClipboardViewer = NULL;
+		}
+    }
+}
+
+//Message that the clipboard data has changed
+void CClipboardViewer::OnDrawClipboard() 
+{
+	if( m_bPinging )
+	{
+		m_bPingSuccess = true;
+		return;
+	}
+	
+	if((GetTickCount() - m_lLastCopy) > g_Opt.m_lSaveClipDelay)
+	{
+		// don't process the event when we first attach
+		if( m_pHandler && !m_bCalling_SetClipboardViewer )
+		{
+			if( !::IsClipboardFormatAvailable( theApp.m_cfIgnoreClipboard ) )
+				m_pHandler->OnClipboardChange();
+		}
+	}
+	else
+	{
+		CString cs;
+		cs.Format("Clip copy to fast difference from last copy = %d", (GetTickCount() - m_lLastCopy));
+		Log(cs);
+	}
+	
+	// pass the event to the next Clipboard viewer in the chain
+	if( m_hNextClipboardViewer != NULL )
+	{
+		if(m_hNextClipboardViewer != m_hWnd)
+		{
+			::SendMessage(m_hNextClipboardViewer, WM_DRAWCLIPBOARD, 0, 0);	
+		}
+		else
+		{
+			m_hNextClipboardViewer = NULL;
+		}
+	}
+}
+
+void CClipboardViewer::OnTimer(UINT nIDEvent) 
+{
+	switch(nIDEvent)
+	{
+	case TIMER_ENSURE_VIEWER_IN_CHAIN:
+		EnsureConnected();
+		break;
+	}
+	
+	CWnd::OnTimer(nIDEvent);
+}
+
+LRESULT CClipboardViewer::OnCVGetConnect(WPARAM wParam, LPARAM lParam)
+{
+	return GetConnect();
+}
+
+LRESULT CClipboardViewer::OnCVSetConnect(WPARAM wParam, LPARAM lParam)
+{
+	SetConnect(wParam != FALSE); // convert to bool
+	return TRUE;
+}
+
+LRESULT CClipboardViewer::OnCVIsConnected(WPARAM wParam, LPARAM lParam)
+{
+	return SendPing();
+}

+ 72 - 0
ClipboardViewer.h

@@ -0,0 +1,72 @@
+#if !defined(AFX_CLIPBOARDVIEWER_H__67418FB6_6048_48FA_86D4_F412CACC41B1__INCLUDED_)
+#define AFX_CLIPBOARDVIEWER_H__67418FB6_6048_48FA_86D4_F412CACC41B1__INCLUDED_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+
+
+#define TIMER_ENSURE_VIEWER_IN_CHAIN	6
+
+class CClipboardViewer : public CWnd
+{
+// Construction
+public:
+	CClipboardViewer(CCopyThread* pHandler);
+	virtual ~CClipboardViewer();
+
+// Overrides
+	// ClassWizard generated virtual function overrides
+	//{{AFX_VIRTUAL(CClipboardViewer)
+	//}}AFX_VIRTUAL
+
+// Implementation
+public:
+	void Create();
+
+	HWND	m_hNextClipboardViewer;
+	bool	m_bCalling_SetClipboardViewer;
+	long	m_lReconectCount;
+	bool	m_bIsConnected;  // a cache of the last known state
+	bool	m_bConnect; // the user's requested state for the viewer.
+	// m_bConnect and m_bIsConnected can differ if, e.g., we want to stay
+	//  connected, but are dropped from the chain for some unknown reason.
+
+	// m_pHandler->OnClipboardChange is called when the clipboard changes.
+	CCopyThread*	m_pHandler;
+
+	void Connect();    // connects as a clipboard viewer
+	void Disconnect(); // disconnects as a clipboard viewer
+
+	bool	m_bPinging;
+	bool	m_bPingSuccess;
+	bool SendPing(); // returns true if we are in the chain
+	bool EnsureConnected(); // pings and connects if ping fails
+	void SetCVIgnore(); // puts format "Clipboard Viewer Ignore" on the clipboard
+
+	bool GetConnect() { return m_bConnect; }
+	void SetConnect( bool bConnect );
+
+	long m_lLastCopy;
+
+	// Generated message map functions
+protected:
+	//{{AFX_MSG(CClipboardViewer)
+	afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
+	afx_msg void OnDestroy();
+	afx_msg void OnChangeCbChain(HWND hWndRemove, HWND hWndAfter);
+	afx_msg void OnDrawClipboard();
+	afx_msg void OnTimer(UINT nIDEvent);
+	//}}AFX_MSG
+	afx_msg LRESULT OnCVGetConnect(WPARAM wParam, LPARAM lParam);
+	afx_msg LRESULT OnCVSetConnect(WPARAM wParam, LPARAM lParam);
+	afx_msg LRESULT OnCVIsConnected(WPARAM wParam, LPARAM lParam);
+	DECLARE_MESSAGE_MAP()
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+//{{AFX_INSERT_LOCATION}}
+// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
+
+#endif // !defined(AFX_CLIPBOARDVIEWER_H__67418FB6_6048_48FA_86D4_F412CACC41B1__INCLUDED_)

+ 250 - 0
CopyThread.cpp

@@ -0,0 +1,250 @@
+// CopyThread.cpp : implementation file
+//
+
+#include "stdafx.h"
+#include "cp_main.h"
+#include "CopyThread.h"
+
+#ifdef _DEBUG
+#define new DEBUG_NEW
+#undef THIS_FILE
+static char THIS_FILE[] = __FILE__;
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// CCopyThread
+
+IMPLEMENT_DYNCREATE(CCopyThread, CWinThread)
+
+CCopyThread::CCopyThread()
+{
+	m_bQuit = false;
+	m_bAutoDelete = false;
+	
+	m_bConfigChanged = false;
+	m_pClips = new CClipList;
+	m_pClipboardViewer = new CClipboardViewer(this);
+	::InitializeCriticalSection(&m_CS);
+}
+
+CCopyThread::~CCopyThread()
+{
+	m_LocalConfig.DeleteTypes();
+	m_SharedConfig.DeleteTypes();
+	DELETE_PTR( m_pClipboardViewer );
+	if( m_pClips )
+		ASSERT( m_pClips->GetCount() == 0 );
+	DELETE_PTR( m_pClips );
+	::DeleteCriticalSection(&m_CS);
+}
+
+BOOL CCopyThread::InitInstance()
+{
+	SetThreadName(m_nThreadID, "COPY");
+
+	// the window is created within this thread and therefore uses its message queue
+	m_pClipboardViewer->Create();
+
+	return TRUE;
+}
+
+int CCopyThread::ExitInstance()
+{
+	m_pClipboardViewer->Disconnect();
+
+	return CWinThread::ExitInstance();
+}
+
+BEGIN_MESSAGE_MAP(CCopyThread, CWinThread)
+	//{{AFX_MSG_MAP(CCopyThread)
+		// NOTE - the ClassWizard will add and remove mapping macros here.
+	//}}AFX_MSG_MAP
+END_MESSAGE_MAP()
+
+/////////////////////////////////////////////////////////////////////////////
+// CCopyThread message handlers
+
+
+// Called within Copy Thread:
+void CCopyThread::OnClipboardChange()
+{
+	SyncConfig(); // synchronize with the main thread's copy configuration
+	
+	// if we are told not to copy on change, then we have nothing to do.
+	if( !m_LocalConfig.m_bCopyOnChange )
+		return;
+	
+	CClip* pClip = new CClip;
+	bool bResult = pClip->LoadFromClipboard( m_LocalConfig.m_pSupportedTypes );
+	
+	if(!bResult)
+	{
+		delete pClip;
+		return; // error
+	}
+	
+	AddToClips(pClip);
+	
+	if(m_LocalConfig.m_bAsyncCopy)
+		::PostMessage(m_LocalConfig.m_hClipHandler, WM_CLIPBOARD_COPIED, (WPARAM)pClip, 0);
+	else
+		::SendMessage(m_LocalConfig.m_hClipHandler, WM_CLIPBOARD_COPIED, (WPARAM)pClip, 0);
+	
+}
+
+void CCopyThread::SyncConfig()
+{
+	// atomic read
+	if(m_bConfigChanged)
+	{
+		CClipTypes* pTypes = NULL;
+		Hold();
+		
+		pTypes = m_LocalConfig.m_pSupportedTypes;
+		
+		m_LocalConfig = m_SharedConfig;
+		
+		// NULL means that it shouldn't have been sync'ed
+		if( m_SharedConfig.m_pSupportedTypes == NULL )
+		{	// let m_LocalConfig keep its types
+			m_LocalConfig.m_pSupportedTypes = pTypes; // undo sync
+			pTypes = NULL; // nothing to delete
+		}
+		else
+			m_SharedConfig.m_pSupportedTypes = NULL; // now owned by LocalConfig
+		
+		Release();
+		// delete old types
+		if( pTypes )
+			delete pTypes;
+	}
+}
+
+void CCopyThread::AddToClips(CClip* pClip)
+{
+	Hold();
+	if(!m_pClips)
+		m_pClips = new CClipList;
+
+	m_pClips->AddTail(pClip); // m_pClips now owns pClip
+	Release();
+}
+
+// Shared (use thread-safe access functions below)
+// Called within Main thread:
+bool CCopyThread::IsClipboardViewerConnected()
+{
+	ASSERT(m_pClipboardViewer && m_pClipboardViewer->m_hWnd);
+	return ::SendMessage(m_pClipboardViewer->m_hWnd, WM_CV_IS_CONNECTED, 0, 0) != FALSE;
+}
+
+bool CCopyThread::GetConnectCV()
+{
+	ASSERT(m_pClipboardViewer && m_pClipboardViewer->m_hWnd);
+	return ::SendMessage(m_pClipboardViewer->m_hWnd, WM_CV_GETCONNECT, 0, 0) != FALSE;
+}
+
+void CCopyThread::SetConnectCV(bool bConnect)
+{
+	ASSERT(m_pClipboardViewer && m_pClipboardViewer->m_hWnd);
+	::SendMessage( m_pClipboardViewer->m_hWnd, WM_CV_SETCONNECT, bConnect, 0);
+}
+
+CClipList* CCopyThread::GetClips()
+{
+	Hold();
+	CClipList* pRet = m_pClips;
+	m_pClips = NULL;
+	Release();
+	return pRet;
+}
+void CCopyThread::SetSupportedTypes( CClipTypes* pTypes )
+{
+	Hold();
+
+	if(m_SharedConfig.m_pSupportedTypes)
+	{
+		DELETE_PTR(m_SharedConfig.m_pSupportedTypes);
+	}
+
+	m_SharedConfig.m_pSupportedTypes = pTypes;
+	m_bConfigChanged = true;
+
+	Release();
+}
+
+HWND CCopyThread::SetClipHandler(HWND hWnd)
+{
+	Hold();
+
+	HWND hRet = m_SharedConfig.m_hClipHandler;
+	m_SharedConfig.m_hClipHandler = hWnd;
+	m_bConfigChanged = (hRet != hWnd);
+
+	Release();
+
+	return hRet;
+}
+HWND CCopyThread::GetClipHandler()
+{
+	Hold();
+
+	HWND hRet = m_SharedConfig.m_hClipHandler;
+
+	Release();
+
+	return hRet;
+}
+bool CCopyThread::SetCopyOnChange(bool bVal)
+{
+	Hold();
+
+	bool bRet = m_SharedConfig.m_bCopyOnChange;
+	m_SharedConfig.m_bCopyOnChange = bVal;
+	m_bConfigChanged = (bRet != bVal);
+
+	Release();
+
+	return bRet;
+}
+bool CCopyThread::GetCopyOnChange()
+{
+	Hold();
+	bool bRet = m_SharedConfig.m_bCopyOnChange;
+	Release();
+
+	return bRet;
+}
+bool CCopyThread::SetAsyncCopy(bool bVal)
+{
+	Hold();
+	bool bRet = m_SharedConfig.m_bAsyncCopy;
+	m_SharedConfig.m_bAsyncCopy = bVal;
+	m_bConfigChanged = (bRet != bVal);
+	Release();
+
+	return bRet;
+}
+bool CCopyThread::GetAsyncCopy()
+{
+	Hold();
+	bool bRet = m_SharedConfig.m_bAsyncCopy;
+	Release();
+
+	return bRet;
+}
+
+void CCopyThread::Init(CCopyConfig cfg)
+{
+	ASSERT(m_LocalConfig.m_pSupportedTypes == NULL);
+	m_LocalConfig = m_SharedConfig = cfg;
+	// let m_LocalConfig own the m_pSupportedTypes
+	m_SharedConfig.m_pSupportedTypes = NULL;
+}
+
+bool CCopyThread::Quit()
+{
+	m_bQuit = true;
+	m_pClipboardViewer->PostMessage( WM_QUIT );
+	return CWinThread::PostThreadMessage( WM_QUIT, NULL, NULL ) != FALSE;
+}

+ 120 - 0
CopyThread.h

@@ -0,0 +1,120 @@
+#if !defined(AFX_COPYTHREAD_H__C6766F04_0111_4314_986A_A0E02FF3B322__INCLUDED_)
+#define AFX_COPYTHREAD_H__C6766F04_0111_4314_986A_A0E02FF3B322__INCLUDED_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+
+#include "ClipboardViewer.h"
+
+struct CCopyConfig
+{
+public:
+	// WM_CLIPBOARD_COPIED is sent to this window when a copy is made.
+	HWND        m_hClipHandler;
+	// true to use PostMessage (asynchronous)
+	// false to use SendMessage (synchronous)
+	bool        m_bAsyncCopy;
+	// true to create a copy of the clipboard contents when it changes
+	// false to ignore changes in the clipboard
+	bool        m_bCopyOnChange;
+	// the supported types which are copied from the clipboard when it changes.
+	CClipTypes* m_pSupportedTypes; // ONLY accessed from CopyThread
+
+	CCopyConfig( HWND hClipHandler = NULL,
+	             bool bAsyncCopy = false,
+				 bool bCopyOnChange = false,
+				 CClipTypes* pSupportedTypes = NULL )
+	{
+		m_hClipHandler = hClipHandler;
+		m_bAsyncCopy = bAsyncCopy;
+		m_bCopyOnChange = bCopyOnChange;
+		m_pSupportedTypes = pSupportedTypes;
+	}
+
+	void DeleteTypes()
+	{
+		if( m_pSupportedTypes )
+		{
+			delete m_pSupportedTypes;
+			m_pSupportedTypes = NULL;
+		}
+	}
+};
+
+class CCopyThread : public CWinThread
+{
+	DECLARE_DYNCREATE(CCopyThread)
+public:
+	CCopyThread();
+	virtual ~CCopyThread();
+
+// Attributes
+public:
+
+// Operations
+public:
+
+	bool m_bQuit;
+
+	// critical section is held whenever shared data is changed 
+	CRITICAL_SECTION		m_CS;
+	void Hold()		{ ::EnterCriticalSection(&m_CS); }
+	void Release()	{ ::LeaveCriticalSection(&m_CS); }
+
+// CopyThread Local (accessed from this CopyThread)
+	// window owned by this thread which handles clipboard viewer messages
+	CClipboardViewer*   m_pClipboardViewer; // permanent during lifetime of thread
+	CCopyConfig         m_LocalConfig;
+
+	// Called within Copy Thread:
+	void OnClipboardChange(); // called by ClipboardViewer
+	void SyncConfig(); // safely syncs m_LocalConfig with m_SharedConfig
+	void AddToClips( CClip* pClip ); // after this, pClip is owned by m_pClips
+
+// Shared (use thread-safe access functions below)
+	CCopyConfig         m_SharedConfig; 
+	bool                m_bConfigChanged; // true if m_SharedConfig was changed.
+	CClipList*          m_pClips; // snapshots of the clipboard when it changed.
+
+	// Called within Main thread:
+	bool IsClipboardViewerConnected();
+	bool GetConnectCV();
+	void SetConnectCV( bool bConnect );
+
+	CClipList* GetClips(); // caller owns the returned CClipList
+	void SetSupportedTypes( CClipTypes* pTypes ); // CopyThread will own pTypes
+	HWND SetClipHandler( HWND hWnd ); // returns previous value
+	HWND GetClipHandler();
+	bool SetCopyOnChange( bool bVal ); // returns previous value
+	bool GetCopyOnChange();
+	bool SetAsyncCopy( bool bVal ); // returns previous value
+	bool GetAsyncCopy();
+
+// Main thread
+	void Init( CCopyConfig cfg );
+	bool Quit();
+
+// Overrides
+	// ClassWizard generated virtual function overrides
+	//{{AFX_VIRTUAL(CCopyThread)
+	public:
+	virtual BOOL InitInstance();
+	virtual int ExitInstance();
+	//}}AFX_VIRTUAL
+
+
+	// Generated message map functions
+	//{{AFX_MSG(CCopyThread)
+		// NOTE - the ClassWizard will add and remove member functions here.
+	//}}AFX_MSG
+
+	DECLARE_MESSAGE_MAP()
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+//{{AFX_INSERT_LOCATION}}
+// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
+
+#endif // !defined(AFX_COPYTHREAD_H__C6766F04_0111_4314_986A_A0E02FF3B322__INCLUDED_)

+ 1 - 1
ProcessPaste.h

@@ -10,7 +10,7 @@
 #endif // _MSC_VER > 1000
 #endif // _MSC_VER > 1000
 
 
 #include "ArrayEx.h"
 #include "ArrayEx.h"
-#include "ProcessCopy.h"
+#include "Clip.h"
 
 
 // returns the increment necessary to fit "count" elements between (dStart,dEnd)
 // returns the increment necessary to fit "count" elements between (dStart,dEnd)
 // if this returns 0, then "count" elements cannot fit between (dStart,dEnd).
 // if this returns 0, then "count" elements cannot fit between (dStart,dEnd).