Forráskód Böngészése

Shared Data, Groups, etc.

git-svn-id: svn://svn.code.sf.net/p/ditto-cp/code/trunk@42 595ec19a-5cb4-439b-94a8-42fb3063c22c
ingenuus 22 éve
szülő
commit
a8b84a7e2d
26 módosított fájl, 2066 hozzáadás és 694 törlés
  1. 172 16
      CP_Main.cpp
  2. 22 1
      CP_Main.h
  3. 150 130
      CP_Main.rc
  4. 52 2
      Changes.txt
  5. 7 34
      CopyProperties.cpp
  6. 36 37
      DataTable.cpp
  7. 7 4
      DataTable.h
  8. 294 57
      DatabaseUtilities.cpp
  9. 24 9
      DatabaseUtilities.h
  10. 93 2
      MainTable.cpp
  11. 15 4
      MainTable.h
  12. 121 69
      Misc.cpp
  13. 30 11
      Misc.h
  14. 2 2
      OptionsKeyBoard.cpp
  15. 3 0
      OptionsQuickPaste.cpp
  16. 1 0
      OptionsQuickPaste.h
  17. 12 21
      OptionsStats.cpp
  18. 81 139
      ProcessCopy.cpp
  19. 3 6
      ProcessCopy.h
  20. 558 6
      ProcessPaste.cpp
  21. 52 1
      ProcessPaste.h
  22. 84 56
      QListCtrl.cpp
  23. 3 3
      QListCtrl.h
  24. 227 80
      QPasteWnd.cpp
  25. 10 1
      QPasteWnd.h
  26. 7 3
      Resource.h

+ 172 - 16
CP_Main.cpp

@@ -45,6 +45,15 @@ CCP_MainApp::CCP_MainApp()
 	m_bShowCopyProperties = false;
 	m_bRemoveOldEntriesPending = false;
 
+	m_IC_bCopy = false;
+
+	m_GroupDefaultID = 0;
+	m_GroupID = 0;
+	m_GroupParentID = 0;
+	m_GroupText = "History";
+
+	m_FocusID = -1; // -1 == keep previous position, 0 == go to latest ID
+
 	m_pDatabase = NULL;
 	// Place all significant initialization in InitInstance
 }
@@ -83,8 +92,8 @@ BOOL CCP_MainApp::InitInstance()
 
 	AfxOleInit();
 
-	if(DoCleanups() == FALSE)
-		return TRUE;
+//	if(DoCleanups() == FALSE)
+//		return TRUE;
 
 	CMainFrame* pFrame = new CMainFrame;
 	m_pMainWnd = m_pMainFrame = pFrame;
@@ -320,17 +329,160 @@ void CCP_MainApp::OnCopyCompleted(long lLastID, int count)
 	CGetSetOptions::SetTripCopyCount( -count );
 	CGetSetOptions::SetTotalCopyCount( -count );
 
+	// if we are in the History group, focus on the latest copy
+	if( m_GroupID == 0 )
+		m_FocusID = 0;
+
 	RefreshView();
 	ShowCopyProperties( lLastID );
 }
 
-void CCP_MainApp::SetStatus( const char* status, bool bRepaintImmediately )
+// Internal Clipboard for cut/copy/paste items between Groups
+
+// if NULL, this uses the current QPaste selection
+void CCP_MainApp::IC_Cut( ARRAY* pIDs )
+{
+	if( pIDs == NULL )
+	{
+		if( QPasteWnd() )
+			QPasteWnd()->m_lstHeader.GetSelectionItemData( m_IC_IDs );
+		else
+			m_IC_IDs.SetSize(0);
+	}
+	else
+		m_IC_IDs.Copy( *pIDs );
+
+	m_IC_bCopy = false;
+
+	if( QPasteWnd() )
+		QPasteWnd()->UpdateStatus();
+}
+
+// if NULL, this uses the current QPaste selection
+void CCP_MainApp::IC_Copy( ARRAY* pIDs )
+{
+	if( pIDs == NULL )
+	{
+		if( QPasteWnd() )
+			QPasteWnd()->m_lstHeader.GetSelectionItemData( m_IC_IDs );
+		else
+			m_IC_IDs.SetSize(0);
+	}
+	else
+		m_IC_IDs.Copy( *pIDs );
+
+	m_IC_bCopy = true;
+
+	if( QPasteWnd() )
+		QPasteWnd()->UpdateStatus();
+}
+
+void CCP_MainApp::IC_Paste()
+{
+	if( m_IC_IDs.GetSize() <= 0 )
+		return;
+
+	if( m_IC_bCopy )
+		m_IC_IDs.CopyTo( GetValidGroupID() );
+	else // Move
+		m_IC_IDs.MoveTo( GetValidGroupID() );
+
+	// don't process the same items twice.
+	m_IC_IDs.SetSize(0);
+	RefreshView();
+}
+
+// Groups
+
+BOOL CCP_MainApp::EnterGroupID( long lID )
 {
-	if(status != NULL)
+BOOL bResult = FALSE;
+
+	if( m_GroupID == lID )
+		return TRUE;
+
+	// if we are switching to the parent, focus on the previous group
+	if( m_GroupParentID == lID && m_GroupID > 0 )
+		m_FocusID = m_GroupID;
+
+	switch( lID )
+	{
+	case 0:  // History Group "ID"
+		m_GroupID = 0;
+		m_GroupParentID = 0;
+		m_GroupText = "History";
+		bResult = TRUE;
+		break;
+	case -1: // All Groups "ID"
+		m_GroupID = -1;
+		m_GroupParentID = 0;
+		m_GroupText = "Groups";
+		bResult = TRUE;
+		break;
+	default: // Normal Group
+		try
+		{
+		CMainTable recs;
+		COleVariant varKey( lID, VT_I4 );
+
+			recs.Open( dbOpenTable, "Main" );
+			recs.SetCurrentIndex("lID");
+
+			// Find first record whose [lID] field == lID
+			if( recs.Seek(_T("="), &varKey) && recs.m_bIsGroup )
+			{
+				m_GroupID = recs.m_lID;
+				m_GroupParentID = recs.m_lParentID;
+				if( m_GroupParentID == 0 )
+                    m_GroupParentID = -1; // back out into "all top-level groups" list.
+				m_GroupText = recs.m_strText;
+				bResult = TRUE;
+			}
+
+			recs.Close();
+		}
+		CATCHDAO
+		break;
+	}
+
+	if( bResult )
 	{
-		m_Status = status;
+		theApp.RefreshView();
+		if( QPasteWnd() )
+			QPasteWnd()->UpdateStatus( true );
 	}
 
+	return bResult;
+}
+
+// returns a usable group id (not negative)
+long CCP_MainApp::GetValidGroupID()
+{
+	if( m_GroupID <= 0 )
+		return 0;
+	return m_GroupID;
+}
+
+// sets a valid id
+void CCP_MainApp::SetGroupDefaultID( long lID )
+{
+	if( m_GroupDefaultID == lID )
+		return;
+
+	if( lID <= 0 )
+		m_GroupDefaultID = 0;
+	else
+		m_GroupDefaultID = lID;
+
+	if( QPasteWnd() )
+		QPasteWnd()->UpdateStatus();
+}
+
+// Window States
+
+void CCP_MainApp::SetStatus( const char* status, bool bRepaintImmediately )
+{
+	m_Status = status;
 	if( QPasteWnd() )
 		QPasteWnd()->UpdateStatus( bRepaintImmediately );
 }
@@ -385,19 +537,23 @@ int CCP_MainApp::ExitInstance()
 
 CDaoDatabase* CCP_MainApp::EnsureOpenDB(CString csName)
 {
-	if(!m_pDatabase)
-		m_pDatabase = new CDaoDatabase;
-
-	if(!m_pDatabase->IsOpen())
+	try
 	{
-		if(csName == "")
-			m_pDatabase->Open(GetDBName());
-		else
-			m_pDatabase->Open(csName);
-	}
+		if(!m_pDatabase)
+			m_pDatabase = new CDaoDatabase;
 
-	if(m_pMainWnd)
-		((CMainFrame *)m_pMainWnd)->ResetKillDBTimer();
+		if(!m_pDatabase->IsOpen())
+		{
+			if(csName == "")
+				m_pDatabase->Open(GetDBName());
+			else
+				m_pDatabase->Open(csName);
+		}
+
+		if(m_pMainWnd)
+			((CMainFrame *)m_pMainWnd)->ResetKillDBTimer();
+	}
+	CATCHDAO
 
 	return m_pDatabase;
 }

+ 22 - 1
CP_Main.h

@@ -21,6 +21,7 @@
 #include "TypesTable.h"
 #include "ArrayEx.h"
 #include "MainFrm.h"
+#include "ProcessPaste.h"
 
 #define MAIN_WND_TITLE		"Ditto MainWnd"
 //#define GET_APP    ((CCP_MainApp *)AfxGetApp())	
@@ -61,7 +62,7 @@ public:
 	bool ReleaseFocus(); // activate the target only if we are the active window
 	CString GetTargetName() { return GetWndText( m_hTargetWnd ); }
 	void SendPaste(); // Activates the Target and sends Ctrl-V
-	
+
 	CLIPFORMAT m_cfIgnoreClipboard; // used by CClip::LoadFromClipboard
 
 // CopyThread and ClipViewer (Copy and Paste Management)
@@ -85,7 +86,27 @@ public:
 	void OnCopyCompleted( long lLastID, int count = 1 );
 	void OnPasteCompleted();
 
+// Internal Clipboard for cut/copy/paste items between Groups
+	bool		m_IC_bCopy;   // true to copy the items, false to move them
+	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_Paste();
+
+// Groups
+	long		m_GroupDefaultID; // new clips are saved to this group
+	long		m_GroupID;        // current group
+	long		m_GroupParentID;  // current group's parent
+	CString		m_GroupText;      // current group's description
+
+	BOOL EnterGroupID( long lID );
+	long GetValidGroupID(); // returns a valid id (not negative)
+	void SetGroupDefaultID( long lID ); // sets a valid id
+
 // Window States
+	// the ID given focus by CQPasteWnd::FillList
+	long	m_FocusID; // -1 == keep previous position, 0 == go to latest ID
+
 	bool	m_bShowingOptions;
 	bool	m_bShowingQuickPaste;
 

+ 150 - 130
CP_Main.rc

@@ -1,4 +1,4 @@
-//Microsoft Developer Studio generated resource script.
+// Microsoft Visual C++ generated resource script.
 //
 #include "resource.h"
 
@@ -27,18 +27,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 // TEXTINCLUDE
 //
 
-1 TEXTINCLUDE MOVEABLE PURE 
+1 TEXTINCLUDE 
 BEGIN
     "resource.h\0"
 END
 
-2 TEXTINCLUDE MOVEABLE PURE 
+2 TEXTINCLUDE 
 BEGIN
     "#include ""afxres.h""\r\n"
     "\0"
 END
 
-3 TEXTINCLUDE MOVEABLE PURE 
+3 TEXTINCLUDE 
 BEGIN
     "#define _AFX_NO_SPLITTER_RESOURCES\r\n"
     "#define _AFX_NO_OLE_RESOURCES\r\n"
@@ -67,22 +67,22 @@ END
 
 // Icon with lowest ID value placed first to ensure application icon
 // remains consistent on all systems.
-IDR_MAINFRAME           ICON    DISCARDABLE     "res\\Ditto.ico"
-IDR_CP_MAITYPE          ICON    DISCARDABLE     "res\\CP_MainDoc.ico"
+IDR_MAINFRAME           ICON                    "res\\Ditto.ico"
+IDR_CP_MAITYPE          ICON                    "res\\CP_MainDoc.ico"
 
 /////////////////////////////////////////////////////////////////////////////
 //
 // Bitmap
 //
 
-IDR_MAINFRAME           BITMAP  MOVEABLE PURE   "res\\Toolbar.bmp"
+IDR_MAINFRAME           BITMAP                  "res\\Toolbar.bmp"
 
 /////////////////////////////////////////////////////////////////////////////
 //
 // Toolbar
 //
 
-IDR_MAINFRAME TOOLBAR MOVEABLE PURE  16, 15
+IDR_MAINFRAME TOOLBAR  16, 15
 BEGIN
     BUTTON      ID_FILE_NEW
     BUTTON      ID_FILE_OPEN
@@ -103,7 +103,7 @@ END
 // Menu
 //
 
-IDR_MAINFRAME MENU DISCARDABLE 
+IDR_MAINFRAME MENU 
 BEGIN
     POPUP "&File"
     BEGIN
@@ -139,7 +139,7 @@ BEGIN
     END
 END
 
-IDR_MENU MENU DISCARDABLE 
+IDR_MENU MENU 
 BEGIN
     POPUP "First"
     BEGIN
@@ -153,72 +153,92 @@ BEGIN
     END
 END
 
-IDR_QUICK_PASTE MENU DISCARDABLE 
+IDR_QUICK_PASTE MENU 
 BEGIN
     POPUP "Menu"
     BEGIN
-        POPUP "Lines Per Row"
-        BEGIN
-            MENUITEM "1",                           ID_MENU_LINESPERROW_1
-            MENUITEM "2",                           ID_MENU_LINESPERROW_2
-            MENUITEM "3",                           ID_MENU_LINESPERROW_3
-            MENUITEM "4",                           ID_MENU_LINESPERROW_4
-            MENUITEM "5",                           ID_MENU_LINESPERROW_5
-        END
-        POPUP "Transparency"
-        BEGIN
-            MENUITEM "None",                        ID_MENU_TRANSPARENCY_NONE
-
-            MENUITEM "5 %",                         ID_MENU_TRANSPARENCY_5
-            MENUITEM "10 %",                        ID_MENU_TRANSPARENCY_10
-            MENUITEM "15 %",                        ID_MENU_TRANSPARENCY_15
-            MENUITEM "20 %",                        ID_MENU_TRANSPARENCY_20
-            MENUITEM "25 %",                        ID_MENU_TRANSPARENCY_25
-            MENUITEM "30 %",                        ID_MENU_TRANSPARENCY_30
-            MENUITEM "40 %",                        ID_MENU_TRANSPARENCY_40
-        END
-        POPUP "Positioning"
+        MENUITEM "New Group\tF7",               ID_MENU_NEWGROUP
+        MENUITEM "New Group Selection\tCtrl-F7", ID_MENU_NEWGROUPSELECTION
+        MENUITEM "View Full Description\tF3",   ID_MENU_VIEWFULLDESCRIPTION
+        MENUITEM SEPARATOR
+        MENUITEM "Delete Entry\tDel",           ID_MENU_DELETE
+        MENUITEM SEPARATOR
+        MENUITEM "Properties\tAlt-Enter",       ID_MENU_PROPERTIES
+        MENUITEM SEPARATOR
+        MENUITEM "Reconnect To Clipboard Chain", 
+                                                ID_MENU_RECONNECTTOCLIPBOARDCHAIN
+
+        MENUITEM "Options...",                  ID_MENU_OPTIONS
+        POPUP "Quick Options"
         BEGIN
-            MENUITEM "At Caret",                    ID_MENU_POSITIONING_ATCARET
+            POPUP "Lines Per Row"
+            BEGIN
+                MENUITEM "1",                           ID_MENU_LINESPERROW_1
 
-            MENUITEM "At Cursor",                   ID_MENU_POSITIONING_ATCURSOR
+                MENUITEM "At Cursor",                   ID_MENU_POSITIONING_ATCURSOR
 
-            MENUITEM "At Previous Position",        ID_MENU_POSITIONING_ATPREVIOUSPOSITION
+                MENUITEM "3",                           ID_MENU_LINESPERROW_3
 
-        END
-        POPUP "First Ten Hot Keys"
-        BEGIN
-            MENUITEM "Use Ctrl - Num",              ID_MENU_FIRSTTENHOTKEYS_USECTRLNUM
+                MENUITEM "4",                           ID_MENU_LINESPERROW_4
 
-            MENUITEM "Show Hot Key Text",           ID_MENU_FIRSTTENHOTKEYS_SHOWHOTKEYTEXT
+                MENUITEM "5",                           ID_MENU_LINESPERROW_5
 
-        END
-        POPUP "View Caption Bar On"
-        BEGIN
-            MENUITEM "Top",                         ID_VIEWCAPTIONBARON_TOP
-            MENUITEM "Right",                       ID_VIEWCAPTIONBARON_RIGHT
+            END
+            POPUP "Transparency"
+            BEGIN
+                MENUITEM "None",                        ID_MENU_TRANSPARENCY_NONE
 
-            MENUITEM "Bottom",                      ID_VIEWCAPTIONBARON_BOTTOM
+                MENUITEM "5 %",                         ID_MENU_TRANSPARENCY_5
 
-            MENUITEM "Left",                        ID_VIEWCAPTIONBARON_LEFT
-        END
-        POPUP "Sort"
-        BEGIN
-            MENUITEM "Ascending (Latest on the Top)", ID_SORT_ASCENDING
-            MENUITEM "Descending (Latest on the Bottom", ID_SORT_DESCENDING
-        END
-        MENUITEM "Allways on Top\tCtrl-Spce",   ID_MENU_ALLWAYSONTOP
-        MENUITEM "Auto Roolup",                 ID_MENU_AUTOHIDE
-        MENUITEM "View Full Description\tF3",   ID_MENU_VIEWFULLDESCRIPTION
-        MENUITEM "Reconnect To Clipboard Chain", 
-                                                ID_MENU_RECONNECTTOCLIPBOARDCHAIN
+                MENUITEM "10 %",                        ID_MENU_TRANSPARENCY_10
 
