123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 |
- #include "stdafx.h"
- #include "CP_Main.h"
- #include "ProcessPaste.h"
- #ifdef _DEBUG
- #undef THIS_FILE
- static char THIS_FILE[]=__FILE__;
- #define new DEBUG_NEW
- #endif
- // returns the increment necessary to fit "count" elements between (dStart,dEnd)
- // if this returns 0, then "count" elements cannot fit between (dStart,dEnd).
- double GetFitIncrement( int count, double dStart, double dEnd )
- {
- VERIFY( count > 0 && dStart <= dEnd );
- double dIncrement = (dEnd - dStart) / ((double) (count+1));
- if( dIncrement == 0 )
- return 0;
- // verify that each element is unique
- // I'm not sure if this is necessary, but I'm doing it just to be on the safe side
- // I think the resolution of floating points are variable.
- // i.e. we cannot depend upon an increment always effecting a change.
- int i = 0;
- double dPrev = dStart;
- double dOrder = dStart + dIncrement;
- while( dOrder < dEnd )
- {
- i++;
- if( dOrder <= dPrev )
- return 0;
- dPrev = dOrder;
- dOrder = dOrder + dIncrement;
- }
- // verify count (and that we're not too close to dEnd)
- if( i < count )
- return 0;
- return dIncrement;
- }
- /*------------------------------------------------------------------*\
- ID based Globals
- \*------------------------------------------------------------------*/
- BOOL MarkClipAsPasted(long lID)
- {
- CGetSetOptions::SetTripPasteCount(-1);
- CGetSetOptions::SetTotalPasteCount(-1);
- if( !g_Opt.m_bUpdateTimeOnPaste )
- return FALSE;
- try
- {
- //Update the time it was copied so that it appears at the top of the
- //paste list. Items are sorted by this time.
- CMainTable ctMain;
- ctMain.Open("SELECT * FROM Main WHERE lID = %d", lID);
- ctMain.Edit();
-
- CTime now = CTime::GetCurrentTime();
- ctMain.m_lDate = (long)now.GetTime();
-
- ctMain.Update();
- ctMain.Close();
- return TRUE;
- }
- catch(CDaoException *e)
- {
- ASSERT(FALSE);
- e->Delete();
- }
- return FALSE;
- }
- long NewGroupID( long lParentID, CString text )
- {
- long lID=0;
- CTime time;
- time = CTime::GetCurrentTime();
- try
- {
- CMainTable recset;
- recset.Open(dbOpenTable, "Main");
- recset.AddNew(); // overridden to set m_lID to the new autoincr number
- lID = recset.m_lID;
- recset.m_lDate = (long) time.GetTime();
- if( text != "" )
- recset.m_strText = text;
- else
- recset.m_strText = time.Format("NewGroup %y/%m/%d %H:%M:%S");
- recset.m_bIsGroup = TRUE;
- recset.m_lParentID = lParentID;
- recset.Update();
- // recset.SetBookmark( recset.GetLastModifiedBookmark() );
- // lID = recset.m_lID;
- recset.Close();
- }
- catch(CDaoException* e)
- {
- e->ReportError();
- ASSERT(FALSE);
- e->Delete();
- return 0;
- }
- return lID;
- }
- // !!!!!! UNTESTED SQL
- // creates copies of all lSrc Data and returns the copy's lDataID (or 0 on fail)
- long NewCopyDataID( long lSrc )
- {
- long lDataID = 0;
- CString sql;
- // create the copies
- sql.Format(
- "INSERT INTO Data (strClipBoardFormat, ooData) "
- "SELECT strClipBoardFormat, ooData FROM Data "
- "WHERE lDataID = %d", lSrc );
- // each lID should be assigned a unique ID (autoinc)
- // lDataID should be assigned to 0 (default value) or NULL
- ExecuteSQL( sql );
- // assign lDataID to the first record's lID
- try
- {
- CDataTable recs;
- recs.Open("SELECT * FROM Data WHERE lDataID = 0 OR lDataID IS NULL");
- if( !recs.IsEOF() )
- lDataID = recs.m_lID;
- recs.Close();
- }
- CATCHDAO
- // assign the copies to lDest
- sql.Format(
- "UPDATE Data "
- "SET lDataID = %d "
- "WHERE lDataID = 0 OR lDataID IS NULL", lDataID );
- ExecuteSQL( sql );
- return lDataID;
- }
- // deletes the given item
- BOOL DeleteID( long lID, bool bDisband )
- {
- long lDataID = 0;
- int i = 0;
- COleVariant varKey( (long) 0 ); // VT_I4
- try
- {
- CMainTable recs;
- recs.Open( dbOpenTable, "Main" );
- recs.SetCurrentIndex("lID"); // set "Seek" to use this index.
- varKey.lVal = lID;
- // Goto the record whose [lID] field == varKey
- if( !recs.Seek( _T("="), &varKey ) )
- {
- ASSERT(0);
- return FALSE;
- }
- lDataID = recs.m_lDataID;
- // must delete this record first so that DeleteDataID can properly
- // determine if any other Clip is using the same Data.
- recs.Delete();
- if( recs.m_bIsGroup )
- DeleteGroupID( lID, bDisband );
- else
- DeleteDataID( lDataID );
- recs.Close();
- }
- CATCHDAO
- return TRUE;
- }
- // deletes all items in the group
- BOOL DeleteGroupID( long lGroupID, bool bDisband )
- {
- if( bDisband )
- {
- return ExecuteSQL(
- StrF("UPDATE Main SET lParentID = 0 WHERE lParentID = %d", lGroupID) );
- }
- try
- {
- CMainTable recs;
- recs.Open( "SELECT * FROM Main WHERE lParentID = %d", lGroupID );
- while( !recs.IsEOF() )
- {
- DeleteID( recs.m_lID );
- recs.MoveNext();
- }
- recs.Close();
- }
- CATCHDAO
- return TRUE;
- }
- // deletes all data for the given ID
- // NOTE!: this checks to see if there are any records in Main which reference
- // the given lDataID, and if so, does NOT delete the Data.
- // THEREFORE, Main records should be deleted BEFORE calling this function.
- BOOL DeleteDataID( long lDataID )
- {
- CMainTable recs;
- BOOL bEmpty;
- // check to see if the data is referenced by any Clips
- recs.Open( "SELECT * FROM Main WHERE lDataID = %d", lDataID );
- bEmpty = recs.IsEOF();
- recs.Close();
- // if the data is no longer referenced, delete the data
- if( bEmpty )
- return ExecuteSQL( StrF("DELETE FROM Data WHERE lDataID = %d", lDataID) );
- // else, there are more clips which use the data
- return TRUE;
- }
- BOOL DeleteAllIDs()
- {
- CMainTable MainTable;
- CDataTable DataTable;
- MainTable.DeleteAll();
- DataTable.DeleteAll();
- if( CompactDatabase() )
- {
- RepairDatabase();
- }
- return TRUE;
- }
- // all "formatIDs" (Data.lID) must be elements of the "lDataID" (Data.lDataID) set
- BOOL DeleteFormats( long lDataID, ARRAY& formatIDs )
- {
- long lNewTotalSize = 0;
- bool bIsHeadDeleted = false;
- long lNewDataID = 0;
- if( formatIDs.GetSize() <= 0 )
- return TRUE;
- formatIDs.SortAscending();
- try
- {
- CDataTable recs;
- recs.Open("SELECT * FROM Data WHERE lDataID = %d", lDataID);
- //Go through the data table and find the deleted items
- recs.MoveFirst();
- while(!recs.IsEOF())
- {
- if(formatIDs.Find(recs.m_lID))
- {
- // if we are deleting the head, then we need a new head
- // actually, this might not be absolutely necessary if
- // lID autoincr field never reuses IDs.
- if( lDataID == recs.m_lID )
- bIsHeadDeleted = true;
- recs.Delete();
- }
- else
- lNewTotalSize += recs.m_ooData.m_dwDataLength;
- recs.MoveNext();
- }
- if( bIsHeadDeleted )
- {
- recs.MoveFirst();
- if( !recs.IsEOF() )
- lNewDataID = recs.m_lID;
- recs.Close();
- // update the Main Table with lNewTotalSize and lNewDataID
- ExecuteSQL( StrF(
- "UPDATE Main SET lTotalCopySize = %d, lDataID = %d WHERE lDataID = %d",
- lNewTotalSize, lNewDataID, lDataID) );
- // update the Data Table with lNewDataID
- ExecuteSQL( StrF(
- "UPDATE Data SET lDataID = %d WHERE lDataID = %d",
- lNewDataID, lDataID) );
- }
- else // still update the total copy size
- {
- recs.Close();
- ExecuteSQL( StrF(
- "UPDATE Main SET lTotalCopySize = %d WHERE lDataID = %d",
- lNewTotalSize, lDataID) );
- }
- }
- CATCHDAO
- return TRUE;
- }
- /*------------------------------------------------------------------*\
- CClipIDs
- \*------------------------------------------------------------------*/
- //-------------------
- // PASTING FUNCTIONS
- //-------------------
- // allocate an HGLOBAL of the given Format Type representing these Clip IDs.
- HGLOBAL CClipIDs::Render( UINT cfType )
- {
- int count = GetSize();
- if( count <= 0 )
- return 0;
- if( count == 1 )
- return CClip::LoadFormat( ElementAt(0), cfType );
- CString text = AggregateText( CF_TEXT, "\r\n" );
- return NewGlobalP( (void*)(LPCSTR) text, text.GetLength()+1 );
- }
- void CClipIDs::GetTypes( CClipTypes& types )
- {
- int count = GetSize();
- types.RemoveAll();
- if( count > 1 )
- types.Add( CF_TEXT );
- else if( count == 1 )
- CClip::LoadTypes( ElementAt(0), types );
- }
- // Aggregates the cfType Format Data of the Clip IDs in this array, assuming
- // each Format is NULL terminated and placing pSeparator between them.
- // This assumes that the given cfType is a null terminated text type.
- CString CClipIDs::AggregateText( UINT cfType, char* pSeparator )
- {
- CString csSQL;
- CDataTable recset;
- CString text;
- char* pData = NULL;
- DWORD len;
- DWORD maxLen;
- // maybe we should sum up the "recset.m_ooData.m_dwDataLength" of all IDs first
- // in order to determine the max space required?? Or would that be wastefull?
- // allocate a large initial buffer to minimize realloc for concatenations
- text.GetBuffer(1000);
- text.ReleaseBuffer(0);
- int numIDs = GetSize();
- int* pIDs = GetData();
- csSQL.Format(
- "SELECT Data.* FROM Data "
- "INNER JOIN Main ON Main.lDataID = Data.lDataID "
- "WHERE Data.strClipBoardFormat = \'%s\' "
- "AND Main.lID = %%d",
- GetFormatName(cfType));
- try
- {
- for( int i=0; i < numIDs; i++ )
- {
- recset.Open( csSQL, pIDs[i] );
- if( !recset.IsBOF() && !recset.IsEOF() )
- {
- maxLen = recset.m_ooData.m_dwDataLength;
- if( maxLen == 0 )
- continue;
- pData = (char*) GlobalLock(recset.m_ooData.m_hData);
- ASSERT( pData );
- // verify that pData is null terminated
- // do a quick check to see if the last character is null
- if( pData[maxLen-1] != '\0' )
- {
- for( len=0; len < maxLen && pData[len] != '\0'; len++ ) {}
- // if it is not null terminated, skip this item
- if( len >= maxLen )
- continue;
- }
- text += pData;
- GlobalUnlock(recset.m_ooData.m_hData);
- if( pSeparator )
- text += pSeparator;
- }
- recset.Close();
- }
- }
- CATCHDAO
- return text;
- }
- //----------------------------------------------
- // ELEMENT (Clip or Group) MANAGEMENT FUNCTIONS
- //----------------------------------------------
- // returns the address of the given id in this array or NULL.
- long* CClipIDs::FindID( long lID )
- {
- int count = GetSize();
- long* pID = (long*) GetData();
- for( int i=0; i < count; i++ )
- {
- if( *pID == lID )
- return pID;
- pID++;
- }
- return NULL;
- }
- // Blindly Moves IDs into the lParentID Group sequentially with the given order
- // (i.e. this does not check to see if the IDs' order conflict)
- // if( dIncrement < 0 ), this does not change the order
- BOOL CClipIDs::MoveTo( long lParentID, double dFirst, double dIncrement )
- {
- int count = GetSize();
- int i = 0;
- COleVariant varKey( (long) 0 ); // VT_I4
- double dOrder = dFirst;
- BOOL bChangeOrder = (dIncrement >= 0);
- try
- {
- CMainTable recs;
- recs.Open( dbOpenTable, "Main" );
- recs.SetCurrentIndex("lID"); // set "Seek" to use this index.
- // for each id, assign lParentID
- while( i < count )
- {
- varKey.lVal = ElementAt(i);
- // Goto the record whose [lID] field == varKey
- if( !recs.Seek( _T("="), &varKey ) )
- {
- ASSERT(0);
- break;
- }
- // don't allow an item to become its own parent
- // NOTE!: deeper recursion is not checked, so it is theoretically
- // possible for: A -> B -> A
- if( recs.m_lID != lParentID )
- {
- recs.Edit();
- recs.m_lParentID = lParentID;
- if( bChangeOrder )
- recs.m_dOrder = dOrder;
- recs.Update();
- dOrder = dOrder + dIncrement;
- }
- i++;
- }
- recs.Close();
- }
- CATCHDAO
- return (i == count);
- }
- // reorders the "lParentID" Group, inserting before the given id.
- // if the id cannot be found, this appends the IDs.
- BOOL CClipIDs::ReorderGroupInsert( long lParentID, long lInsertBeforeID )
- {
- int count = GetSize();
- int i = 1; // start the enumeration
- int insert = 0;
- BOOL bResult;
- try
- {
- MoveTo(-1); // move all elements outside any group
- CMainTable recs;
- recs.m_strSort = "dOrder ASC";
- recs.Open( "SELECT * FROM Main WHERE lParentID = %d", lParentID );
- while( !recs.IsEOF() )
- {
- if( recs.m_lID == lInsertBeforeID )
- {
- insert = i;
- i = insert + count;
- }
- recs.Edit();
- recs.m_dOrder = i;
- recs.Update();
- i++;
- recs.MoveNext();
- }
- recs.Close();
- if( insert == 0 )
- insert = i;
- // move the elements into their proper position in the group
- bResult = MoveTo( lParentID, (double) insert, (double) 1 );
- }
- CATCHDAO
- return bResult;
- }
- // Empties this array and fills it with the elements of the given group ID
- BOOL CClipIDs::LoadElementsOf( long lGroupID )
- {
- int fldID; // index of the lID field
- COleVariant varID; // value of the lID field
- int count = 0;
- SetSize(0);
- try
- {
- CMainTable recs;
- recs.SetBindFields(false);
- recs.Open("SELECT lID FROM Main WHERE lParentID = %d", lGroupID);
- fldID = GetFieldPos(recs,"lID");
- VERIFY( fldID == 0 ); // should be 0 since it is the only field
- while( !recs.IsEOF() )
- {
- recs.GetFieldValue( fldID, varID );
- Add( varID.lVal );
- recs.MoveNext();
- }
- recs.Close();
- }
- CATCHDAO
- return GetSize();
- }
- // Creates copies (duplicates) of all items in this array and assigns the
- // lParentID of the copies to the given "lParentID" group.
- // - if lParentID <= 0, then the copies have the same parent as the source
- // - pCopies is filled with the corresponding duplicate IDs.
- // - pAddNewTable and pSeekTable are used for more efficient recursion.
- // - the primary overhead for recursion is one ID array per level deep.
- // an alternative design would be to have one CMainTable per level deep,
- // but I thought that might be too costly, so I implemented it this way.
- BOOL CClipIDs::CopyTo( long lParentID, CClipIDs* pCopies,
- CMainTable* pAddNewTable, CMainTable* pSeekTable )
- {
- int count = GetSize();
- if( pCopies )
- {
- pCopies->SetSize( count );
- // initialize all IDs to 0
- for( int i=0; i < count; i++ )
- pCopies->ElementAt(i) = 0;
- }
- if( count == 0 )
- return TRUE;
- // for Seeking
- BOOL bSeekFailed = FALSE;
- COleVariant varID( (long) 0, VT_I4 );
- // for recursing into groups
- CMainTable* pAddTable = pAddNewTable;
- CMainTable* pTable = pSeekTable;
- CClipIDs groupIDs;
- long lCopyID;
- try
- {
- if( pTable == NULL )
- {
- pTable = new CMainTable;
- pTable->Open(dbOpenTable,"Main");
- pTable->SetCurrentIndex("lID");
- }
- if( pAddTable == NULL )
- {
- pAddTable = new CMainTable;
- pAddTable->Open(dbOpenTable,"Main");
- // pAddTable->SetCurrentIndex("lID");
- }
- for( int i=0; i < count; i++ )
- {
- varID.lVal = ElementAt(i);
- // Find first record whose [lID] field == lID
- if( pTable->Seek(_T("="), &varID) )
- {
- // copy the record
- pAddTable->AddNew(); // overridden to fetch autoincr lID
- lCopyID = pAddTable->m_lID;
- pAddTable->CopyRec( *pTable ); // copy the fields
- if( lParentID > 0 ) // if valid, assign the given parent
- pAddTable->m_lParentID = lParentID;
- pAddTable->Update();
-
- // if it's a group, copy its elements
- if( pTable->m_bIsGroup )
- {
- groupIDs.LoadElementsOf( pTable->m_lID );
- // RECURSION
- groupIDs.CopyTo( lCopyID, NULL, pAddTable, pTable );
- }
- }
- else
- {
- ASSERT(0);
- bSeekFailed = TRUE;
- break;
- }
- if( pCopies )
- pCopies->ElementAt(i) = lCopyID;
- }
- // if we were not given the table, then we created it, so we must delete it
- if( pAddTable && pAddNewTable == NULL )
- {
- pAddTable->Close();
- delete pAddTable;
- }
- if( pTable && pSeekTable == NULL )
- {
- pTable->Close();
- delete pTable;
- }
- }
- CATCHDAO
- return !bSeekFailed;
- }
- BOOL CClipIDs::DeleteIDs( bool bDisband )
- {
- CPopup status(0,0,::GetForegroundWindow());
- bool bAllowShow;
- bAllowShow = IsAppWnd(::GetForegroundWindow());
- BOOL bRet = TRUE;
- int count = GetSize();
- if(count <= 0)
- return FALSE;
- for( int i=0; i < count; i++ )
- {
- if( bAllowShow )
- status.Show( StrF("Deleting %d out of %d...",i+1,count) );
- bRet = bRet && DeleteID( ElementAt(i), bDisband );
- }
- return bRet;
- }
- /*------------------------------------------------------------------*\
- COleClipSource
- \*------------------------------------------------------------------*/
- //IMPLEMENT_DYNAMIC(COleClipSource, COleDataSource)
- COleClipSource::COleClipSource()
- {
- }
- COleClipSource::~COleClipSource()
- {
- }
- BOOL COleClipSource::DoDelayRender()
- {
- CClipTypes types;
- m_ClipIDs.GetTypes( types );
- int count = types.GetSize();
- for( int i=0; i < count; i++ )
- DelayRenderData( types[i] );
- return count;
- }
- BOOL COleClipSource::DoImmediateRender()
- {
- int count = m_ClipIDs.GetSize();
- if( count <= 0 )
- return 0;
- if( count == 1 )
- {
- CClipFormats formats;
- CClipFormat* pCF;
- CClip::LoadFormats( m_ClipIDs[0], formats );
- count = formats.GetSize(); // reusing "count"
- for( int i=0; i < count; i++ )
- {
- pCF = &formats[i];
- CacheGlobalData( pCF->m_cfType, pCF->m_hgData );
- pCF->m_hgData = 0; // OLE owns it now
- }
- formats.RemoveAll();
- return count;
- }
- HGLOBAL hGlobal;
- CString text = m_ClipIDs.AggregateText( CF_TEXT, "\r\n" );
- hGlobal = NewGlobalP( (void*)(LPCSTR) text, text.GetLength()+1 );
- CacheGlobalData( CF_TEXT, hGlobal );
- return hGlobal != 0;
- }
- BEGIN_MESSAGE_MAP(COleClipSource, COleDataSource)
- END_MESSAGE_MAP()
- // COleClipSource message handlers
- BOOL COleClipSource::OnRenderGlobalData(LPFORMATETC lpFormatEtc, HGLOBAL* phGlobal)
- {
- HGLOBAL hData = m_ClipIDs.Render( lpFormatEtc->cfFormat );
- if( !hData )
- return FALSE;
- // if phGlobal is null, we can just give the allocated mem
- // else, our data must fit within the GlobalSize(*phGlobal)
- if( *phGlobal == 0 )
- *phGlobal = hData;
- else
- {
- UINT len = min( ::GlobalSize(*phGlobal), ::GlobalSize(hData) );
- if( len )
- CopyToGlobalHH( *phGlobal, hData, len );
- ::GlobalFree( hData );
- }
- return TRUE;
- }
- /*------------------------------------------------------------------*\
- CProcessPaste
- \*------------------------------------------------------------------*/
- CProcessPaste::CProcessPaste() : m_bDeleteOle(true)
- {
- m_pOle = new COleClipSource;
- }
- CProcessPaste::~CProcessPaste()
- {
- if( m_bDeleteOle )
- delete m_pOle;
- }
- BOOL CProcessPaste::DoPaste()
- {
- if( m_pOle->DoImmediateRender() )
- {
- // if we are pasting a single element, do not handle clipboard data change
- // (the element is already in the db and its lDate is updated by MarkAsPasted())
- if( GetClipIDs().GetSize() == 1 )
- {
- m_pOle->CacheGlobalData(theApp.m_cfIgnoreClipboard, NewGlobalP("Ignore", 8));
- m_pOle->SetClipboard();
- }
- else // we are pasting a new aggregate text
- {
- if( g_Opt.m_bSaveMultiPaste )
- m_pOle->SetClipboard();
- else
- {
- m_pOle->CacheGlobalData(theApp.m_cfIgnoreClipboard, NewGlobalP("Ignore", 8));
- m_pOle->SetClipboard();
- }
- }
- m_bDeleteOle = false; // m_pOle is managed by the OLE clipboard now
- theApp.SendPaste();
- MarkAsPasted();
- return TRUE;
- }
- return FALSE;
- }
- BOOL CProcessPaste::DoDrag()
- {
- m_pOle->DoDelayRender();
- DROPEFFECT de = m_pOle->DoDragDrop( DROPEFFECT_COPY );
- if( de != DROPEFFECT_NONE )
- {
- MarkAsPasted();
- return TRUE;
- }
- return FALSE;
- }
- void CProcessPaste::MarkAsPasted()
- {
- CClipIDs& clips = GetClipIDs();
- if( clips.GetSize() == 1 )
- MarkClipAsPasted( clips.ElementAt(0) );
- }
|