-        MENUITEM SEPARATOR
-        MENUITEM "Properties\tAlt-Enter",       ID_MENU_PROPERTIES
-        MENUITEM SEPARATOR
-        MENUITEM "Options",                     ID_MENU_OPTIONS
-        MENUITEM SEPARATOR
-        MENUITEM "Delete Copy Entry",           ID_MENU_DELETE
+                MENUITEM "15 %",                        ID_MENU_TRANSPARENCY_15
+
+                MENUITEM "20 %",                        ID_MENU_TRANSPARENCY_20
+
+                MENUITEM "25 %",                        ID_MENU_TRANSPARENCY_25
+
+                MENUITEM "30 %",                        ID_MENU_TRANSPARENCY_30
+
+                MENUITEM "40 %",                        ID_MENU_TRANSPARENCY_40
+
+            END
+            POPUP "Positioning"
+            BEGIN
+                MENUITEM "At Caret",                    ID_MENU_POSITIONING_ATCARET
+
+                MENUITEM "At Cursor",                   ID_MENU_POSITIONING_ATCURSOR
+
+                MENUITEM "At Previous Position",        ID_MENU_POSITIONING_ATPREVIOUSPOSITION
+
+            END
+            POPUP "First Ten Hot Keys"
+            BEGIN
+                MENUITEM "Use Ctrl - Num",              ID_MENU_FIRSTTENHOTKEYS_USECTRLNUM
+
+                MENUITEM "Show Hot Key Text",           ID_MENU_FIRSTTENHOTKEYS_SHOWHOTKEYTEXT
+
+            END
+            POPUP "View Caption Bar On"
+            BEGIN
+                MENUITEM "Top",                         ID_VIEWCAPTIONBARON_TOP
+
+                MENUITEM "Right",                       ID_VIEWCAPTIONBARON_RIGHT
+
+                MENUITEM "Bottom",                      ID_VIEWCAPTIONBARON_BOTTOM
+
+                MENUITEM "Left",                        ID_VIEWCAPTIONBARON_LEFT
+
+            END
+            POPUP "Sort"
+            BEGIN
+                MENUITEM "Ascending (Latest on the Top)", ID_SORT_ASCENDING
+                MENUITEM "Descending (Latest on the Bottom)", 
+                                                        ID_SORT_DESCENDING
+            END
+            MENUITEM "Always on Top\tCtrl-Space",   ID_MENU_ALLWAYSONTOP
+            MENUITEM "Auto Roll-up",                ID_MENU_AUTOHIDE
+        END
         MENUITEM SEPARATOR
         MENUITEM "Exit Program",                ID_MENU_EXITPROGRAM
     END
@@ -230,7 +250,7 @@ END
 // Accelerator
 //
 
-IDR_MAINFRAME ACCELERATORS MOVEABLE PURE 
+IDR_MAINFRAME ACCELERATORS 
 BEGIN
     "N",            ID_FILE_NEW,            VIRTKEY, CONTROL
     "O",            ID_FILE_OPEN,           VIRTKEY, CONTROL
@@ -254,8 +274,8 @@ END
 // Dialog
 //
 
-IDD_ABOUTBOX DIALOG DISCARDABLE  0, 0, 235, 55
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_ABOUTBOX DIALOG  0, 0, 235, 55
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "About CP_Main"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -265,8 +285,8 @@ BEGIN
     DEFPUSHBUTTON   "OK",IDOK,178,7,50,14,WS_GROUP
 END
 
-IDD_OPTIONS_UTILITIES DIALOG DISCARDABLE  0, 0, 241, 137
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_OPTIONS_UTILITIES DIALOG  0, 0, 241, 137
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Database"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -277,8 +297,8 @@ BEGIN
     LTEXT           "Database Path:",IDC_STATIC,14,56,50,8
 END
 
-IDD_OPTIONS_TYPES DIALOG DISCARDABLE  0, 0, 230, 172
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_OPTIONS_TYPES DIALOG  0, 0, 230, 172
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Supported Types"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -288,41 +308,43 @@ BEGIN
     PUSHBUTTON      "&Delete",IDC_DELETE,147,30,38,14
 END
 
-IDD_OPTIONS_QUICK_PASTE DIALOGEX 0, 0, 256, 159
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_OPTIONS_QUICK_PASTE DIALOGEX 0, 0, 206, 193
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Quick Paste"
-FONT 8, "MS Sans Serif"
+FONT 8, "MS Sans Serif", 0, 0, 0x0
 BEGIN
     CONTROL         "Enable Quick Paste Transparency",IDC_TRANSPARENCY,
-                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,41,122,10
-    EDITTEXT        IDC_TRANS_PERC,140,40,19,12,ES_AUTOHSCROLL
-    LTEXT           "%",IDC_STATIC,162,43,8,8
-    LTEXT           "Text Lines per Row",IDC_STATIC,18,56,62,8
-    EDITTEXT        IDC_LINES_ROW,82,54,19,12,ES_AUTOHSCROLL
-    GROUPBOX        "Positioning",IDC_STATIC,11,69,196,41
+                    "Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,67,122,10
+    EDITTEXT        IDC_TRANS_PERC,140,66,19,12,ES_AUTOHSCROLL
+    LTEXT           "%",IDC_STATIC,162,69,8,8
+    LTEXT           "Text Lines per Row",IDC_STATIC,18,54,62,8
+    EDITTEXT        IDC_LINES_ROW,82,52,19,12,ES_AUTOHSCROLL
+    GROUPBOX        "Popup Positioning",IDC_STATIC,11,80,116,41
     CONTROL         "At Caret",IDC_AT_CARET,"Button",BS_AUTORADIOBUTTON,22,
-                    78,41,10
+                    89,41,10
     CONTROL         "At Cursor",IDC_AT_CURSOR,"Button",BS_AUTORADIOBUTTON,22,
-                    88,45,10
+                    99,45,10
     CONTROL         "At Previous Position",IDC_AT_PREVIOUS,"Button",
-                    BS_AUTORADIOBUTTON,22,98,79,10
+                    BS_AUTORADIOBUTTON,22,109,79,10
     CONTROL         "Use Ctrl - Num for first ten copy hot keys",
                     IDC_CTRL_CLICK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,18,
                     19,142,10
     CONTROL         "Show text for first ten copy hot keys",
                     IDC_SHOW_TEXT_FOR_FIRST_TEN_HOT_KEYS,"Button",
-                    BS_AUTOCHECKBOX | WS_TABSTOP,18,29,128,10
-    LTEXT           "** before text  = text has a hot key assigned to it.",
-                    IDC_STATIC,18,117,153,8
-    LTEXT           "* before text = text will never be auto deleted.",
-                    IDC_STATIC,18,128,143,8
+                    BS_AUTOCHECKBOX | WS_TABSTOP,18,30,128,10
+    LTEXT           "  * = Don't Auto Delete\n  s = Shortcut exists\n G = Item is a Group\n  ! = Item is attached to a Group",
+                    IDC_STATIC,17,137,103,36
     CONTROL         "History Starts at the Top of the list (vs. Bottom)",
                     IDC_HISTORY_START_TOP,"Button",BS_AUTOCHECKBOX | 
                     WS_TABSTOP,18,8,161,10
+    CONTROL         "Show leading whitespace",
+                    IDC_DESC_SHOW_LEADING_WHITESPACE,"Button",
+                    BS_AUTOCHECKBOX | WS_TABSTOP,18,41,97,10
+    GROUPBOX        "List Item Prefix Legend",IDC_STATIC,11,127,116,50
 END
 
-IDD_OPTIONS_KEYSTROKES DIALOG DISCARDABLE  0, 0, 186, 90
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_OPTIONS_KEYSTROKES DIALOG  0, 0, 186, 90
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Keyboard Shortcuts"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -334,8 +356,8 @@ BEGIN
     LTEXT           "Named Copy",IDC_STATIC,19,42,42,8
 END
 
-IDD_OPTIONS_GENERAL DIALOG DISCARDABLE  0, 0, 294, 178
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_OPTIONS_GENERAL DIALOG  0, 0, 294, 178
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "General"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -369,12 +391,12 @@ BEGIN
     CONTROL         "Save Multi-Pastes",IDC_SAVE_MULTIPASTE,"Button",
                     BS_AUTOCHECKBOX | WS_TABSTOP,23,144,73,10
     EDITTEXT        IDC_DESC_TEXT_SIZE,154,155,35,12,ES_AUTOHSCROLL
-    LTEXT           "Amount of text to save for desciption",IDC_STATIC,24,
+    LTEXT           "Amount of text to save for description",IDC_STATIC,24,
                     156,122,8
 END
 
-IDD_SELECT_DB DIALOG DISCARDABLE  0, 0, 276, 46
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION
+IDD_SELECT_DB DIALOG  0, 0, 276, 46
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION
 CAPTION "Select Database"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -385,8 +407,8 @@ BEGIN
     PUSHBUTTON      "Use Default",IDC_USE_DEFAULT,27,25,50,14
 END
 
-IDD_OPTIONS_STATS DIALOG DISCARDABLE  0, 0, 272, 163
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_OPTIONS_STATS DIALOG  0, 0, 272, 163
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Stats"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -412,8 +434,8 @@ BEGIN
     EDITTEXT        IDC_DATABASE_SIZE,76,111,41,13,ES_AUTOHSCROLL
 END
 
-IDD_ADD_TYPE DIALOG DISCARDABLE  0, 0, 276, 230
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_ADD_TYPE DIALOG  0, 0, 276, 230
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Add Supported Type"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -432,16 +454,17 @@ BEGIN
 END
 
 IDD_COPY_PROPERTIES DIALOGEX 0, 0, 244, 211
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "Copy Properties"
-FONT 8, "MS Sans Serif"
+FONT 8, "MS Sans Serif", 0, 0, 0x0
 BEGIN
     CONTROL         "HotKey1",IDC_HOTKEY,"msctls_hotkey32",WS_BORDER | 
                     WS_TABSTOP,37,7,82,13
     CONTROL         "&Never Auto Delete",IDC_NEVER_AUTO_DELETE,"Button",
                     BS_AUTOCHECKBOX | WS_TABSTOP,8,23,75,10
     EDITTEXT        IDC_EDIT_DISPLAY_TEXT,7,44,230,56,ES_MULTILINE | 
-                    ES_AUTOHSCROLL | ES_WANTRETURN
+                    ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | 
+                    WS_VSCROLL
     LISTBOX         IDC_COPY_DATA,7,111,230,75,LBS_SORT | 
                     LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | 
                     WS_TABSTOP
@@ -457,8 +480,8 @@ BEGIN
     PUSHBUTTON      "Parse",IDC_PARSE_BUTTON,212,24,25,14
 END
 
-IDD_ABOUT DIALOG DISCARDABLE  0, 0, 292, 124
-STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
+IDD_ABOUT DIALOG  0, 0, 292, 124
+STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
 CAPTION "About"
 FONT 8, "MS Sans Serif"
 BEGIN
@@ -468,7 +491,6 @@ BEGIN
 END
 
 
-#ifndef _MAC
 /////////////////////////////////////////////////////////////////////////////
 //
 // Version
@@ -491,13 +513,13 @@ BEGIN
     BEGIN
         BLOCK "040904b0"
         BEGIN
-            VALUE "FileDescription", "Ditto\0"
-            VALUE "FileVersion", "1, 50, 0, 0\0"
-            VALUE "InternalName", "CP_Main\0"
-            VALUE "LegalCopyright", "Copyright (C) 2003\0"
-            VALUE "OriginalFilename", "Ditto\0"
-            VALUE "ProductName", "Ditto\0"
-            VALUE "ProductVersion", "1, 50, 0, 0\0"
+            VALUE "FileDescription", "Ditto"
+            VALUE "FileVersion", "1, 50, 0, 0"
+            VALUE "InternalName", "CP_Main"
+            VALUE "LegalCopyright", "Copyright (C) 2003"
+            VALUE "OriginalFilename", "Ditto"
+            VALUE "ProductName", "Ditto"
+            VALUE "ProductVersion", "1, 50, 0, 0"
         END
     END
     BLOCK "VarFileInfo"
@@ -506,8 +528,6 @@ BEGIN
     END
 END
 
-#endif    // !_MAC
-
 
 /////////////////////////////////////////////////////////////////////////////
 //
@@ -515,7 +535,7 @@ END
 //
 
 #ifdef APSTUDIO_INVOKED
-GUIDELINES DESIGNINFO MOVEABLE PURE 
+GUIDELINES DESIGNINFO 
 BEGIN
     IDD_ABOUTBOX, DIALOG
     BEGIN
@@ -544,9 +564,9 @@ BEGIN
     IDD_OPTIONS_QUICK_PASTE, DIALOG
     BEGIN
         LEFTMARGIN, 7
-        RIGHTMARGIN, 249
+        RIGHTMARGIN, 199
         TOPMARGIN, 7
-        BOTTOMMARGIN, 152
+        BOTTOMMARGIN, 185
     END
 
     IDD_OPTIONS_KEYSTROKES, DIALOG
@@ -613,18 +633,18 @@ END
 // String Table
 //
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     IDR_MAINFRAME           "CP_Main\n\nCP_Mai\n\n\nCPMain.Document\nCP_Mai Document"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     AFX_IDS_APP_TITLE       "CP_Main"
     AFX_IDS_IDLEMESSAGE     "Ready"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_INDICATOR_EXT        "EXT"
     ID_INDICATOR_CAPS       "CAP"
@@ -634,7 +654,7 @@ BEGIN
     ID_INDICATOR_REC        "REC"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_FILE_NEW             "Create a new document\nNew"
     ID_FILE_OPEN            "Open an existing document\nOpen"
@@ -647,13 +667,13 @@ BEGIN
     ID_FILE_PRINT_PREVIEW   "Display full pages\nPrint Preview"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_APP_ABOUT            "Display program information, version number and copyright\nAbout"
     ID_APP_EXIT             "Quit the application; prompts to save documents\nExit"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_FILE_MRU_FILE1       "Open this document"
     ID_FILE_MRU_FILE2       "Open this document"
@@ -673,18 +693,18 @@ BEGIN
     ID_FILE_MRU_FILE16      "Open this document"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_NEXT_PANE            "Switch to the next window pane\nNext Pane"
     ID_PREV_PANE            "Switch back to the previous window pane\nPrevious Pane"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_WINDOW_SPLIT         "Split the active window into panes\nSplit"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_EDIT_CLEAR           "Erase the selection\nErase"
     ID_EDIT_CLEAR_ALL       "Erase everything\nErase All"
@@ -699,13 +719,13 @@ BEGIN
     ID_EDIT_REDO            "Redo the previously undone action\nRedo"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     ID_VIEW_TOOLBAR         "Show or hide the toolbar\nToggle ToolBar"
     ID_VIEW_STATUS_BAR      "Show or hide the status bar\nToggle StatusBar"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     AFX_IDS_SCSIZE          "Change the window size"
     AFX_IDS_SCMOVE          "Change the window position"
@@ -716,13 +736,13 @@ BEGIN
     AFX_IDS_SCCLOSE         "Close the active window and prompts to save the documents"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     AFX_IDS_SCRESTORE       "Restore the window to normal size"
     AFX_IDS_SCTASKLIST      "Activate Task List"
 END
 
-STRINGTABLE DISCARDABLE 
+STRINGTABLE 
 BEGIN
     AFX_IDS_PREVIEW_CLOSE   "Close print preview mode\nCancel Preview"
 END

+ 52 - 2
Changes.txt

@@ -1,7 +1,57 @@
+03 Sept 22
+----------
++ Shared Data: Data can now be shared amongst multiple Clips.
+  + DittoDB.mdb: Main.lDataID, Data.lDataID
+  * SQL statements, e.g. INNER JOIN through Main.lID
+  
++ Groups:
+  + DittoDB.mdb: Main.bIsGroup, Main.lParentID, Main.dOrder
+  * CCP_MainApp:
+    + long    m_GroupDefaultID; // new clips are saved to this group
+    + long    m_GroupID;        // current group
+    + long    m_GroupParentID;  // current group's parent
+    + CString m_GroupText;      // current group's description
+    +	BOOL EnterGroupID( long lID );
+    + long GetValidGroupID(); // returns a valid id (not negative)
+    + void SetGroupDefaultID( long lID ); // sets a valid id
+
++ Internal Clipboard for cut/copy/paste items between Groups
+  CCP_MainApp:
+  + bool      m_IC_bCopy; // true to copy the items, false to move them
+  + CClipIDs  m_IC_IDs;   // buffer
+  + void IC_Cut( ARRAY* pIDs = NULL ); // if NULL, use current QPaste selection
+  + void IC_Copy( ARRAY* pIDs = NULL ); // if NULL, use current QPaste selection
+  + void IC_Paste();
+
++ Persistent QPasteWnd focus item
+  + CCP_MainApp: long m_FocusID; // the ID given focus by CQPasteWnd::FillList
+	* CQPasteWnd::FillList
+
++ Item Description: Show Leading WhiteSpace
+  + CString CMainTable::GetDisplayText()
+  * void CQPasteWnd::OnGetToolTipText()
+  * CGetSetOptions:
+    + static BOOL    m_bDescShowLeadingWhiteSpace;
+    + static void SetDescShowLeadingWhiteSpace(BOOL bVal);
+    + static BOOL GetDescShowLeadingWhiteSpace();
+
++ QListCtrl Keys:
+  +        F7: Create a New Group
+  +   Ctrl-F7: Create a New Group of Selected Elements
+  + Backspace: Go to parent group
+  +     Enter: Enter group or paste if clip
+  +  Alt-Home: Go to History Group
+  +   Alt-End: List all top level Groups
+  +    Ctrl-X: Move selection (establishes the source)
+  +    Ctrl-C: Copy selection (establishes the source)
+  +    Ctrl-V: Paste selection (Move or Copy)
+
+
 03 Sept 14
 ----------
-+ View caption on all sides(top, right, bottom, left)
-+ Roll up window by button on caption or by auto roll up(looses focus then it will rool up)
++ View caption on all sides (top, right, bottom, left)
++ Roll up window by button on caption or by auto roll up (rolls up when it looses focus)
+
 
 03 Sept 10
 ----------

+ 7 - 34
CopyProperties.cpp

@@ -90,7 +90,7 @@ BOOL CCopyProperties::OnInitDialog()
 		m_HotKey.SetRules(HKCOMB_A, 0);
 
 		CString cs;
-		cs.Format("SELECT * FROM Data WHERE lParentID = %d", m_MainTable.m_lID);
+		cs.Format("SELECT * FROM Data WHERE lDataID = %d", m_MainTable.m_lDataID);
 
 		m_DataTable.Open(AFX_DAO_USE_DEFAULT_TYPE, cs ,NULL);
 
@@ -200,37 +200,14 @@ void CCopyProperties::OnOK()
 		bUpdate = true;
 	}
 
+	if(bUpdate)
+		m_MainTable.Update();
+	
 	if(m_bDeletedData)
 	{
-		m_DeletedData.SortAscending();
-		
-		long lNewTotalSize = 0;
-
-		//Go through the data table and find the deleted items
-		m_DataTable.MoveFirst();
-		while(!m_DataTable.IsEOF())
-		{
-			if(m_DeletedData.Find(m_DataTable.m_lID))
-				m_DataTable.Delete();
-			else
-				lNewTotalSize += m_DataTable.m_ooData.m_dwDataLength;
-
-			m_DataTable.MoveNext();
-		}
-
-		if(lNewTotalSize > 0)
-		{
-			if(!bUpdate)
-				m_MainTable.Edit();
-
-			m_MainTable.m_lTotalCopySize = lNewTotalSize;
-			bUpdate = true;
-		}
+		DeleteFormats( m_MainTable.m_lDataID, m_DeletedData );
 	}
 
-	if(bUpdate)
-		m_MainTable.Update();
-	
 	m_bHandleKillFocus = true;
 
 	CDialog::OnOK();
@@ -268,11 +245,7 @@ void CCopyProperties::OnCancel()
 
 void CCopyProperties::OnBnClickedParseButton()
 {
-//RECT rcScreen;
-//	SystemParametersInfo (SPI_GETWORKAREA, 0, &rcScreen, 0);
-CPoint pos(0,0);
-	ClientToScreen(&pos);
-CPopup status( pos );
+CPopup status(0,0,m_hWnd);
 	status.Show("Parsing...");
 
 CString delims;
@@ -340,7 +313,7 @@ int count = tokens.GetSize();
 			continue;
 		clip.AddFormat( CF_TEXT, (void*) (LPCTSTR) tokens[i], len+1 );
 		clip.m_Time = lDate;
-		clip.AddToDB();
+		clip.AddToDB( false ); // false = don't check for duplicates
 		clip.Clear();
 		lDate++; // make sure they are sequential
 	}

+ 36 - 37
DataTable.cpp

@@ -22,7 +22,7 @@ CDataTable::CDataTable(CDaoDatabase* pdb)
 {
 	//{{AFX_FIELD_INIT(CDataTable)
 	m_lID = 0;
-	m_lParentID = 0;
+	m_lDataID = 0;
 	m_strClipBoardFormat = _T("");
 	m_nFields = 4;
 	//}}AFX_FIELD_INIT
@@ -45,30 +45,30 @@ void CDataTable::DoFieldExchange(CDaoFieldExchange* pFX)
 	//{{AFX_FIELD_MAP(CDataTable)
 	pFX->SetFieldType(CDaoFieldExchange::outputColumn);
 	DFX_Long(pFX, _T("[lID]"), m_lID);
-	DFX_Long(pFX, _T("[lParentID]"), m_lParentID);
+	DFX_Long(pFX, _T("[lDataID]"), m_lDataID);
 	DFX_Text(pFX, _T("[strClipBoardFormat]"), m_strClipBoardFormat);
 	DFX_LongBinary(pFX, _T("[ooData]"), m_ooData);
 	//}}AFX_FIELD_MAP
 }
 
+
 /////////////////////////////////////////////////////////////////////////////
-// CDataTable diagnostics
+// CDataTable Member Functions
 
-#ifdef _DEBUG
-void CDataTable::AssertValid() const
+// assigns the new autoincr ID to m_lID
+void CDataTable::AddNew()
 {
-	CDaoRecordset::AssertValid();
+	CDaoRecordset::AddNew();
+	// get the new, automatically assigned ID
+COleVariant varID;
+	GetFieldValue("lID", varID);
+	m_lID = varID.lVal;
 }
 
-void CDataTable::Dump(CDumpContext& dc) const
-{
-	CDaoRecordset::Dump(dc);
-}
-#endif //_DEBUG
 
 // caller must free
 // takes m_ooData's HGLOBAL (do not update recset after calling this)
-// This should be faster than making a copy, but is this SAFE ?????
+// This should be faster than making a copy, but is this SAFE ??
 HGLOBAL CDataTable::TakeData()
 {
 	// if there is nothing to take
@@ -113,16 +113,16 @@ BOOL CDataTable::ReplaceData( HGLOBAL hgData, UINT len )
 	return TRUE;
 }
 
-// copies hgData into m_ooData using ::GlobalSize(hgData) for the size
-BOOL CDataTable::SetData( HGLOBAL hgData )
+// copies hgData into m_ooData
+BOOL CDataTable::SetData( HGLOBAL hgData, UINT size )
 {
-UINT unSize = GlobalSize(hgData);
+UINT unSize = (size < 0)? ::GlobalSize(hgData) : size;
 
 	//Reallocate m_ooData.m_hData
 	if(m_ooData.m_hData)
-		m_ooData.m_hData = GlobalReAlloc(m_ooData.m_hData, unSize, GMEM_MOVEABLE);
+		m_ooData.m_hData = ::GlobalReAlloc(m_ooData.m_hData, unSize, GMEM_MOVEABLE);
 	else
-		m_ooData.m_hData = GlobalAlloc(GHND, unSize);
+		m_ooData.m_hData = ::GlobalAlloc(GHND, unSize);
 
 	m_ooData.m_dwDataLength = unSize;
 
@@ -149,26 +149,10 @@ ULONG ulBufLen = m_ooData.m_dwDataLength; //Retrieve size of array
 	return hGlobal;
 }
 
-bool CDataTable::DeleteParent( long lParentID )
+void CDataTable::CopyRec( CDataTable& src )
 {
-CString csDataSQL;
-bool bRet = false;
-
-	csDataSQL.Format( "DELETE FROM Data WHERE lParentID = %d", lParentID );
-
-	try
-	{
-		theApp.EnsureOpenDB();
-		theApp.m_pDatabase->Execute(csDataSQL, dbFailOnError);
-		bRet = TRUE;
-	}
-	catch(CDaoException* e)
-	{
-		AfxMessageBox(e->m_pErrorInfo->m_strDescription);
-		e->Delete();
-	}
-
-	return bRet;
+	m_strClipBoardFormat = src.m_strClipBoardFormat;
+	SetData( src.m_ooData.m_hData, src.m_ooData.m_dwDataLength );
 }
 
 BOOL CDataTable::DeleteAll()
@@ -214,4 +198,19 @@ void CDataTable::Open(int nOpenType, LPCTSTR lpszSql, int nOptions)
 BOOL CDataTable::DataEqual(HGLOBAL hgData)
 {
 	return ::CompareGlobalHH( hgData, m_ooData.m_hData, m_ooData.m_dwDataLength ) == 0;
-}
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// CDataTable diagnostics
+
+#ifdef _DEBUG
+void CDataTable::AssertValid() const
+{
+	CDaoRecordset::AssertValid();
+}
+
+void CDataTable::Dump(CDumpContext& dc) const
+{
+	CDaoRecordset::Dump(dc);
+}
+#endif //_DEBUG

+ 7 - 4
DataTable.h

@@ -16,7 +16,7 @@ public:
 // Field/Param Data
 	//{{AFX_FIELD(CDataTable, CDaoRecordset)
 	long	m_lID;
-	long	m_lParentID;
+	long	m_lDataID;
 	CString	m_strClipBoardFormat;
 	CLongBinary	m_ooData;
 	//}}AFX_FIELD
@@ -31,21 +31,24 @@ public:
 	virtual void Open(int nOpenType = AFX_DAO_USE_DEFAULT_TYPE, LPCTSTR lpszSql = NULL, int nOptions = 0);
 	//}}AFX_VIRTUAL
 public:
+	void AddNew(); // assigns the new autoincr ID to m_lID
+
 	// caller owns the returned HGLOBAL
 	// takes m_ooData's HGLOBAL (do NOT update recset after calling this)
 	HGLOBAL TakeData();
 	// this takes ownership of hgData, freeing m_ooData if necessary
 	BOOL ReplaceData( HGLOBAL hgData, UINT len );
 
-	// copies hgData into m_ooData using ::GlobalSize(hgData) for the size
-	BOOL SetData(HGLOBAL hgData); 
+	// copies hgData into m_ooData
+	// if size < 0, ::GlobalSize(hgData) is used
+	BOOL SetData(HGLOBAL hgData, UINT size = -1); 
 	HGLOBAL LoadData(); // allocates a new copy of the data
 
 	BOOL DeleteAll();
 	void Open(LPCTSTR lpszFormat,...);
 	BOOL DataEqual(HGLOBAL hgData);
 
-	static bool DeleteParent( long lParentID );
+	void CopyRec( CDataTable& src );
 
 // Implementation
 #ifdef _DEBUG

+ 294 - 57
DatabaseUtilities.cpp

@@ -5,43 +5,29 @@
 #include "stdafx.h"
 #include "CP_Main.h"
 #include "DatabaseUtilities.h"
+#include "ProcessPaste.h"
 #include <io.h>
 
 //////////////////////////////////////////////////////////////////////
 // Construction/Destruction
 //////////////////////////////////////////////////////////////////////
 
-BOOL DoCleanups()
+BOOL CreateBackup(CString csPath)
 {
-	try
-	{		
-		//Try and open mText if it's not there then create it
-		CDaoRecordset recset(theApp.EnsureOpenDB());
-		recset.Open(AFX_DAO_USE_DEFAULT_TYPE, "SELECT mText FROM Main", 0);
-		recset.Close();		
-	}
-	catch(CDaoException* e)
+CString csOriginal;
+int count = 0;
+	// create a backup of the existing database
+	do
 	{
-		e->Delete();
-		try
-		{
-			theApp.EnsureOpenDB();
-			theApp.m_pDatabase->Execute("ALTER TABLE Main ADD COLUMN mText MEMO", dbFailOnError);
-			theApp.m_pDatabase->Execute("UPDATE Main SET mText=strText", dbFailOnError);
-			theApp.m_pDatabase->Execute("ALTER TABLE Main DROP COLUMN strText", dbFailOnError);
-			theApp.m_pDatabase->Execute("ALTER TABLE Main DROP COLUMN strType", dbFailOnError);
-		}
-		catch(CDaoException *e)
+		count++;
+		csOriginal = csPath + StrF(".%03d",count);
+		// in case of some weird infinite loop
+		if( count > 50 )
 		{
-			CString cs;
-			cs = e->m_pErrorInfo->m_strDescription;
-			cs += "\n\nError updating Database!";
-			MessageBox(NULL, cs, "Ditto", MB_OK);
-
-			e->Delete();
+			ASSERT(0);
 			return FALSE;
 		}
-	}
+	} while( !::CopyFile(csPath, csOriginal, TRUE) );
 
 	return TRUE;
 }
@@ -88,8 +74,7 @@ BOOL CheckDBExists(CString csDBPath)
 		CGetSetOptions::SetDBPath("");
 		
 		// -- create a new one
-		CreateDB(GetDefaultDBName());
-		return TRUE;
+		return CreateDB(GetDefaultDBName());
 	}
 
 	BOOL bRet = FALSE;
@@ -118,7 +103,6 @@ BOOL CheckDBExists(CString csDBPath)
 		AfxMessageBox(cs);
 		
 		CFile::Rename(csDBPath, csMarkAsBad);
-		
 
 		bRet = CreateDB(csPath);
 	}
@@ -128,8 +112,27 @@ BOOL CheckDBExists(CString csDBPath)
 	return bRet;
 }
 
-BOOL ValidDB(CString csPath)
+// m_pErrorInfo:
+// - m_lErrorCode      0x00000cc1
+// - m_strSource       "DAO.Fields"
+// - m_strDescription  "Item not found in this collection."
+#define ON_FIELD_ABSENT(name,onabsent) \
+	try { table.GetFieldInfo(name,info); } \
+	catch(CDaoException* e) \
+	{ \
+		if( !bUpgrade || e->m_pErrorInfo->m_lErrorCode != 0x00000cc1 ) \
+			throw e; \
+		if( bUpgraded == FALSE ) \
+			CreateBackup(csPath); \
+		bResult &= onabsent; \
+		bUpgraded = TRUE; \
+		e->Delete(); \
+	}
+
+BOOL ValidDB(CString csPath, BOOL bUpgrade)
 {
+BOOL bResult = TRUE;
+BOOL bUpgraded = FALSE;
 	try
 	{
 		CDaoDatabase db;
@@ -139,54 +142,75 @@ BOOL ValidDB(CString csPath)
 		CDaoFieldInfo info;
 
 		table.Open("Main");
+		table.GetFieldInfo("lID", info);
+		table.GetFieldInfo("lDate", info);
+		ON_FIELD_ABSENT("mText", Upgrade_mText(db)); // +mText, -strText, -strType
+		table.GetFieldInfo("lShortCut", info);
+		table.GetFieldInfo("lDontAutoDelete", info);
+		table.GetFieldInfo("lTotalCopySize", info);
+		ON_FIELD_ABSENT("bIsGroup", Upgrade_Groups(db));
+		table.GetFieldInfo("lParentID", info); // part of Upgrade_Groups
+		table.GetFieldInfo("dOrder", info);  // part of Upgrade_Groups
+		ON_FIELD_ABSENT("lDataID", Upgrade_ShareData(db)); // +lDataID, -lParentID
 		table.Close();
 
 		table.Open("Data");
+		table.GetFieldInfo("lID", info);
+		table.GetFieldInfo("lDataID", info); // part of Upgrade_ShareData()
+		table.GetFieldInfo("strClipBoardFormat", info);
+		table.GetFieldInfo("ooData", info);
 		table.Close();
 
 		table.Open("Types");
+		table.GetFieldInfo("ID", info);
+		table.GetFieldInfo("TypeText", info);
 		table.Close();
 	}
 	catch(CDaoException* e)
 	{
-		e->Delete();
+		e->ReportError();
 		ASSERT(FALSE);
+		e->Delete();
 		return FALSE;
 	}
 
-	return TRUE;
+	// if we upgraded, perform full validation again without upgrading
+	if( bUpgraded )
+		return ValidDB( csPath, FALSE);
+
+	return bResult;
 }
 
 BOOL CreateDB(CString csPath)
 {
-	CDaoDatabase db;
-
 	try
 	{
+	CDaoDatabase db;
 		EnsureDirectory(csPath);
-
 		db.Create(csPath);
-		
-		CDaoTableDefEx table(&db);
 
-		//Creat the Main table
+	CDaoTableDefEx table(&db);
+		//Create the Main table
 		table.Create("Main");
-		
 		table.CreateField("lID", dbLong, 4, dbAutoIncrField);
 		table.CreateIndex(TRUE, "lID");
-
 		table.CreateField("lDate", dbLong, 4, 0, "0");
 		table.CreateIndex(FALSE, "lDate");
-
 		table.CreateField("mText", dbMemo, 0, dbVariableField);
-//		table.CreateField("strText", dbText, 250, dbVariableField);
-
 		table.CreateField("lShortCut", dbLong, 4, 0, "0");
 		table.CreateIndex(FALSE, "lShortCut");
-		
 		table.CreateField("lDontAutoDelete", dbLong, 4, 0, "0");
-
 		table.CreateField("lTotalCopySize", dbLong, 4, 0, "0");
+		// GROUPS
+		table.CreateField("bIsGroup", dbBoolean, 1, 0, "0"); // for Groups
+		table.CreateIndex(FALSE, "bIsGroup");
+		table.CreateField("lParentID", dbLong, 4, 0, "0"); // parent Group Main.lID
+		table.CreateIndex(FALSE, "lParentID");
+		table.CreateField("dOrder", dbDouble, 8, 0, "0"); // for Order within Groups
+		table.CreateIndex(FALSE, "dOrder");
+		// for sharing data amongst multiple clips
+		table.CreateField("lDataID", dbLong, 4, 0, "0"); // corresponds to Data.lDataID
+		table.CreateIndex(FALSE, "lDataID");
 
 		table.Append();
 		table.Close();
@@ -196,10 +220,8 @@ BOOL CreateDB(CString csPath)
 		
 		table.CreateField("lID", dbLong, 4, dbAutoIncrField);
 		table.CreateIndex(TRUE, "lID");
-
-		table.CreateField("lParentID", dbLong, 4, 0, "0");
-		table.CreateIndex(FALSE, "lParentID");
-
+		table.CreateField("lDataID", dbLong, 4, 0, "0");
+		table.CreateIndex(FALSE, "lDataID");
 		table.CreateField("strClipBoardFormat", dbText, 50, dbVariableField);
 		table.CreateField("ooData", dbLongBinary, 0);
 
@@ -220,6 +242,7 @@ BOOL CreateDB(CString csPath)
 	}
 	catch(CDaoException *e)
 	{
+		e->ReportError();
 		ASSERT(FALSE);
 		e->Delete();
 	}
@@ -227,6 +250,157 @@ BOOL CreateDB(CString csPath)
 	return FALSE;
 }
 
+// +mText, -strText, -strType
+BOOL Upgrade_mText(CDaoDatabase& db)
+{
+	try
+	{
+		db.Execute("ALTER TABLE Main ADD COLUMN mText MEMO", dbFailOnError);
+		db.Execute("UPDATE Main SET mText=strText", dbFailOnError);
+		db.Execute("ALTER TABLE Main DROP COLUMN strText", dbFailOnError);
+		db.Execute("ALTER TABLE Main DROP COLUMN strType", dbFailOnError);
+	}
+	CATCHDAO
+	return TRUE;
+}
+
+BOOL Upgrade_Groups(CDaoDatabase& db)
+{
+	try
+	{
+	CDaoTableDefEx table(&db);
+		table.Open("Main");
+		// Groups
+		table.CreateField("bIsGroup", dbBoolean, 1, 0, "0"); // for Groups
+		table.CreateIndex(FALSE, "bIsGroup");
+		table.CreateField("lParentID", dbLong, 4, 0, "0"); // parent Group Main.lID
+		table.CreateIndex(FALSE, "lParentID");
+		table.CreateField("dOrder", dbDouble, 8, 0, "0"); // for Order within Groups
+		table.CreateIndex(FALSE, "dOrder");
+		table.Close();
+		// set defaults (otherwise might be NULL)
+		db.Execute("UPDATE Main SET bIsGroup = 0, lParentID = 0, dOrder = 0", dbFailOnError);
+	}
+	CATCHDAO
+	return TRUE;
+}
+
+BOOL Upgrade_ShareData(CDaoDatabase& db)
+{
+CPopup status(10000,10000); // peg at the bottom-right corner of screen
+	try
+	{
+	CDaoTableDefEx table(&db);
+
+		table.Open("Main");
+		table.CreateField("lDataID", dbLong, 4, 0, "0"); // corresponds to Data.lDataID
+		table.CreateIndex(FALSE, "lDataID");
+		table.Close();
+
+		table.Open("Data");
+		table.CreateField("lDataID", dbLong, 4, 0, "0"); // parent Group Main.lID
+		table.CreateIndex(FALSE, "lDataID");
+		table.Close();
+
+		// set defaults
+		db.Execute(	"UPDATE Main SET lDataID = 0", dbFailOnError );
+		db.Execute( "UPDATE Data SET lDataID = 0", dbFailOnError );
+
+		// update Main.lDataID and Data.lParentID for sharing Data
+		//
+		// - multiple Formats (Data.lID) exist for a single ClipData (Data.lDataID)
+		// - The value of lDataID is arbitrary, but must be unique to the ClipData.
+		//   - In order to ensure uniqueness, lDataID is assigned the lID of
+		//     the first Format in the Clip's set.
+
+	COleVariant var((long)0);
+	CDaoRecordset main(&db);
+	long main_fldID;
+	long main_fldDataID;
+	long main_lID;
+
+	CDaoRecordset data(&db);
+	long data_fldID;
+	long data_fldDataID;
+	long lDataID;
+	int count = 0;
+	int i = 0;
+	int percentPrev = -1;
+	int percent = -1;
+
+		main.Open(dbOpenDynaset,"SELECT lID, lDataID FROM Main");
+
+		main_fldID = GetFieldPos(main,"lID");
+		VERIFY(main_fldID == 0);
+		main_fldDataID = GetFieldPos(main,"lDataID");
+		VERIFY(main_fldDataID == 1);
+
+		if( !main.IsEOF() )
+		{
+			main.MoveLast();
+			count = main.GetRecordCount();
+			main.MoveFirst();
+		}
+
+		// for each record in Main and its corresponding records in Data,
+		//  assign a new unique lDataID.
+		while( !main.IsEOF() )
+		{
+			i++;
+			percentPrev = percent;
+			percent = (i*100)/count;
+			if( percent != percentPrev )
+				status.Show(StrF("Ditto: Upgrading database (%d%%)",percent));
+
+			main.GetFieldValue(main_fldID,var);
+			main_lID = var.lVal;
+
+			data.Open(dbOpenDynaset, StrF(
+				"SELECT lID, lDataID "
+				"FROM Data WHERE lParentID = %d", main_lID) );
+
+			data_fldID = GetFieldPos(data,"lID");
+			VERIFY(data_fldID == 0);
+			data_fldDataID = GetFieldPos(data,"lDataID");
+			VERIFY(data_fldDataID == 1);
+
+			// lDataID = the first data record lID
+			lDataID = 0;
+			if( !data.IsEOF() )
+			{
+				data.GetFieldValue(0,var); // 0 == lID field
+				lDataID = var.lVal;
+			}
+			// assign all Data records the same lDataID
+			while( !data.IsEOF() )
+			{
+				var.lVal = lDataID;
+				data.Edit();
+				data.SetFieldValue(1,var); // 1 == lDataID field
+				data.Update();
+				data.MoveNext();
+			}
+
+			// assign Main.lDataID
+			var.lVal = lDataID;
+			main.Edit();
+			main.SetFieldValue(1,var); // 1 == lDataID field
+			main.Update();
+			main.MoveNext();
+
+			data.Close();
+		}
+
+		main.Close();
+
+		// delete the old field
+		db.Execute("ALTER TABLE Data DROP CONSTRAINT lParentID", dbFailOnError);
+		db.Execute("ALTER TABLE Data DROP COLUMN lParentID", dbFailOnError);
+	}
+	CATCHDAO
+	return TRUE;
+}
+
 BOOL CompactDatabase()
 {
 	if(!theApp.CloseDB())
@@ -256,7 +430,6 @@ BOOL CompactDatabase()
 		return FALSE;
 	}
 
-
 	//Since compacting the database creates a new db delete the old one and replace it
 	//with the compacted db
 	if(DeleteFile(csDBName))
@@ -311,19 +484,19 @@ BOOL RemoveOldEntries()
 
 			long lCount = recset.GetRecordCount();
 
-			ARRAY IDs;
+			CClipIDs IDs;
 
 			while((lCount > lMax) && (!recset.IsBOF()))
 			{
-				//Don't delete entries that have shorcuts or the flag set
-				if(recset.m_lDontAutoDelete <= 0)
+				//Only delete entries that have no shortcut and don't have the flag set
+				if(recset.m_lShortCut == 0 && recset.m_lDontAutoDelete == 0)
 					IDs.Add(recset.m_lID);
 
 				lCount--;
 				recset.MovePrev();
 			}
 
-			CClip::Delete(IDs);
+			IDs.DeleteIDs();
 		}
 	}
 
@@ -339,9 +512,9 @@ BOOL RemoveOldEntries()
 			CMainTable recset;
 			recset.Open("SELECT * FROM Main "
 						"WHERE lDate < %d AND "
-						"lShortCut <= 0 AND lDontAutoDelete <= 0", now.GetTime());
+						"lShortCut = 0 AND lDontAutoDelete = 0", now.GetTime());
 
-			ARRAY IDs;
+			CClipIDs IDs;
 
 			while(!recset.IsEOF())
 			{
@@ -349,7 +522,7 @@ BOOL RemoveOldEntries()
 				recset.MoveNext();
 			}
 
-			CClip::Delete(IDs);
+			IDs.DeleteIDs();
 		}
 	}
 
@@ -377,4 +550,68 @@ BOOL EnsureDirectory(CString csPath)
 		return TRUE;
 
 	return FALSE;
+}
+
+BOOL ExecuteSQL( CString csSQL, BOOL bReportError, CDaoException** ppEx )
+{
+	try
+	{
+		theApp.EnsureOpenDB();
+		theApp.m_pDatabase->Execute(csSQL, dbFailOnError);
+	}
+	catch(CDaoException* e)
+	{
+		if( bReportError )
+			e->ReportError();
+
+		if( ppEx )
+			*ppEx = e;
+		else
+			e->Delete();
+
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+int GetFieldPos(CDaoRecordset& recs, LPCTSTR fieldName)
+{
+CDaoFieldInfo fi;
+int count = recs.GetFieldCount();
+
+	for( int i = 0; i < count; i++ )
+	{
+		recs.GetFieldInfo(i, fi);
+		if( fi.m_strName.Compare( fieldName ) == 0 )
+			return i; // when found a match, return it
+	}
+
+	return -1;
+}
+
+void VerifyFieldPos(CDaoRecordset& recs, LPCTSTR fieldName, int index)
+{
+CDaoFieldInfo fi;
+int count = recs.GetFieldCount();
+	VERIFY( index >= 0 && index < count );
+	recs.GetFieldInfo(index, fi);
+	VERIFY( fi.m_strName.Compare( fieldName ) == 0 );
+}
+
+CString GetFieldList(CDaoRecordset& recs)
+{
+CString field;
+CString list;
+CDaoFieldInfo fi;
+int count = recs.GetFieldCount();
+
+	for( int i = 0; i < count; i++ )
+	{
+		recs.GetFieldInfo(i, fi);
+		field = StrF("\n%d: ",i) + fi.m_strName;
+		list += field;
+	}
+
+	return list;
 }

+ 24 - 9
DatabaseUtilities.h

@@ -11,6 +11,14 @@
 
 #define DEFAULT_DB_NAME "DittoDB.mdb"
 
+#define CATCHDAO \
+	catch(CDaoException* e) \
+	{                       \
+		e->ReportError();   \
+		ASSERT(0);          \
+		e->Delete();        \
+	}
+
 class CDaoTableDefEx : public CDaoTableDef
 {
 public:
@@ -74,23 +82,30 @@ public:
 			ASSERT(FALSE);
 		}
 
-	return FALSE;
-		
+		return FALSE;
 	}
-
-
 	
 };
 
-BOOL DoCleanups();
+BOOL CreateBackup(CString csPath);
 CString GetDBName();
-BOOL CompactDatabase();
 CString GetDefaultDBName();
-BOOL RepairDatabase();
+
 BOOL CheckDBExists(CString csDBPath);
-BOOL RemoveOldEntries();
+BOOL ValidDB(CString csPath, BOOL bUpgrade=TRUE);
 BOOL CreateDB(CString csPath);
-BOOL ValidDB(CString csPath);
+BOOL Upgrade_mText(CDaoDatabase& db);
+BOOL Upgrade_Groups(CDaoDatabase& db);
+BOOL Upgrade_ShareData(CDaoDatabase& db);
+
+BOOL CompactDatabase();
+BOOL RepairDatabase();
+BOOL RemoveOldEntries();
+
 BOOL EnsureDirectory(CString csPath);
+BOOL ExecuteSQL(CString csSQL, BOOL bReportError = TRUE, CDaoException** ppEx = NULL);
+int GetFieldPos(CDaoRecordset& recs, LPCTSTR fieldName);
+void VerifyFieldPos(CDaoRecordset& recs, LPCTSTR fieldName, int index);
+CString GetFieldList(CDaoRecordset& recs);
 
 #endif // !defined(AFX_DATABASEUTILITES_H__039F53EB_228F_4640_8009_3D2B1FF435D4__INCLUDED_)

+ 93 - 2
MainTable.cpp

@@ -28,7 +28,12 @@ CMainTable::CMainTable(CDaoDatabase* pdb)
 	m_lDontAutoDelete = 0;
 	m_lTotalCopySize = 0;
 
-	m_nFieldCount = m_nFields = 6;
+	m_bIsGroup = FALSE;
+	m_lParentID = 0;
+	m_dOrder = 0;
+	m_lDataID = 0;
+
+	m_nFieldCount = m_nFields = 10;
 	//}}AFX_FIELD_INIT
 	m_nDefaultType = dbOpenDynaset;
 	m_bBindFields = true;
@@ -57,11 +62,18 @@ void CMainTable::DoFieldExchange(CDaoFieldExchange* pFX)
 	DFX_Long(pFX, _T("[lShortCut]"), m_lShortCut);
 	DFX_Long(pFX, _T("[lDontAutoDelete]"), m_lDontAutoDelete);
 	DFX_Long(pFX, _T("[lTotalCopySize]"), m_lTotalCopySize);
+	// GROUPS
+	DFX_Bool(pFX, _T("[bIsGroup]"), m_bIsGroup);
+	DFX_Long(pFX, _T("[lParentID]"), m_lParentID);
+	DFX_Double(pFX, _T("[dOrder]"), m_dOrder);
+	// sharing data
+	DFX_Long(pFX, _T("[lDataID]"), m_lDataID);
 	//}}AFX_FIELD_MAP
 }
 
 void CMainTable::Open(int nOpenType , LPCTSTR lpszSql , int nOptions)
 {
+	m_pDatabase = theApp.EnsureOpenDB();
 	OnQuery();
 	CDaoRecordset::Open(nOpenType, lpszSql, nOptions);
 }
@@ -76,8 +88,58 @@ void CMainTable::Requery()
 /////////////////////////////////////////////////////////////////////////////
 // CMainTable member functions
 
+CString CMainTable::GetDisplayText()
+{
+CString text = m_strText;
+	// assign tabs to 2 spaces (rather than the default 8)
+	text.Replace("\t", "  ");
+
+	if( g_Opt.m_bDescShowLeadingWhiteSpace )
+		return text;
+	// else, remove the leading indent from every line.
+
+	// get the lines
+CString token;
+CStringArray tokens;
+CTokenizer tokenizer(text,"\r\n");
+	while( tokenizer.Next( token ) )
+		tokens.Add( token );
+
+	// remove each line's indent
+char chFirst;
+CString line;
+int count = tokens.GetSize();
+	text = "";
+	for( int i=0; i < count; i++ )
+	{
+		line = tokens.ElementAt(i);
+		chFirst = line.GetAt(0);
+		if( chFirst == ' ' || chFirst == '\t' )
+		{
+			text += "» "; // show indication that the line is modified
+			text += line.TrimLeft();
+		}
+		else
+			text += line;
+		text += "\n";
+	}
+
+	return text;
+}
+
+// assigns the new autoincr ID to m_lID
+void CMainTable::AddNew()
+{
+	CDaoRecordset::AddNew();
+	// get the new, automatically assigned ID
+COleVariant varID;
+	GetFieldValue("lID", varID);
+	m_lID = varID.lVal;
+}
+
 void CMainTable::OnQuery()
-{}
+{
+}
 
 bool CMainTable::SetBindFields(bool bVal)
 {
@@ -93,6 +155,35 @@ bool bOld = m_bBindFields;
 	return bOld;
 }
 
+// copies the current source record to this current record
+void CMainTable::CopyRec(CMainTable& source)
+{
+//	m_lID = source.m_lID;
+	m_lDate = source.m_lDate;
+	m_strText = source.m_strText;
+//	m_lShortCut = source.m_lShortCut; // don't copy the shortcut
+	m_lDontAutoDelete = source.m_lDontAutoDelete;
+	m_lTotalCopySize = source.m_lTotalCopySize;
+	m_lDataID = source.m_lDataID;
+
+	m_bIsGroup = source.m_bIsGroup;
+	m_lParentID = source.m_lParentID;
+	m_dOrder = source.m_dOrder;
+}
+
+// makes a new copy of the current record and moves to the copy record
+void CMainTable::NewCopyRec()
+{
+	if( IsBOF() || IsEOF() )
+		return;
+
+CMainTable temp;
+	temp.CopyRec( *this ); // temporary copy
+	AddNew(); // overridden to fetch the autoincr lID
+	CopyRec( temp );
+	Update();
+}
+
 // only deletes from Main
 BOOL CMainTable::DeleteAll()
 {

+ 15 - 4
MainTable.h

@@ -22,6 +22,11 @@ public:
 	long	m_lShortCut;
 	long	m_lDontAutoDelete;
 	long	m_lTotalCopySize;
+
+	BOOL	m_bIsGroup;
+	long	m_lParentID;
+	double	m_dOrder;
+	long	m_lDataID;
 	//}}AFX_FIELD
 
 // Overrides
@@ -36,6 +41,8 @@ public:
 	//}}AFX_VIRTUAL
 
 public:
+	CString GetDisplayText();
+	void AddNew(); // assigns the new autoincr ID to m_lID
 	void OnQuery();
 
 	int		m_nFieldCount;
@@ -44,14 +51,18 @@ public:
 
 //	long GetID();
 
-	int		m_nCurPos;
-	long	m_lCurID; // used to validate m_nCurPos
+//	int		m_nCurPos;
+//	long	m_lCurID; // used to validate m_nCurPos
+
+	// copies the current source record to this current record
+	void CopyRec( CMainTable& source );
+	// makes a new copy of the current record and moves to the copy record
+	void NewCopyRec();
 
-	// only deletes from Main
+	// ONLY deletes from Main
 	static BOOL DeleteAll();
 	
 	static void LoadAcceleratorKeys( CAccels& accels );
-//	static HACCEL LoadAcceleratorKeys(); //!!!!!
 	void Open(LPCTSTR lpszFormat,...);
 
 // Implementation

+ 121 - 69
Misc.cpp

@@ -142,42 +142,6 @@ DWORD dwTestPID;
 	return dwMyPID == dwTestPID;
 }
 
-/* !!!!!
-HWND GetFocusWnd( CPoint *pPointCaret )
-{
-HWND hFocusWnd = 0;
-CPoint pt;
-
-	if( pPointCaret )
-		*pPointCaret = CPoint(-1, -1);
-
-	HWND hForeWnd = ::GetForegroundWindow();
-
-	if( !::IsWindow(hForeWnd) )
-		return 0;
-
-	DWORD dwMyThread = ::GetCurrentThreadId();
-	DWORD dwTargetPID;
-	DWORD dwTargetThread = ::GetWindowThreadProcessId( hForeWnd, &dwTargetPID );
-
-	// get the focus window's caret position
-	// attach to new focus window
-	if( ::AttachThreadInput(dwMyThread,dwTargetThread,TRUE) )
-	{
-		hFocusWnd = ::GetFocus();
-		::GetCaretPos( &pt );
-		::ClientToScreen( hFocusWnd, &pt );
-		// detach
-		::AttachThreadInput(dwMyThread,dwTargetThread,FALSE);
-	}
-
-	if( pPointCaret )
-		*pPointCaret = pt;
-
-	return hFocusWnd;
-}
-*/
-
 HWND GetFocusWnd(CPoint *pPointCaret)
 {
 	HWND hWndFocus = NULL;
@@ -440,6 +404,7 @@ BOOL CGetSetOptions::m_bSaveMultiPaste;
 BOOL CGetSetOptions::m_bShowPersistent;
 BOOL CGetSetOptions::m_bHistoryStartTop;
 long CGetSetOptions::m_bDescTextSize;
+BOOL CGetSetOptions::m_bDescShowLeadingWhiteSpace;
 
 CGetSetOptions g_Opt;
 
@@ -452,6 +417,7 @@ CGetSetOptions::CGetSetOptions()
 	m_bShowPersistent = GetShowPersistent();
 	m_bHistoryStartTop = GetHistoryStartTop();
 	m_bDescTextSize = GetDescTextSize();
+	m_bDescShowLeadingWhiteSpace = GetDescShowLeadingWhiteSpace();
 }
 
 CGetSetOptions::~CGetSetOptions()
@@ -907,6 +873,9 @@ BOOL CGetSetOptions::GetAutoHide()				{	return GetProfileLong("AutoHide", FALSE)
 void CGetSetOptions::SetDescTextSize(long lSize){	SetProfileLong("DescTextSize", lSize); m_bDescTextSize = lSize; }
 long CGetSetOptions::GetDescTextSize()			{	return GetProfileLong("DescTextSize", 500); }
 
+void CGetSetOptions::SetDescShowLeadingWhiteSpace(BOOL bVal){  SetProfileLong("DescShowLeadingWhiteSpace", bVal); m_bDescShowLeadingWhiteSpace = bVal; }
+BOOL CGetSetOptions::GetDescShowLeadingWhiteSpace()         {  return GetProfileLong("DescShowLeadingWhiteSpace", FALSE); }
+
 /*------------------------------------------------------------------*\
 	CHotKey - a single system-wide hotkey
 \*------------------------------------------------------------------*/
@@ -1030,7 +999,7 @@ CHotKey* pHotKey;
 int count = GetSize();
 	for( int i=0; i < count; i++ )
 	{
-		pHotKey = GetAt(i);
+		pHotKey = ElementAt(i);
 		if( pHotKey )
 			delete pHotKey;
 	}
@@ -1041,7 +1010,7 @@ int CHotKeys::Find( CHotKey* pHotKey )
 int count = GetSize();
 	for( int i=0; i < count; i++ )
 	{
-		if( pHotKey == GetAt(i) )
+		if( pHotKey == ElementAt(i) )
 			return i;
 	}
 	return -1;
@@ -1062,14 +1031,14 @@ void CHotKeys::LoadAllKeys()
 {
 int count = GetSize();
 	for( int i=0; i < count; i++ )
-		GetAt(i)->LoadKey();
+		ElementAt(i)->LoadKey();
 }
 
 void CHotKeys::SaveAllKeys()
 {
 int count = GetSize();
 	for( int i=0; i < count; i++ )
-		GetAt(i)->SaveKey();
+		ElementAt(i)->SaveKey();
 }
 
 void CHotKeys::RegisterAll( bool bMsgOnError )
@@ -1079,7 +1048,7 @@ CHotKey* pHotKey;
 int count = GetSize();
 	for( int i=0; i < count; i++ )
 	{
-		pHotKey = GetAt(i);
+		pHotKey = ElementAt(i);
 		if( !pHotKey->Register() )
 		{
 			str =  "Error Registering ";
@@ -1098,7 +1067,7 @@ CHotKey* pHotKey;
 int count = GetSize();
 	for( int i=0; i < count; i++ )
 	{
-		pHotKey = GetAt(i);
+		pHotKey = ElementAt(i);
 		if( !pHotKey->Unregister() )
 		{
 			str =  "Error Unregistering ";
@@ -1115,7 +1084,7 @@ void CHotKeys::GetKeys( ARRAY& keys )
 int count = GetSize();
 	keys.SetSize( count );
 	for( int i=0; i < count; i++ )
-		keys[i] = GetAt(i)->GetKey();
+		keys[i] = ElementAt(i)->GetKey();
 }
 
 // caution! this alters hotkeys based upon corresponding indexes
@@ -1124,7 +1093,7 @@ void CHotKeys::SetKeys( ARRAY& keys, bool bSave )
 int count = GetSize();
 	ASSERT( count == keys.GetSize() );
 	for( int i=0; i < count; i++ )
-		GetAt(i)->SetKey( keys[i], bSave );
+		ElementAt(i)->SetKey( keys[i], bSave );
 }
 
 bool CHotKeys::FindFirstConflict( ARRAY& keys, int* pX, int* pY )
@@ -1135,14 +1104,14 @@ int count = keys.GetSize();
 DWORD key;
 	for( i=0; i < count && !bConflict; i++ )
 	{
-		key = keys.GetAt(i);
+		key = keys.ElementAt(i);
 		// only check valid keys
 		if( key == 0 )
 			continue;
 		// scan the array for a duplicate
 		for( j=i+1; j < count; j++ )
 		{
-			if( keys.GetAt(j) == key )
+			if( keys.ElementAt(j) == key )
 			{
 				bConflict = true;
 				break;
@@ -1501,7 +1470,7 @@ void InitToolInfo( TOOLINFO& ti )
 {
 	// INITIALIZE MEMBERS OF THE TOOLINFO STRUCTURE
 	ti.cbSize = sizeof(TOOLINFO);
-	ti.uFlags = TTF_TRACK;
+	ti.uFlags = TTF_ABSOLUTE | TTF_TRACK;
 	ti.hwnd = NULL;
 	ti.hinst = NULL;
 	ti.uId = 0; // CPopup only uses uid 0
@@ -1518,35 +1487,69 @@ void InitToolInfo( TOOLINFO& ti )
 	- technique learned from codeproject "ToolTipZen" by "Zarembo Maxim"
 \*------------------------------------------------------------------*/
 
-// when using this constructor, you must call Init( hWnd ) before using the CPopup.
 CPopup::CPopup()
 {
-	m_bOwnTT = false;
-	m_hTTWnd = NULL;
-	m_bIsShowing = false;
+	Init();
 }
 
-CPopup::CPopup( CPoint& pos, HWND hTTWnd )
+// HWND_TOP
+CPopup::CPopup( int x, int y, HWND hWndPosRelativeTo, HWND hWndInsertAfter )
 {
-	m_Pos = pos;
-	Init(hTTWnd);
+	Init();
+	m_hWndPosRelativeTo = hWndPosRelativeTo;
+	m_hWndInsertAfter = hWndInsertAfter;
+	SetPos( CPoint(x,y) );
 }
 
 CPopup::~CPopup()
 {
+	Hide();
 	if( m_bOwnTT && ::IsWindow(m_hTTWnd) )
 		::DestroyWindow( m_hTTWnd );
 }
 
-void CPopup::Init( HWND hTTWnd, TOOLINFO* pTI )
+void CPopup::Init()
+{
+// initialize variables
+	m_bOwnTT = false;
+	m_hTTWnd = NULL;
+	m_bIsShowing = false;
+	m_bAllowShow = true; // used by AllowShow()
+
+	m_Pos.x = m_Pos.y = 0;
+	m_bTop = true;
+	m_bLeft = true;
+	m_bCenterX = false;
+	m_bCenterY = false;
+	m_hWndPosRelativeTo = NULL;
+
+RECT rcScreen;
+	// Get cordinates of the working area on the screen
+	SystemParametersInfo (SPI_GETWORKAREA, 0, &rcScreen, 0);
+	m_ScreenMaxX = rcScreen.right;
+	m_ScreenMaxY = rcScreen.bottom;
+
+	m_hWndInsertAfter = HWND_TOP; //HWND_TOPMOST
+
+	SetTTWnd();
+}
+
+void CPopup::SetTTWnd( HWND hTTWnd, TOOLINFO* pTI )
 {
 	if( pTI )
 		m_TI = *pTI;
 	else
 		InitToolInfo( m_TI );
 
+	if( m_bOwnTT && ::IsWindow(m_hTTWnd) )
+	{
+		if( !::IsWindow(hTTWnd) )
+			return; // we would have to recreate the one that already exists
+		::DestroyWindow( m_hTTWnd );
+	}
+
 	m_hTTWnd = hTTWnd;
-	if( hTTWnd )
+	if( ::IsWindow(m_hTTWnd) )
 	{
 		m_bOwnTT = false;
 		// if our uid tooltip already exists, get the data, else add it.
@@ -1558,7 +1561,6 @@ void CPopup::Init( HWND hTTWnd, TOOLINFO* pTI )
 		m_bOwnTT = true;
 		CreateToolTip();
 	}
-	m_bIsShowing = false;
 }
 
 void CPopup::CreateToolTip()
@@ -1594,29 +1596,73 @@ void CPopup::SetTimeout( int timeout )
 	::SendMessage(m_hTTWnd, TTM_SETDELAYTIME, TTDT_AUTOMATIC, timeout);
 }
 
-void CPopup::Show( CString text, CPoint& pos )
+void CPopup::SetPos( CPoint& pos )
 {
-	if( m_hTTWnd == NULL )
-		return;
+	m_Pos = pos;
+}
 
-	// deactivate if it is currently activated
-	::SendMessage(m_hTTWnd, TTM_TRACKACTIVATE, false, (LPARAM)(LPTOOLINFO) &m_TI);
+void CPopup::SetPosInfo( bool bTop, bool bCenterY, bool bLeft, bool bCenterX )
+{
+	m_bTop = bTop;
+	m_bCenterY = bCenterY;
+	m_bLeft = bLeft;
+	m_bCenterX = bCenterX;
+}
+
+void CPopup::AdjustPos( CPoint& pos )
+{
+CRect rel(0,0,0,0);
+CRect rect(0,0,0,0);
+
+//	::SendMessage(m_hTTWnd, TTM_ADJUSTRECT, TRUE, (LPARAM)&rect);
+	::GetWindowRect(m_hTTWnd,&rect);
+
+	if( ::IsWindow(m_hWndPosRelativeTo) )
+		::GetWindowRect(m_hWndPosRelativeTo, &rel);
+
+	rect.MoveToXY( rel.left, rel.top );
+
+	rect.OffsetRect( 0, pos.y - (m_bCenterY? rect.Height()/2: (m_bTop? 0: rect.Height())) );
+	if( rect.bottom > m_ScreenMaxY )
+		rect.OffsetRect( 0, m_ScreenMaxY - rect.bottom );
+
+	rect.OffsetRect( pos.x - (m_bCenterX? rect.Width()/2: (m_bLeft? 0: rect.Width())), 0 );
+	if( rect.right > m_ScreenMaxX )
+		rect.OffsetRect( m_ScreenMaxX - rect.right, 0 );
+
+	pos.x = rect.left;
+	pos.y = rect.top;
+}
 
+void CPopup::SendToolTipText( CString text )
+{
 	//Replace the tabs with spaces, the tooltip didn't like the \t s
 	text.Replace("\t", "  ");
-
 	m_TI.lpszText = (LPSTR) (LPCTSTR) text;
 
-	// make sure the tooltip will be on top.
-	::SetWindowPos( m_hTTWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE );
 	// this allows \n and \r to be interpreted correctly
 	::SendMessage(m_hTTWnd, TTM_SETMAXTIPWIDTH, 0, 500);
 	// set the text
 	::SendMessage(m_hTTWnd, TTM_SETTOOLINFO, 0, (LPARAM) (LPTOOLINFO) &m_TI);
-	// set the position
-	::SendMessage(m_hTTWnd, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD) MAKELONG(pos.x, pos.y));
-	// show the tooltip
+}
+
+void CPopup::Show( CString text, CPoint pos, bool bAdjustPos )
+{
+	if( m_hTTWnd == NULL )
+		return;
+
+	if( !m_bIsShowing )
+		::SendMessage(m_hTTWnd, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD) MAKELONG(-10000,-10000));
+
+	SendToolTipText( text );
 	::SendMessage(m_hTTWnd, TTM_TRACKACTIVATE, true, (LPARAM)(LPTOOLINFO) &m_TI);
+	if( bAdjustPos )
+		AdjustPos(pos);
+	// set the position
+	::SendMessage(m_hTTWnd, TTM_TRACKPOSITION, 0, (LPARAM)(DWORD) MAKELONG(pos.x,pos.y));
+
+	// make sure the tooltip will be on top.
+	::SetWindowPos( m_hTTWnd, m_hWndInsertAfter, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_NOACTIVATE );
 
 	m_bIsShowing = true;
 }
@@ -1624,11 +1670,17 @@ void CPopup::Show( CString text, CPoint& pos )
 void CPopup::Show( CString text )
 { Show( text, m_Pos ); }
 
+void CPopup::AllowShow( CString text )
+{
+	if( m_bAllowShow )
+		Show( text, m_Pos );
+}
+
 void CPopup::Hide()
 {
 	if( m_hTTWnd == NULL )
 		return;
 	// deactivate if it is currently activated
-	::SendMessage(m_hTTWnd, TTM_TRACKACTIVATE, false, (LPARAM)(LPTOOLINFO) &m_TI);
+	::SendMessage(m_hTTWnd, TTM_TRACKACTIVATE, FALSE, (LPARAM)(LPTOOLINFO) &m_TI);
 	m_bIsShowing = false;
 }

+ 30 - 11
Misc.h

@@ -10,14 +10,6 @@
 
 #define DELETE_PTR(ptr) {  if(ptr) {delete ptr; ptr = NULL;}  }
 
-#define CATCHDAO \
-	catch(CDaoException* e) \
-	{                       \
-		e->ReportError();   \
-		ASSERT(0);          \
-		e->Delete();        \
-	}
-
 #include "DatabaseUtilities.h"
 
 // Debugging
@@ -63,6 +55,8 @@ HGLOBAL NewGlobalH( HGLOBAL hSource, UINT nLen );
 int CompareGlobalHP( HGLOBAL hLeft, LPVOID pBuf, ULONG ulBufLen );
 int CompareGlobalHH( HGLOBAL hLeft, HGLOBAL hRight, ULONG ulBufLen );
 
+int GetScreenWidth();
+int GetScreenHeight();
 void GetMonitorRect(int iMonitor, LPRECT lpDestRect);
 int GetMonitorFromRect(LPRECT lpMonitorRect);
 
@@ -228,6 +222,9 @@ public:
 	static void		SetDescTextSize(long lSize);
 	static long		GetDescTextSize();
 
+	static BOOL		m_bDescShowLeadingWhiteSpace;
+	static void		SetDescShowLeadingWhiteSpace(BOOL bVal);
+	static BOOL		GetDescShowLeadingWhiteSpace();
 
 	/*
 	BOOL IsAutoRun();
@@ -402,19 +399,41 @@ public:
 	TOOLINFO m_TI; // struct specifying info about tool in ToolTip control
 
 	bool m_bIsShowing;
+
+	bool m_bTop;  // true if m_Pos.x is the top, false if the bottom
+	bool m_bLeft; // true if m_Pos.y is the left, false if the right
+	bool m_bCenterY; // true if m_Pos is the y center, false if corner
+	bool m_bCenterX; // true if m_Pos is the x center, false if corner
+	HWND m_hWndPosRelativeTo;
 	CPoint m_Pos;
 
+	int m_ScreenMaxX;
+	int m_ScreenMaxY;
+
+	HWND m_hWndInsertAfter;
+
+	bool m_bAllowShow; // used by SafeShow to determine whether to show or not
+
 	CPopup();
-	CPopup( CPoint& pos, HWND hTTWnd = NULL );
+	CPopup( int x, int y, HWND hWndPosRelativeTo = NULL, HWND hWndInsertAfter = HWND_TOP );
 	~CPopup();
 
-	void Init( HWND hTTWnd = NULL, TOOLINFO* pTI = NULL );
+	void Init();
+	void SetTTWnd( HWND hTTWnd = NULL, TOOLINFO* pTI = NULL );
 	void CreateToolTip();
 
 	void SetTimeout( int timeout );
 
-	void Show( CString text, CPoint& pos );
+	void AdjustPos( CPoint& pos );
+	void SetPos( CPoint& pos );
+	void SetPosInfo( bool bTop, bool bCenterY, bool bLeft, bool bCenterX );
+
+	void SendToolTipText( CString text );
+
+	void Show( CString text, CPoint pos, bool bAdjustPos = true );
 	void Show( CString text );
+	void AllowShow( CString text ); // only shows if m_bAllowShow is true
+
 	void Hide();
 };
 

+ 2 - 2
OptionsKeyBoard.cpp

@@ -84,9 +84,9 @@ ARRAY keys;
 
 	if( g_HotKeys.FindFirstConflict(keys,&x,&y) )
 	{
-		str =  g_HotKeys.GetAt(x)->GetName();
+		str =  g_HotKeys.ElementAt(x)->GetName();
 		str += " and ";
-		str += g_HotKeys.GetAt(y)->GetName();
+		str += g_HotKeys.ElementAt(y)->GetName();
 		str += " cannot be the same.";
 		MessageBox(str);
 		g_HotKeys.SetKeys( keys ); // restore the original values

+ 3 - 0
OptionsQuickPaste.cpp

@@ -37,6 +37,7 @@ void COptionsQuickPaste::DoDataExchange(CDataExchange* pDX)
 	DDX_Control(pDX, IDC_TRANSPARENCY, m_btEnableTransparency);
 	DDX_Control(pDX, IDC_CTRL_CLICK, m_btUseCtrlNum);
 	DDX_Control(pDX, IDC_HISTORY_START_TOP, m_btHistoryStartTop);
+	DDX_Control(pDX, IDC_DESC_SHOW_LEADING_WHITESPACE, m_btDescShowLeadingWhiteSpace);
 	//}}AFX_DATA_MAP
 }
 
@@ -66,6 +67,7 @@ BOOL COptionsQuickPaste::OnInitDialog()
 	else if(CGetSetOptions::GetQuickPastePosition() == POS_AT_PREVIOUS)
 		CheckDlgButton(IDC_AT_PREVIOUS, BST_CHECKED);
 
+	m_btDescShowLeadingWhiteSpace.SetCheck(g_Opt.m_bDescShowLeadingWhiteSpace);
 	m_btHistoryStartTop.SetCheck(g_Opt.m_bHistoryStartTop);
 	m_btUseCtrlNum.SetCheck(CGetSetOptions::GetUseCtrlNumForFirstTenHotKeys());
 
@@ -87,6 +89,7 @@ BOOL COptionsQuickPaste::OnApply()
 	else if(IsDlgButtonChecked(IDC_AT_PREVIOUS))
 		CGetSetOptions::SetQuickPastePosition(POS_AT_PREVIOUS);
 
+	g_Opt.SetDescShowLeadingWhiteSpace( m_btDescShowLeadingWhiteSpace.GetCheck() );
 	g_Opt.SetHistoryStartTop( m_btHistoryStartTop.GetCheck() );
 	CGetSetOptions::SetUseCtrlNumForFirstTenHotKeys(m_btUseCtrlNum.GetCheck());
 	CGetSetOptions::SetShowTextForFirstTenHotKeys(m_btShowText.GetCheck());

+ 1 - 0
OptionsQuickPaste.h

@@ -31,6 +31,7 @@ public:
 	CButton	m_btEnableTransparency;
 	CButton	m_btUseCtrlNum;
 	CButton m_btHistoryStartTop;
+	CButton m_btDescShowLeadingWhiteSpace;
 	//}}AFX_DATA
 
 

+ 12 - 21
OptionsStats.cpp

@@ -4,6 +4,7 @@
 #include "stdafx.h"
 #include "cp_main.h"
 #include "OptionsStats.h"
+#include "ProcessPaste.h"
 #include <sys/types.h>
 #include <sys/stat.h>
 
@@ -120,28 +121,18 @@ void COptionsStats::OnRemoveAll()
 {
 	if(MessageBox("This will remove all Copy Entries!\n\nContinue?", "Warning", MB_YESNO) == IDYES)
 	{
-		CMainTable MainTable;
-		if(MainTable.DeleteAll())
+		if( DeleteAllIDs() )
 		{
-			CDataTable DataTable;
-			if(DataTable.DeleteAll())
-			{
-				if(CompactDatabase())
-				{
-					RepairDatabase();
-
-					m_eSavedCopies.Empty();
-					m_eSavedCopyData.Empty();
-
-					struct _stat buf;
-					int nResult;
-					nResult = _stat(GetDBName(), &buf);
-					if(nResult == 0)
-						m_eDatabaseSize.Format("%d KB", (buf.st_size/1024));
-
-					UpdateData(FALSE);
-				}
-			}
+			m_eSavedCopies.Empty();
+			m_eSavedCopyData.Empty();
+
+			struct _stat buf;
+			int nResult;
+			nResult = _stat(GetDBName(), &buf);
+			if(nResult == 0)
+				m_eDatabaseSize.Format("%d KB", (buf.st_size/1024));
+
+			UpdateData(FALSE);
 		}
 	}
 }

+ 81 - 139
ProcessCopy.cpp

@@ -96,7 +96,7 @@ int count = GetSize();
 	CClip - holds multiple CClipFormats and CopyClipboard() statistics
 \*----------------------------------------------------------------------------*/
 
-CClip::CClip() : m_ID(0), m_lTotalCopySize(0)
+CClip::CClip() : m_ID(0), m_DataID(0), m_lTotalCopySize(0)
 {}
 
 CClip::~CClip()
@@ -113,6 +113,7 @@ void CClip::Clear()
 	m_Time = 0;
 	m_Desc = "";
 	m_lTotalCopySize = 0;
+	m_DataID = 0;
 	EmptyFormats();
 }
 
@@ -205,7 +206,7 @@ CClipTypes* pTypes = pClipTypes;
 
 	// reset copy stats
 	m_lTotalCopySize = 0;
-	m_Desc = "[Ditto Error] !!BAD DESCRIPTION!!";
+	m_Desc = "[Ditto Error] BAD DESCRIPTION";
 
 	// Get Description String
 	// NOTE: We make sure that the description always corresponds to the
@@ -226,7 +227,7 @@ CClipFormat cf;
 int numTypes = pTypes->GetSize();
 	for(int i = 0; i < numTypes; i++)
 	{
-		cf.m_cfType = pTypes->GetAt(i);
+		cf.m_cfType = pTypes->ElementAt(i);
 
 		// is this the description we already fetched?
 		if( cf.m_cfType == cfDesc.m_cfType )
@@ -279,7 +280,7 @@ bool CClip::SetDescFromText( HGLOBAL hgData )
 
 bool bRet = false;
 char* text = (char *) GlobalLock(hgData);
-ULONG ulBufLen = GlobalSize(hgData);
+long ulBufLen = GlobalSize(hgData);
 
 	ASSERT( text != NULL );
 
@@ -309,30 +310,34 @@ bool CClip::SetDescFromType()
 	return m_Desc.GetLength() > 0;
 }
 
-bool CClip::AddToDB()
+bool CClip::AddToDB( bool bCheckForDuplicates )
 {
 bool bResult;
 	try
 	{
-		CMainTable recset;
-
-		if( FindDuplicate( recset, g_Opt.m_bAllowDuplicates ) )
+		if( bCheckForDuplicates )
 		{
-			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;
-		}
+			CMainTable recset;
 
-		if( recset.IsOpen() )
-			recset.Close();
+			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
 
-	bResult = AddToMainTable() && AddToDataTable();
+	// AddToDataTable must go first in order to assign m_DataID
+	bResult = AddToDataTable() && AddToMainTable();
 
 	// should be emptied by AddToDataTable
 	ASSERT( m_Formats.GetSize() == 0 );
@@ -355,7 +360,7 @@ bool CClip::FindDuplicate( CMainTable& recset, BOOL bCheckLastOnly )
 			// 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_lID) == 0) )
+			    (CompareFormatDataTo(recset.m_lDataID) == 0) )
 			{	return true; }
 			return false;
 		}
@@ -365,7 +370,7 @@ bool CClip::FindDuplicate( CMainTable& recset, BOOL bCheckLastOnly )
 		while( !recset.IsEOF() )
 		{
 			//if there is any then look if it is an exact match
-			if( CompareFormatDataTo(recset.m_lID) == 0 )
+			if( CompareFormatDataTo(recset.m_lDataID) == 0 )
 				return true;
 
 			recset.MoveNext();
@@ -376,16 +381,15 @@ bool CClip::FindDuplicate( CMainTable& recset, BOOL bCheckLastOnly )
 	return false;
 }
 
-int CClip::CompareFormatDataTo( long lID )
+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 lParentID = %d", lID);
+	CDataTable recset;
+		recset.Open("SELECT * FROM Data WHERE lDataID = %d", lDataID);
 
 		if( !recset.IsBOF() && !recset.IsEOF() )
 		{
@@ -440,16 +444,24 @@ long lDate;
 
 		lDate = (long) m_Time.GetTime();
 
-		recset.AddNew();
+		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.MoveLast();
-		
-		m_ID = recset.m_lID;
+
+//		recset.SetBookmark( recset.GetLastModifiedBookmark() );
+//		m_ID = recset.m_lID;
 
 		recset.Close();
 	}
@@ -466,21 +478,26 @@ long lDate;
 // Empties m_Formats as it saves them to the Data Table.
 bool CClip::AddToDataTable()
 {
-	ASSERT( m_ID != 0 );
+	VERIFY( m_DataID <= 0 ); // this func will assign m_DataID
 	try
 	{
-	CDataTable recset;
 	CClipFormat* pCF;
-
-		recset.Open(AFX_DAO_USE_DEFAULT_TYPE, "SELECT * FROM Data" ,NULL);
+	CDataTable recset;
+		recset.Open(dbOpenTable,"Data");
 
 		for( int i = m_Formats.GetSize()-1; i >= 0 ; i-- )
 		{
 			pCF = & m_Formats.ElementAt(i);
 
-			recset.AddNew();
+			recset.AddNew(); // overridden to assign new autoincr ID to m_lID
 
-			recset.m_lParentID = m_ID;
+			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) );
@@ -489,6 +506,8 @@ bool CClip::AddToDataTable()
 
 			m_Formats.RemoveAt( i ); // the recset now owns the global
 		}
+
+		recset.Close();
 		return true;
 	}
 	CATCHDAO
@@ -524,97 +543,7 @@ long lDate;
 	CATCHDAO
 }
 
-#define DELETE_CHUNCK_SIZE 100
-
 // STATICS
-BOOL CClip::Delete( ARRAY& IDs )
-{
-	#ifdef _DEBUG
-		DWORD dTick = GetTickCount();
-	#endif
-
-	if(IDs.GetSize() <= 0)
-		return FALSE;
-
-	int nStart = 0;
-	int nEnd = DELETE_CHUNCK_SIZE;
-	int nLoops = (IDs.GetSize() / DELETE_CHUNCK_SIZE) + 1;
-	BOOL bRet = TRUE;
-	CString csMainSQL;
-	CString csDataSQL;
-	CString csMainFormat;
-
-	for(int n = 0; n < nLoops; n++)
-	{
-		csMainSQL = "DELETE FROM Main WHERE";
-		csDataSQL = "DELETE FROM Data WHERE";
-		csMainFormat.Empty();
-
-		csMainFormat.Format(" lID = %d", IDs[nStart]);
-		csMainSQL += csMainFormat;
-
-		csMainFormat.Format(" lParentID = %d", IDs[nStart]);
-		csDataSQL += csMainFormat;
-
-		nEnd = min(nEnd, IDs.GetSize());
-
-		for(int i = nStart+1; i < nEnd; i++)
-		{
-			csMainFormat.Format(" Or lID = %d", IDs[i]);
-			csMainSQL += csMainFormat;
-
-			csMainFormat.Format(" Or lParentID = %d", IDs[i]);
-			csDataSQL += csMainFormat;
-		}
-
-		nStart = nEnd;
-		nEnd += DELETE_CHUNCK_SIZE;
-			
-		bRet = TRUE;
-
-		try
-		{
-			theApp.EnsureOpenDB();
-			theApp.m_pDatabase->Execute(csMainSQL, dbFailOnError);
-			theApp.m_pDatabase->Execute(csDataSQL, dbFailOnError);
-		}
-		catch(CDaoException* e)
-		{
-			AfxMessageBox(e->m_pErrorInfo->m_strDescription);
-			e->Delete();
-			bRet = FALSE;
-		}
-	}
-
-	#ifdef _DEBUG
-	{
-		CString cs;
-		cs.Format("Delete Time = %d\n", GetTickCount() - dTick);
-		TRACE(cs);
-	}
-	#endif	
-
-	return bRet;
-}
-
-BOOL CClip::DeleteAll()
-{
-BOOL bRet = FALSE;
-	try
-	{
-		theApp.EnsureOpenDB();
-		theApp.m_pDatabase->Execute("DELETE * FROM Main", dbFailOnError);
-		theApp.m_pDatabase->Execute("DELETE * FROM Data", dbFailOnError);
-		bRet = TRUE;
-	}
-	catch(CDaoException* e)
-	{
-		AfxMessageBox(e->m_pErrorInfo->m_strDescription);
-		e->Delete();
-	}
-
-	return bRet;
-}
 
 // Allocates a Global containing the requested Clip Format Data
 HGLOBAL CClip::LoadFormat( long lID, UINT cfType )
@@ -622,18 +551,24 @@ HGLOBAL CClip::LoadFormat( long lID, UINT cfType )
 HGLOBAL hGlobal = 0;
 	try
 	{
-		CDataTable recset;
+	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));
 
-		//Open the data table for all that have the parent id and format
-		CString csSQL;
-		csSQL.Format("SELECT * FROM Data WHERE lParentID = %d AND 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???
+			// XOR take the recset's HGLOBAL... is this SAFE??
 //			hGlobal = recset.TakeData();
 			if( !hGlobal || ::GlobalSize(hGlobal) == 0 )
 			{
@@ -663,14 +598,18 @@ HGLOBAL hGlobal = 0;
 
 		//Open the data table for all that have the parent id
 		CString csSQL;
-		csSQL.Format("SELECT * FROM Data WHERE lParentID = %d", lID);
+		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() )
 		{
 			// 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???
+			// XOR take the recset's HGLOBAL... is this SAFE??
 //			hGlobal = recset.TakeData();
 			if( !hGlobal || ::GlobalSize(hGlobal) == 0 )
 			{
@@ -697,11 +636,14 @@ void CClip::LoadTypes( long lID, CClipTypes& types )
 	types.RemoveAll();
 	try
 	{
-		CDataTable recset;
+	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);
 
-		//Open the data table for all that have the parent id
-		CString csSQL;
-		csSQL.Format("SELECT * FROM Data WHERE lParentID = %d", lID );
 		recset.Open(AFX_DAO_USE_DEFAULT_TYPE, csSQL);
 
 		while( !recset.IsEOF() )
@@ -723,7 +665,7 @@ void CClip::LoadTypes( long lID, CClipTypes& types )
 CClipList::~CClipList()
 {
 CClip* pClip;
-	while( GetCount() )
+	while( GetSize() )
 	{
 		pClip = RemoveHead();
 		DELETE_PTR( pClip );
@@ -740,7 +682,7 @@ CClip* pClip;
 POSITION pos;
 bool bResult;
 
-	nRemaining = GetCount();
+	nRemaining = GetSize();
 	pos = GetHeadPosition();
 	while( pos )
 	{
@@ -982,7 +924,7 @@ CCopyThread::~CCopyThread()
 	m_SharedConfig.DeleteTypes();
 	DELETE_PTR( m_pClipboardViewer );
 	if( m_pClips )
-		ASSERT( m_pClips->GetCount() == 0 );
+		ASSERT( m_pClips->GetSize() == 0 );
 	DELETE_PTR( m_pClips );
 	::DeleteCriticalSection(&m_CS);
 }

+ 3 - 6
ProcessCopy.h

@@ -64,6 +64,7 @@ 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
 
 	// statistics assigned by LoadFromClipboard
@@ -85,22 +86,18 @@ public:
 	bool SetDescFromType();
 
 	// Immediately save this clip to the db (empties m_Formats due to AddToDataTable).
-	bool AddToDB();
+	bool AddToDB( bool bCheckForDuplicates = true );
 	bool AddToMainTable(); // assigns m_ID
 	bool AddToDataTable(); // Empties m_Formats as it saves them to the Data Table.
 
 	// if a duplicate exists, set recset to the duplicate and return true
 	bool FindDuplicate( CMainTable& recset, BOOL bCheckLastOnly = FALSE );
-	int  CompareFormatDataTo( long lID );
+	int  CompareFormatDataTo( long lDataID );
 
 	// changes m_Time to be later than the latest clip entry in the db
 	void MakeLatestTime();
 
 // STATICS
-	// deletes from both Main and Data Tables
-	static BOOL Delete( ARRAY& IDs );
-	static BOOL DeleteAll();
-
 	// 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

+ 558 - 6
ProcessPaste.cpp

@@ -8,8 +8,44 @@ 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;
+}
+
+
 /*------------------------------------------------------------------*\
-	Globals
+	ID based Globals
 \*------------------------------------------------------------------*/
 
 BOOL MarkClipAsPasted(long lID)
@@ -45,6 +81,250 @@ BOOL MarkClipAsPasted(long lID)
 	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
@@ -61,7 +341,7 @@ int count = GetSize();
 	if( count <= 0 )
 		return 0;
 	if( count == 1 )
-		return CClip::LoadFormat( GetAt(0), cfType );
+		return CClip::LoadFormat( ElementAt(0), cfType );
 	CString text = AggregateText( CF_TEXT, "\r\n" );
 	return NewGlobalP( (void*)(LPCSTR) text, text.GetLength()+1 );
 }
@@ -73,7 +353,7 @@ int count = GetSize();
 	if( count > 1 )
 		types.Add( CF_TEXT );
 	else if( count == 1 )
-		CClip::LoadTypes( GetAt(0), types );
+		CClip::LoadTypes( ElementAt(0), types );
 }
 
 // Aggregates the cfType Format Data of the Clip IDs in this array, assuming
@@ -89,7 +369,7 @@ CString CClipIDs::AggregateText( UINT cfType, char* pSeparator )
 	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?
+	//  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);
@@ -98,7 +378,13 @@ CString CClipIDs::AggregateText( UINT cfType, char* pSeparator )
 	int numIDs = GetSize();
 	int* pIDs = GetData();
 
-	csSQL.Format("SELECT * FROM Data WHERE strClipBoardFormat = \'%s\' AND lParentID = %%d", GetFormatName(cfType));
+	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++ )
@@ -136,6 +422,272 @@ CString CClipIDs::AggregateText( UINT cfType, char* pSeparator )
 	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
@@ -278,6 +830,6 @@ void CProcessPaste::MarkAsPasted()
 {
 CClipIDs& clips = GetClipIDs();
 	if( clips.GetSize() == 1 )
-		MarkClipAsPasted( clips.GetAt(0) );
+		MarkClipAsPasted( clips.ElementAt(0) );
 }
 

+ 52 - 1
ProcessPaste.h

@@ -12,13 +12,37 @@
 #include "ArrayEx.h"
 #include "ProcessCopy.h"
 
+// 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 );
+
+/*------------------------------------------------------------------*\
+	ID based Globals
+\*------------------------------------------------------------------*/
+
 // Sets lID's lDate to GetCurrentTime() and updates paste stats
 BOOL MarkClipAsPasted(long lID);
 
+long NewGroupID( long lParentID = 0, CString text = "" );
+// creates copies of all lSrc Data and returns the copy's lDataID (or 0 on fail)
+long NewCopyDataID( long lSrc );
+
+BOOL DeleteID( long lID, bool bDisband = true ); // deletes the given item
+// deletes all items in the group, but not the group record itself
+BOOL DeleteGroupID( long lGroupID, bool bDisband = true );
+BOOL DeleteDataID( long lDataID ); // deletes all data for the given ID
+// Deletes everything in the Main and Data tables
+BOOL DeleteAllIDs();
+
+// all "formatIDs" (Data.lID) must be elements of the "lDataID" (Data.lDataID) set
+BOOL DeleteFormats( long lDataID, ARRAY& formatIDs );
+
+
 /*------------------------------------------------------------------*\
 	CClipIDs - an array of Clip IDs
 \*------------------------------------------------------------------*/
-class CClipIDs : public ARRAY
+
+class CClipIDs : public CArrayEx<int>
 {
 public:
 // PASTING FUNCTIONS
@@ -30,6 +54,33 @@ public:
 	// Aggregates the cfType Format Data of the Clip IDs in this array, assuming
 	//  each Format is NULL terminated and placing pSeparator between them.
 	CString AggregateText( UINT cfType, char* pSeparator );
+
+// MANAGEMENT FUNCTIONS
+
+	// returns the address of the given id in this array or NULL.
+	long* FindID( long lID );
+
+	// 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 MoveTo( long lParentID, double dFirst = 0, double dIncrement = -1 );
+
+	// reorders the "lParentID" Group, inserting before the given id.
+	//  if the id cannot be found, this appends the IDs.
+	BOOL ReorderGroupInsert( long lParentID, long lInsertBeforeID = 0 );
+
+	// Empties this array and fills it with the elements of the given group ID
+	BOOL LoadElementsOf( long lGroupID );
+
+	// 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.
+	BOOL CopyTo( long lParentID, CClipIDs* pCopies = NULL,
+		CMainTable* pAddNewTable = NULL, CMainTable* pSeekTable = NULL );
+
+	BOOL DeleteIDs( bool bDisband = true );
 };
 
 /*------------------------------------------------------------------*\

+ 84 - 56
QListCtrl.cpp

@@ -42,11 +42,10 @@ CQListCtrl::CQListCtrl()
 	lf.lfPitchAndFamily = VARIABLE_PITCH | FF_DONTCARE;
 	lstrcpy(lf.lfFaceName, "Small Font");
 
-	m_SmallFont = CreateFontIndirect(&lf);
+	m_SmallFont = ::CreateFontIndirect(&lf);
 
 	m_bShowTextForFirstTenHotKeys = true;
 	m_bStartTop = true;
-//	m_Accelerator = NULL; //!!!!!
 }
 
 CQListCtrl::~CQListCtrl()
@@ -57,6 +56,9 @@ CQListCtrl::~CQListCtrl()
 	if(m_pwchTip != NULL)
 		delete m_pwchTip;
 
+	if( m_SmallFont )
+		::DeleteObject( m_SmallFont );
+
 	DestroyAndCreateAccelerator(FALSE);
 }
 
@@ -118,9 +120,9 @@ END_MESSAGE_MAP()
 
 void CQListCtrl::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) 
 {
-	LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR;
+	LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR;
 	
-	switch (pLVKeyDow->wVKey)
+	switch (pLVKeyDown->wVKey)
 	{
 	case VK_RETURN:
 		{
@@ -343,10 +345,10 @@ void CQListCtrl::OnCustomdrawList(NMHDR* pNMHDR, LRESULT* pResult)
 		CPen *pOldPen = NULL;
 		COLORREF OldColor = -1;
 		int nOldBKMode = -1;
-		
-        // Draw the background of the list item.  Colors are selected 
-        // according to the item's state.
-        if(rItem.state & LVIS_SELECTED)
+
+		// Draw the background of the list item.  Colors are selected 
+		// according to the item's state.
+		if(rItem.state & LVIS_SELECTED)
 		{
             if(bListHasFocus)
 			{
@@ -386,6 +388,15 @@ void CQListCtrl::OnCustomdrawList(NMHDR* pNMHDR, LRESULT* pResult)
 		GetItemText(nItem, 0, lpszText, g_Opt.m_bDescTextSize);
 		csText.ReleaseBuffer();
 
+		// extract symbols
+		CString strSymbols;
+		int nSymEnd = csText.Find('|');
+		if( nSymEnd >= 0 )
+		{
+			strSymbols = csText.Left(nSymEnd);
+			csText = csText.Mid(nSymEnd+1);
+		}
+
 		// set firstTenNum to the first ten number (1-10) corresponding to
 		//  the current nItem.
 		// -1 means that nItem is not in the FirstTen block.
@@ -396,8 +407,42 @@ void CQListCtrl::OnCustomdrawList(NMHDR* pNMHDR, LRESULT* pResult)
 			rcText.left += 12;
 		}
 
-		pDC->DrawText(lpszText, rcText, DT_VCENTER | DT_EXPANDTABS);
-		
+		// if we are inside a group, don't display the "in group" flag
+		if( theApp.m_GroupID > 0 )
+		{
+		int nFlag = strSymbols.Find("!");
+			if( nFlag >= 0 )
+				strSymbols.Delete(nFlag);
+		}
+
+		// draw the symbol box
+		if( strSymbols.GetLength() > 0 )
+		{
+			strSymbols = " " + strSymbols + " "; // leave space for box
+			// add spaces to leave room for the symbols
+		CRect rectSym(rcText.left, rcText.top+1, rcText.left, rcText.top+1);
+		CRect rectSpace(0,0,0,0);
+			//Get text bounds
+			pDC->DrawText(" ", &rectSpace, DT_VCENTER | DT_EXPANDTABS | DT_CALCRECT);
+			pDC->DrawText(strSymbols, &rectSym, DT_VCENTER | DT_EXPANDTABS | DT_CALCRECT);
+			VERIFY( rectSpace.Width() > 0 );
+
+		int numSpaces = rectSym.Width() / rectSpace.Width();
+			numSpaces++;
+			csText = CString(' ',numSpaces) + csText;
+
+			// draw the symbols
+//			pDC->FillSolidRect( rectSym, GetSysColor(COLOR_INFOBK) );
+			pDC->FillSolidRect( rectSym, RGB(0,255,255) );
+	        pDC->Draw3dRect(rectSym, GetSysColor(COLOR_3DLIGHT), GetSysColor(COLOR_3DDKSHADOW));
+//		COLORREF crOld = pDC->SetTextColor(GetSysColor(COLOR_INFOTEXT));
+		COLORREF crOld = pDC->SetTextColor(0);
+			pDC->DrawText(strSymbols, rectSym, DT_VCENTER | DT_EXPANDTABS);
+			pDC->SetTextColor(crOld);
+		}
+
+		pDC->DrawText(csText, rcText, DT_VCENTER | DT_EXPANDTABS);
+
         // Draw a focus rect around the item if necessary.
         if(bListHasFocus && (rItem.state & LVIS_FOCUSED))
 			pDC->DrawFocusRect(rcItem);
@@ -431,6 +476,7 @@ void CQListCtrl::OnCustomdrawList(NMHDR* pNMHDR, LRESULT* pResult)
 			pDC->SelectObject(hOldFont);
 		}
 
+		// restore the previous values
 		if(pOldPen)
 			pDC->SelectObject(pOldPen);
 
@@ -572,7 +618,7 @@ int CQListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
 	EnableToolTips();
 
 	m_Popup.Init();
-//	m_Popup.Init( GetToolTips()->m_hWnd );
+//	m_Popup.SetTTWnd( GetToolTips()->m_hWnd );
 //	m_Popup.m_TI.hwnd = m_hWnd;
     
 	return 0;
@@ -580,20 +626,7 @@ int CQListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
 
 BOOL CQListCtrl::PreTranslateMessage(MSG* pMsg) 
 {
-	/* !!!!!
-	//if(m_Accelerator)
-	//{
-	//	m_CheckingAccelerator = true;
-	//	if(TranslateAccelerator(m_hWnd, m_Accelerator, pMsg) != 0)
-	//	{
-	//		m_CheckingAccelerator = false;
-	//		return TRUE;
-	//	}
-	//	m_CheckingAccelerator = false;
-	//}
-	*/
-
-	DWORD dID;
+DWORD dID;
 	if(m_Accels.OnMsg(pMsg, dID))
 		if(GetParent()->SendMessage(NM_SELECT_DB_ID, dID, 0) )
 			return TRUE;
@@ -637,6 +670,30 @@ BOOL CQListCtrl::PreTranslateMessage(MSG* pMsg)
 
 		switch( vk )
 		{
+		case 'X': // Ctrl-X = Cut (prepare for moving the items into a Group)
+			if(GetKeyState(VK_CONTROL) & 0x8000)
+			{
+				theApp.IC_Cut(); // uses selection
+				return TRUE;
+			}
+			break;
+
+		case 'C': // Ctrl-C = Copy (prepare for copying the items into a Group)
+			if(GetKeyState(VK_CONTROL) & 0x8000)
+			{
+				theApp.IC_Copy(); // uses selection
+				return TRUE;
+			}
+			break;
+
+		case 'V': // Ctrl-V = Paste (actually performs the copy or move of items into the current Group)
+			if(GetKeyState(VK_CONTROL) & 0x8000)
+			{
+				theApp.IC_Paste();
+				return TRUE;
+			}
+			break;
+
 		case 'A': // Ctrl-A = Select All
 			if(GetKeyState(VK_CONTROL) & 0x8000)
 			{
@@ -652,7 +709,7 @@ BOOL CQListCtrl::PreTranslateMessage(MSG* pMsg)
 		case VK_F3:
 			{
 				ShowFullDescription();
-			
+				return TRUE;
 				break;
 			}
 		} // end switch(vk)
@@ -669,7 +726,7 @@ void CQListCtrl::ShowFullDescription()
 	CRect rc;
 	GetItemRect(nItem, rc, LVIR_BOUNDS);
 	ClientToScreen(rc);
-	m_Popup.m_Pos = CPoint(rc.left, rc.bottom);
+	m_Popup.m_Pos = CPoint(rc.left, rc.bottom); // rc.top??
 	CString cs;
 	GetToolTipText(nItem, cs);
 	m_Popup.Show( cs );
@@ -737,39 +794,10 @@ void CQListCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
 
 void CQListCtrl::DestroyAndCreateAccelerator(BOOL bCreate)
 {
-// !!!!!!
-	//if(m_Accelerator)
-	//{
-	//	DestroyAcceleratorTable(m_Accelerator);
-	//	m_Accelerator = NULL;
-	//}
-	//
-	//if(bCreate)
-	//	m_Accelerator = CMainTable::LoadAcceleratorKeys();
-
-//	m_Accels.Clear();
-
 	if( bCreate )
 		CMainTable::LoadAcceleratorKeys( m_Accels );
 }
 
-/* !!!!!
-//BOOL CQListCtrl::OnCommand(WPARAM wParam, LPARAM lParam) 
-//{
-//	//return 1 if from accelerator
-//	if((HIWORD(wParam) == 1) && (m_CheckingAccelerator))
-//	{
-//		USHORT usPasteID = LOWORD(wParam);
-//
-//		GetParent()->SendMessage(NM_SELECT_DB_ID, usPasteID, 0);
-//
-//		return TRUE;
-//	}
-//	
-//	return CListCtrl::OnCommand(wParam, lParam);
-//}
-*/
-
 void CQListCtrl::OnKillFocus(CWnd* pNewWnd)
 {
 	CListCtrl::OnKillFocus(pNewWnd);

+ 3 - 3
QListCtrl.h

@@ -18,6 +18,9 @@
 #define NM_GETTOOLTIPTEXT	        WM_USER+0x108
 #define NM_SELECT_DB_ID		        WM_USER+0x109
 #define NM_SELECT_INDEX		        WM_USER+0x110
+//#define NM_LIST_CUT			        WM_USER+0x111
+//#define NM_LIST_COPY		        WM_USER+0x112
+//#define NM_LIST_PASTE		        WM_USER+0x113
 
 class CQListToolTipText
 {
@@ -46,7 +49,6 @@ public:
 	public:
 	virtual int OnToolHitTest(CPoint point, TOOLINFO * pTI) const;
 	virtual BOOL PreTranslateMessage(MSG* pMsg);
-//	virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam); //!!!!!!
 	//}}AFX_VIRTUAL
 
 // Implementation
@@ -98,8 +100,6 @@ protected:
 
 	//Accelerator
 	CAccels	m_Accels;
-//	HACCEL	m_Accelerator; // !!!!!
-//	bool	m_CheckingAccelerator;
 	
 	// Generated message map functions
 protected:

+ 227 - 80
QPasteWnd.cpp

@@ -31,12 +31,12 @@ CQPasteWnd::CQPasteWnd()
 {	
 	m_Title = QPASTE_TITLE;
 	m_bHideWnd = true;
-	m_bAscending = false;
+	m_strSQLSearch = "";
 }
 
 CQPasteWnd::~CQPasteWnd()
 {
-	
+
 }
 
 
@@ -98,6 +98,8 @@ BEGIN_MESSAGE_MAP(CQPasteWnd, CWndEx)
 	ON_COMMAND(ID_MENU_ALLWAYSONTOP, OnMenuAllwaysontop)
 	ON_COMMAND(ID_SORT_ASCENDING, OnSortAscending)
 	ON_COMMAND(ID_SORT_DESCENDING, OnSortDescending)
+	ON_COMMAND(ID_MENU_NEWGROUP, OnMenuNewGroup)
+	ON_COMMAND(ID_MENU_NEWGROUPSELECTION, OnMenuNewGroupSelection)
 END_MESSAGE_MAP()
 
 
@@ -187,7 +189,7 @@ void CQPasteWnd::MoveControls()
 
 	int nWidth = cx;
 
-	if(m_Recset.m_strFilter.IsEmpty() == FALSE)
+	if( m_strSQLSearch.IsEmpty() == FALSE )
 	{
 		m_btCancel.MoveWindow(cx - 20, cy - 20, 20, 20);
 		nWidth -= 19;
@@ -240,6 +242,11 @@ BOOL CQPasteWnd::HideQPasteWindow()
 
 	m_lstHeader.DestroyAndCreateAccelerator(FALSE);
 
+	// save the caret position
+int nCaretPos = m_lstHeader.GetCaret();
+	if( nCaretPos >= 0 )
+		theApp.m_FocusID = m_lstHeader.GetItemData( nCaretPos );
+
 	//Save the size
 	CRect rect;
 	GetWindowRectEx(rect);
@@ -314,51 +321,102 @@ bool CQPasteWnd::Add(const CString &csHeader, const CString &csText, int nID)
 	return true;
 }
 
-LRESULT CQPasteWnd::OnListSelect_DB_ID(WPARAM wParam, LPARAM lParam)
+BOOL CQPasteWnd::OpenID( long lID )
 {
-	CProcessPaste paste;
-	paste.GetClipIDs().Add(wParam);
+	if( theApp.EnterGroupID(lID) )
+		return TRUE;
+
+	// else, it is a clip, so paste it
+CProcessPaste paste;
+	paste.GetClipIDs().Add( lID );
 	paste.DoPaste();
 	theApp.OnPasteCompleted();
-
 	return TRUE;
 }
 
-LRESULT CQPasteWnd::OnListSelect_Index(WPARAM wParam, LPARAM lParam)
+BOOL CQPasteWnd::OpenSelection()
 {
-	if( (int) wParam >= m_lstHeader.GetItemCount() )
+ARRAY IDs;
+	m_lstHeader.GetSelectionItemData( IDs );
+    
+int count = IDs.GetSize();
+
+	if( count <= 0 )
 		return FALSE;
 
-	CProcessPaste paste;
-	paste.GetClipIDs().Add( m_lstHeader.GetItemData(wParam) );
+	if( count == 1 )
+		return OpenID( IDs[0] );
+	// else count > 1
+
+CProcessPaste paste;
+	paste.GetClipIDs().Copy( IDs );
 	paste.DoPaste();
 	theApp.OnPasteCompleted();
-
 	return TRUE;
 }
 
-LRESULT CQPasteWnd::OnListSelect(WPARAM wParam, LPARAM lParam)
+BOOL CQPasteWnd::OpenIndex( long nItem )
 {
-	int nCount = (int) wParam;
-	long *pItems = (long*) lParam;
+	return OpenID( m_lstHeader.GetItemData(nItem) );
+}
+
+BOOL CQPasteWnd::NewGroup( bool bGroupSelection )
+{
+long lID = NewGroupID( theApp.GetValidGroupID() );
+
+	if( lID <= 0 )
+		return FALSE;
 
-	if(nCount <= 0)
+	if( !bGroupSelection )
+	{
+		theApp.m_FocusID = lID; // focus on the new group
+		FillList();
 		return TRUE;
+	}
 
-	CProcessPaste paste;
-	m_lstHeader.GetSelectionItemData( paste.GetClipIDs() );
+CClipIDs IDs;
+	m_lstHeader.GetSelectionItemData( IDs );
+	IDs.MoveTo( lID );
+	theApp.EnterGroupID( lID );
+	return TRUE;
+}
 
-	paste.DoPaste();
+BOOL CQPasteWnd::SetListID( long lID )
+{
+int index;
+	if( !m_Recset.FindFirst( StrF("lID = %d",lID) ) )
+		return FALSE;
+	index = m_Recset.GetAbsolutePosition();
+	m_lstHeader.SetListPos( index );
+	return TRUE;
+}
 
-	theApp.OnPasteCompleted();
 
+LRESULT CQPasteWnd::OnListSelect_DB_ID(WPARAM wParam, LPARAM lParam)
+{
+	OpenID( wParam );
+	return TRUE;
+}
+
+LRESULT CQPasteWnd::OnListSelect_Index(WPARAM wParam, LPARAM lParam)
+{
+	if( (int) wParam >= m_lstHeader.GetItemCount() )
+		return FALSE;
+	OpenIndex( wParam );
+	return TRUE;
+}
+
+LRESULT CQPasteWnd::OnListSelect(WPARAM wParam, LPARAM lParam)
+{
+int nCount = (int) wParam;
+long *pItems = (long*) lParam;
+	OpenSelection();
 	return TRUE;
 }
 
 LRESULT CQPasteWnd::OnListEnd(WPARAM wParam, LPARAM lParam)
 {
 	HideQPasteWindow();
-
 	return 0;
 }
 
@@ -369,7 +427,9 @@ LRESULT CQPasteWnd::OnRefreshView(WPARAM wParam, LPARAM lParam)
 	while( ::PeekMessage( &msg, m_hWnd, WM_REFRESH_VIEW, WM_REFRESH_VIEW, PM_REMOVE ) )
 	{}
 	if( theApp.m_bShowingQuickPaste )
+	{
 		FillList();
+	}
 	return TRUE;
 }
 
@@ -407,6 +467,23 @@ CString prev;
 	else
 		title += " - ";
 
+	// asterisk means we are in the default group
+	if( theApp.m_GroupID == theApp.m_GroupDefaultID )
+		title += "*";
+
+	title += theApp.m_GroupText;
+	title += " - ";
+
+	if( theApp.m_IC_IDs.GetSize() > 0 )
+	{
+		if( theApp.m_IC_bCopy )
+			title += "Copying";
+		else
+			title += "Moving";
+
+		title += " - ";
+	}
+
 	if( ::IsWindow(theApp.m_hTargetWnd) )
 		title += theApp.GetTargetName();
 	else
@@ -423,15 +500,32 @@ BOOL CQPasteWnd::FillList(CString csSQLSearch/*=""*/)
 {
 //	if(m_Recset.IsOpen())
 //		m_Recset.Close();
+CString strFilter;
 
-	// currently, we only have a History Group, so assign it directly.
-	m_lstHeader.m_bStartTop = g_Opt.m_bHistoryStartTop;
-	m_bAscending = !g_Opt.m_bHistoryStartTop;
+	// History Group
+	if( theApp.m_GroupID == 0 )
+	{
+		m_lstHeader.m_bStartTop = g_Opt.m_bHistoryStartTop;
+		if( g_Opt.m_bHistoryStartTop )
+			m_Recset.m_strSort = "lDate DESC";
+		else
+			m_Recset.m_strSort = "lDate ASC";
+	}
+	else // it's some other group
+	{
+		m_lstHeader.m_bStartTop = true;
+		m_Recset.m_strSort = "bIsGroup ASC, mText ASC";
 
-	if( m_bAscending )
-		m_Recset.m_strSort = "lDate ASC";
-	else
-		m_Recset.m_strSort = "lDate DESC";
+		if( theApp.m_GroupID > 0 )
+			strFilter.Format( "lParentID = %d", theApp.m_GroupID );
+		else // All top-level groups
+			strFilter = "bIsGroup = TRUE AND lParentID = 0";
+	}
+
+	// maintain the previous position if theApp.m_FocusID == -1
+	int nCaretPos = m_lstHeader.GetCaret();
+	if( theApp.m_FocusID == -1 && nCaretPos >= 0 )
+		theApp.m_FocusID = m_lstHeader.GetItemData( nCaretPos );
 
 	m_lstHeader.DeleteAllItems();
 
@@ -441,30 +535,35 @@ BOOL CQPasteWnd::FillList(CString csSQLSearch/*=""*/)
 	CString csSQL;
 	if(csSQLSearch == "")
 	{
-		m_Recset.m_strFilter = "";
-		if(m_Recset.IsOpen())
-			m_Recset.Requery();		
+		m_strSQLSearch = "";
 	}
 	else
 	{
 		//Replace all single ' with a double '
 		csSQLSearch.Replace("'", "''");
 
-		//Can't query of strings that have '|' in them
+		//Can't query using strings that have '|' in them
 		//this should be removed later
 		if(csSQLSearch.Find("|") >= 0)
 			return FALSE;
 
-		m_Recset.m_strFilter.Format("strText LIKE \'*%s*\'", csSQLSearch);
-		if(m_Recset.IsOpen())
-			m_Recset.Requery();
+		m_strSQLSearch.Format("mText LIKE \'*%s*\'", csSQLSearch);
+
+		if( strFilter.IsEmpty() )
+			strFilter = m_strSQLSearch;
+		else
+			strFilter += " AND " + m_strSQLSearch;
 	}
 
 	try
 	{
+		m_Recset.m_strFilter = strFilter;
+		if(m_Recset.IsOpen())
+			m_Recset.Requery();
+
 		if(m_Recset.IsOpen() == FALSE)
 			m_Recset.Open("");
-					
+
 		if(!m_Recset.IsEOF())
 		{
 			m_Recset.MoveLast();
@@ -474,22 +573,29 @@ BOOL CQPasteWnd::FillList(CString csSQLSearch/*=""*/)
 	catch(CDaoException* e)
 	{
 		AfxMessageBox(e->m_pErrorInfo->m_strDescription);
+		ASSERT(0);
 		e->Delete();
 	}
 
-	// set the caret based upon which end we're starting from
-	if( m_lstHeader.m_bStartTop )
-	{
-		m_lstHeader.SetListPos( 0 );
-	}
-	else
+	// if the caret position can't be set to the focus ID requested
+	if( theApp.m_FocusID <= 0 || !SetListID( theApp.m_FocusID ) )
 	{
-	int idx = m_lstHeader.GetItemCount() - 1;
-		// if there are elements
-		if( idx >= 0 )
-			m_lstHeader.SetListPos( idx );
+		// set the caret based upon which end we're starting from
+		if( m_lstHeader.m_bStartTop )
+		{
+			m_lstHeader.SetListPos( 0 );
+		}
+		else
+		{
+		int idx = m_lstHeader.GetItemCount() - 1;
+			// if there are elements
+			if( idx >= 0 )
+				m_lstHeader.SetListPos( idx );
+		}
 	}
 
+	theApp.m_FocusID = -1; // maintain previous position from now on.
+
 //	m_lstHeader.Invalidate();
 	RedrawWindow(0,0,RDW_INVALIDATE);
 
@@ -870,36 +976,32 @@ LRESULT CQPasteWnd::OnDelete(WPARAM wParam, LPARAM lParam)
 
 void CQPasteWnd::DeleteSelectedRows()
 {
-	ARRAY IDs;
-	long lCount = 0;
+CClipIDs IDs;
+long lCount = 0;
 
 	if( m_lstHeader.GetSelectedCount() == 0 )
 		return;
 
-	POSITION pos = m_lstHeader.GetFirstSelectedItemPosition();
-	int nFirstSel = m_lstHeader.GetNextSelectedItem( pos );
+POSITION pos = m_lstHeader.GetFirstSelectedItemPosition();
+int nFirstSel = m_lstHeader.GetNextSelectedItem( pos );
 
-	m_Recset.MoveLast();
-	lCount = m_Recset.GetRecordCount();
+	m_lstHeader.GetSelectionItemData( IDs );
+	IDs.DeleteIDs();
 
-	if( lCount == m_lstHeader.GetSelectedCount() )
-		CClip::DeleteAll();
-	else
+	try
 	{
-		m_lstHeader.GetSelectionItemData(IDs);
-		CClip::Delete(IDs);
-	}
-
-	m_Recset.Requery();
+		m_Recset.Requery();
 
-	// set lCount to current number of records
-	if( m_Recset.IsBOF() && m_Recset.IsEOF() )
-		lCount = 0;
-	else
-	{
-		m_Recset.MoveLast();
-		lCount = m_Recset.GetRecordCount();
+		// set lCount to current number of records
+		if( m_Recset.IsBOF() && m_Recset.IsEOF() )
+			lCount = 0;
+		else
+		{
+			m_Recset.MoveLast();
+			lCount = m_Recset.GetRecordCount();
+		}
 	}
+	CATCHDAO
 
 	m_lstHeader.SetItemCountEx(lCount);
 	if(lCount == 0)
@@ -961,6 +1063,17 @@ BOOL CQPasteWnd::PreTranslateMessage(MSG* pMsg)
 
 		switch( pMsg->wParam )
 		{
+		case VK_F7:
+			if(GetKeyState(VK_CONTROL) & 0x8000)
+				NewGroup( true );
+			else
+				NewGroup( false );
+			break;
+
+		case VK_BACK:
+			theApp.EnterGroupID( theApp.m_GroupParentID );
+			break;
+
 		case VK_SPACE:
 			if(GetKeyState(VK_CONTROL) & 0x8000)
 				theApp.ShowPersistent( !g_Opt.m_bShowPersistent );
@@ -1007,17 +1120,30 @@ BOOL CQPasteWnd::PreTranslateMessage(MSG* pMsg)
 
 		break; // end case WM_KEYDOWN 
 
-	case WM_SYSKEYDOWN:
-		if(pMsg->wParam == 'C')
+	case WM_SYSKEYDOWN: // ALT key is held down
+
+		switch( pMsg->wParam )
 		{
+		case 'C': // switch to the filter combobox
 			BYTE key[256];
 			GetKeyboardState((LPBYTE)(&key));
 			if(key[VK_MENU]&128)
 			{
-				OnCancelFilter();				
+				OnCancelFilter();
 			}
-		}
-		break;
+			break;
+
+		case VK_HOME:
+			theApp.EnterGroupID( 0 );  // History
+			break;
+
+		case VK_END:
+			theApp.EnterGroupID( -1 ); // All Groups
+			break; 
+
+		} // end switch( pMsg->wParam )
+
+		break; // end case WM_SYSKEYDOWN
 	}
 
 	return CWndEx::PreTranslateMessage(pMsg);
@@ -1054,7 +1180,7 @@ CClipIDs& clips = paste.GetClipIDs();
 	m_lstHeader.GetSelectionItemData( clips );
 	if( clips.GetSize() <= 0 )
 	{
-		ASSERT(0); // does this ever happen ?????
+		ASSERT(0); // does this ever happen ??
 		clips.Add( m_lstHeader.GetItemData(pLV->iItem) );
 	}
 	paste.DoDrag();
@@ -1062,7 +1188,7 @@ CClipIDs& clips = paste.GetClipIDs();
 }
 
 void CQPasteWnd::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
-{	
+{
 	CWndEx::OnSysKeyDown(nChar, nRepCnt, nFlags);
 }
 
@@ -1085,13 +1211,21 @@ void CQPasteWnd::GetDispInfo(NMHDR* pNMHDR, LRESULT* pResult)
 
 				CString cs;
 				if(m_Recset.m_lDontAutoDelete)
-					cs = "* ";
+					cs += "*";
 
 				if(m_Recset.m_lShortCut > 0)
-					cs += "** ";
+					cs += "s";
+                
+				if(m_Recset.m_bIsGroup)
+					cs += "G";
+
+				// attached to a group
+				if(m_Recset.m_lParentID > 0 )
+					cs += "!";
+
+				// pipe is the "end of symbols" marker
+				cs += "|" + m_Recset.GetDisplayText();
 
-				cs += m_Recset.m_strText;
-				
 				lstrcpyn(pItem->pszText, cs, pItem->cchTextMax);
 				pItem->pszText[pItem->cchTextMax-1] = '\0';
 			}
@@ -1281,5 +1415,18 @@ void CQPasteWnd::OnWindowPosChanging(WINDOWPOS* lpwndpos)
 
 void CQPasteWnd::OnSelectionChange(NMHDR* pNMHDR, LRESULT* pResult)
 {
-	theApp.SetStatus(NULL, TRUE);
-}
+	// avoid temporary 0 flicker when moving cursor
+	// the focus is always implicitly selected.
+	if( m_lstHeader.GetSelectedCount() > 0 )
+		theApp.SetStatus(NULL, TRUE);
+}
+
+void CQPasteWnd::OnMenuNewGroup()
+{
+	NewGroup( false );
+}
+
+void CQPasteWnd::OnMenuNewGroupSelection()
+{
+	NewGroup( true );
+}

+ 10 - 1
QPasteWnd.h

@@ -58,7 +58,7 @@ public:
 	CButton			m_btCancel;
 	bool			m_bHideWnd;
 	CMainTable		m_Recset;
-	bool			m_bAscending;
+	CString			m_strSQLSearch;
 
 	CString			m_Title;
 
@@ -71,6 +71,13 @@ public:
 
 	void DeleteSelectedRows();
 
+	BOOL OpenID( long lID );
+	BOOL OpenSelection();
+	BOOL OpenIndex( long nItem );
+	BOOL NewGroup( bool bGroupSelection = true );
+	// moves the caret to the given ID, selects it, and ensures it is visible.
+	BOOL SetListID( long lID );
+
 	CString LoadDescription( int nItem );
 	bool SaveDescription( int nItem, CString text );
 
@@ -142,6 +149,8 @@ public:
 	afx_msg void OnMenuAllwaysontop();
 	afx_msg void OnSortAscending();
 	afx_msg void OnSortDescending();
+	afx_msg void OnMenuNewGroup();
+	afx_msg void OnMenuNewGroupSelection();
 };
 
 

+ 7 - 3
Resource.h

@@ -1,5 +1,5 @@
 //{{NO_DEPENDENCIES}}
-// Microsoft Developer Studio generated include file.
+// Microsoft Visual C++ generated include file.
 // Used by CP_Main.rc
 //
 #define IDD_ABOUTBOX                    100
@@ -80,6 +80,7 @@
 #define IDC_AT_PREVIOUS                 2017
 #define IDC_COMPACT_DB                  2018
 #define IDC_REPAIR                      2019
+#define IDC_DESC_SHOW_LEADING_WHITESPACE 2021
 #define ID_FIRST_OPTION                 32771
 #define ID_FIRST_EXIT                   32772
 #define ID_FIRST_SHOWQUICKPASTE         32773
@@ -117,6 +118,9 @@
 #define ID_MENU_EXITPROGRAM             32806
 #define ID_MENU_LINESPERROW_1           32807
 #define ID_MENU_TRANSPARENCY_5          32808
+#define ID_MENU_QUICKOPTIONS            32809
+#define ID_MENU_NEWGROUP                32811
+#define ID_MENU_NEWGROUPSELECTION       32812
 
 // Next default values for new objects
 // 
@@ -124,8 +128,8 @@
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_3D_CONTROLS                     1
 #define _APS_NEXT_RESOURCE_VALUE        138
-#define _APS_NEXT_COMMAND_VALUE         32809
-#define _APS_NEXT_CONTROL_VALUE         2020
+#define _APS_NEXT_COMMAND_VALUE         32813
+#define _APS_NEXT_CONTROL_VALUE         2022
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif