@@ -51,14 +51,15 @@ Private Const PD_CB_ALLOW_PNG_PASTE As Boolean = True
5151Public Enum PD_ClipboardFormats
5252 pdcf_All = 0
5353 pdcf_InternalPD = CF_PD_DIB
54+ pdcf_InternalLayer = CF_PD_LAYER
5455 pdcf_Bitmap = CF_BITMAP
5556 pdcf_Dib = CF_DIB
5657 pdcf_DibV5 = CF_DIBV5
5758 pdcf_PNG = -1
5859End Enum
5960
6061#If False Then
61- Private Const pdcf_All = 0 , pdcf_InternalPD = CF_PD_DIB, pdcf_Bitmap = CF_BITMAP, pdcf_Dib = CF_DIB, pdcf_DibV5 = CF_DIBV5, pdcf_PNG = -1
62+ Private Const pdcf_All = 0 , pdcf_InternalPD = CF_PD_DIB, pdcf_InternalLayer = CF_PD_LAYER, pdcf_Bitmap = CF_BITMAP, pdcf_Dib = CF_DIB, pdcf_DibV5 = CF_DIBV5, pdcf_PNG = -1
6263#End If
6364
6465'Some format constants are not inherently defined by VB; we cache these at load-time
@@ -159,8 +160,12 @@ Private m_ClipboardInfo As PD_Clipboard_Info
159160'Lots of clipboard actions require a temporary DIB
160161Private m_ClipboardDIB As pdDIB
161162
162- 'When stashing clipboard data, we use a temp file.
163- Private m_StashFile As String
163+ 'Stashing layer data will generate a layer header and vector stash as well
164+ Private m_StashedLayer As String , m_StashedVector As String
165+
166+ 'When stashing clipboard data, we use a temp file. Note that certain internal types of copy+paste (e.g. copy+pasting
167+ ' a vector layer) may store multiple types of stash data.
168+ Private m_StashFile As String , m_StashFileLayer As String , m_StashFileVector As String
164169
165170'Clipboard interaction object; importantly, if delayed rendering is active, this class *will* raise events that must be handled.
166171Private WithEvents m_Clipboard As pdClipboard
@@ -327,7 +332,14 @@ Friend Sub ClipboardCopy(ByVal copyMerged As Boolean, Optional ByVal updateUI As
327332 'By default, we offer all supported formats
328333 If (cFormat = pdcf_All) Then
329334
335+ 'Internal pixel data is always available
330336 m_Clipboard.SetClipboardData_DelayedRendering CF_PD_DIB
337+
338+ 'If the copy is of a whole layer (not a selection or merged image), we'll also save out a full
339+ ' layer header and any relevant vector data so that we can re-create the whole layer as-is.
340+ If (Not copyMerged) And (Not PDImages.GetActiveImage.IsSelectionActive) Then m_Clipboard.SetClipboardData_DelayedRendering CF_PD_LAYER
341+
342+ 'Bitmap formats are for other apps
331343 m_Clipboard.SetClipboardData_DelayedRendering m_Clipboard.AddClipboardFormat("PNG" )
332344 m_Clipboard.SetClipboardData_DelayedRendering CF_BITMAP
333345 m_Clipboard.SetClipboardData_DelayedRendering CF_DIBV5
@@ -363,6 +375,8 @@ Friend Sub ClipboardCopy(ByVal copyMerged As Boolean, Optional ByVal updateUI As
363375 m_Clipboard.SetClipboardData_DelayedRendering CF_DIBV5
364376 ElseIf (cFormat = pdcf_InternalPD) Then
365377 m_Clipboard.SetClipboardData_DelayedRendering CF_PD_DIB
378+ ElseIf (cFormat = pdcf_InternalLayer) Then
379+ m_Clipboard.SetClipboardData_DelayedRendering CF_PD_LAYER
366380 ElseIf (cFormat = pdcf_PNG) Then
367381 m_Clipboard.SetClipboardData_DelayedRendering m_Clipboard.AddClipboardFormat("PNG" )
368382 Else
@@ -430,7 +444,7 @@ Friend Function ClipboardPaste(ByVal srcIsMeantAsLayer As Boolean, Optional ByRe
430444 PDDebug.LogAction "Clipboard reports the following formats: " & m_Clipboard.GetListOfAvailableFormatNames()
431445
432446 'If PD was used to Cut or Copy something onto the clipboard, our own private format(s) will be listed first.
433- If m_Clipboard.DoesClipboardHaveFormatID(CF_PD_DIB) And PD_CB_ALLOW_INTERNAL_FORMAT_PASTE Then
447+ If ( m_Clipboard.DoesClipboardHaveFormatID(CF_PD_DIB) Or m_Clipboard.DoesClipboardHaveFormatID(CF_PD_LAYER) ) And PD_CB_ALLOW_INTERNAL_FORMAT_PASTE Then
434448 pasteWasSuccessful = ClipboardPaste_InternalData(srcIsMeantAsLayer, pasteToThisDIBInstead)
435449 End If
436450
@@ -574,9 +588,10 @@ Friend Function ClipboardPaste(ByVal srcIsMeantAsLayer As Boolean, Optional ByRe
574588
575589End Function
576590
577- 'If the clipboard contains internal PD-format data (most commonly a bare DIB), you can call this function to initiate a "paste" command
578- ' using the internal data as a source. The parameter "srcIsMeantAsLayer" controls whether the clipboard data is loaded as a new image,
579- ' or as a new layer in an existing image.
591+ 'If the clipboard contains internal PD-format data (most commonly a bare DIB, but also potentially a
592+ ' layer header and/or layer vector contents), you can call this function to initiate a "paste" command
593+ ' using the internal data as a source. The parameter "srcIsMeantAsLayer" controls whether the clipboard
594+ ' data is loaded as a new image, or as a new layer in an existing image.
580595'
581596'RETURNS: TRUE if successful; FALSE otherwise.
582597Private Function ClipboardPaste_InternalData (ByVal srcIsMeantAsLayer As Boolean , Optional ByRef pasteToThisDIBInstead As pdDIB = Nothing ) As Boolean
@@ -600,8 +615,92 @@ Private Function ClipboardPaste_InternalData(ByVal srcIsMeantAsLayer As Boolean,
600615 ClipboardPaste_InternalData = Loading.QuickLoadImageToDIB(m_StashFile, pasteToThisDIBInstead, False , False )
601616 Else
602617 If srcIsMeantAsLayer Then
603- m_LayerCounter = m_LayerCounter + 1
604- ClipboardPaste_InternalData = Layers.LoadImageAsNewLayer(False , m_StashFile, g_Language.TranslateMessage("Clipboard Image" ) & " " & CStr(m_LayerCounter), False , False )
618+
619+ 'If a layer header was saved out to file, try to use it as the paste source
620+ ' (instead of creating a generic new layer object - this will result in a copy+paste operation
621+ ' that preserves attributes like layer blend mode, position, rotation, text/vector contents, etc)
622+ Dim useStashedLayerHeader As Boolean
623+ useStashedLayerHeader = (LenB(m_StashFileLayer) <> 0 )
624+ If useStashedLayerHeader Then useStashedLayerHeader = Files.FileExists(m_StashFileLayer)
625+ If useStashedLayerHeader Then useStashedLayerHeader = Files.FileLoadAsString(m_StashFileLayer, m_StashedLayer)
626+
627+ 'Only attempt to use a stashed layer header if all previous validation steps were successful.
628+ If useStashedLayerHeader Then
629+
630+ PDDebug.LogAction "Pasting internal layer source..."
631+
632+ 'Ask the parent pdImage to create a blank, new layer object
633+ Dim newLayerID As Long
634+ newLayerID = PDImages.GetActiveImage.CreateBlankLayer()
635+
636+ 'See if we stashed raster or vector data. (This affects how we initialize the layer's contents -
637+ ' from pixel buffer or manually, by reading text-based vector data and generating objects accordingly.)
638+ Dim useVectorData As Boolean
639+ useVectorData = False
640+
641+ Dim cSerialize As pdSerialize
642+ Set cSerialize = New pdSerialize
643+ cSerialize.SetParamString m_StashedLayer
644+
645+ Dim srcLayerType As PD_LayerType
646+ srcLayerType = Layers.GetLayerTypeIDFromString(cSerialize.GetString("type" , Layers.GetLayerTypeStringFromID(PDL_Image), True ))
647+
648+ If (srcLayerType <> PDL_Image) Then
649+ useVectorData = Files.FileExists(m_StashFileVector)
650+ If useVectorData Then useVectorData = Files.FileLoadAsString(m_StashFileVector, m_StashedVector)
651+ End If
652+
653+ 'useVectorData will now be TRUE iff the copied layer was a text/vector layer,
654+ ' *and* we stashed vector data successfully.
655+
656+ 'Create the new layer using the appropriate layer type
657+ PDImages.GetActiveImage.GetLayerByID(newLayerID).InitializeNewLayer srcLayerType
658+
659+ 'Initialize the layer using the cached layer header. (This preserves all editable layer attributes.)
660+ PDImages.GetActiveImage.GetLayerByID(newLayerID).CreateNewLayerFromXML m_StashedLayer, newLayerID, True
661+
662+ 'If this is a vector layer, initialize it from text-based vector data
663+ If useVectorData Then useVectorData = PDImages.GetActiveImage.GetLayerByID(newLayerID).SetVectorDataFromXML(m_StashedVector)
664+
665+ 'If the vector creation was successful, no further work is required.
666+ ' (If vector creation was *not* successful - which is unexpected and shouldn't happen -
667+ ' fall back to the cached raster data instead.)
668+ If useVectorData Then
669+ PDDebug.LogAction "Pasting used vector data."
670+ ClipboardPaste_InternalData = True
671+ Else
672+
673+ PDDebug.LogAction "Paste will use raster data..."
674+
675+ 'Load the stashed pixel data into a standalone DIB
676+ Dim tmpDIB As pdDIB
677+ Set tmpDIB = New pdDIB
678+ ClipboardPaste_InternalData = Loading.QuickLoadImageToDIB(m_StashFile, tmpDIB, False , False )
679+ If ClipboardPaste_InternalData Then
680+
681+ 'Forcibly convert the new layer to 32bpp
682+ ' (failsafe only; it should already be in 32-bpp mode from the loader)
683+ If (tmpDIB.GetDIBColorDepth <> 32 ) Then tmpDIB.ConvertTo32bpp
684+
685+ 'Replace the new layer's backing surface with the temporary DIB
686+ PDImages.GetActiveImage.GetLayerByID(newLayerID).SetLayerDIB tmpDIB
687+
688+ Else
689+ PDDebug.LogAction "WARNING! ClipboardPaste_InternalData failed to load the stash file. Paste abandoned."
690+ End If
691+
692+ End If
693+
694+ 'Notify the parent image of these changes, as it needs to generate a new composite image
695+ PDImages.GetActiveImage.NotifyImageChanged UNDO_Image_VectorSafe
696+
697+ 'If a layer header *wasn't* cached, we are probably copying from a non-layer source (like a selection).
698+ ' Simply load the cached raster data as a new, standalone layer.
699+ Else
700+ m_LayerCounter = m_LayerCounter + 1
701+ ClipboardPaste_InternalData = Layers.LoadImageAsNewLayer(False , m_StashFile, g_Language.TranslateMessage("Clipboard Image" ) & " " & CStr(m_LayerCounter), False , False )
702+ End If
703+
605704 Else
606705 ClipboardPaste_InternalData = Loading.LoadFileAsNewImage(m_StashFile, sTitle, False )
607706 End If
@@ -1634,13 +1733,14 @@ DragDropTextFailed:
16341733
16351734End Function
16361735
1637- 'When a Copy or Paste event is initiated, PD doesn't actually copy anything to the clipboard. Instead, it "stashes" a copy of the
1638- ' generic, core image data that would be translated to some clipboard format (e.g. a layer or standalone DIB or something). When some
1639- ' other program (or PD itself) requests that data via Paste, we then retrieve the "stashed" data and render it into the requested
1640- ' format. This provides as ton of benefits, including better Copy performance (as we only render data in a singular format if/when it's
1641- ' actually needed), reduced memory (we don't flood the clipboard with a given format until it's actually required), and cleaner code.
1736+ 'When a Copy or Paste event is initiated, PD doesn't actually copy anything to the clipboard.
1737+ ' Instead, it "stashes" a copy of the generic, core image data that would be translated to some clipboard format
1738+ ' (e.g. a layer or standalone DIB or something). When some other program (or PD itself) requests that data via Paste,
1739+ ' we then retrieve the "stashed" data and render it into the requested format. This provides as ton of benefits,
1740+ ' including better Copy performance (as we only render data in a singular format if/when it's actually needed),
1741+ ' reduced memory (we don't flood the clipboard with a given format until it's actually required), and cleaner code.
16421742'
1643- 'FYI: stashed data uses premultiplied alpha, by design.
1743+ 'FYI: stashed pixel data uses premultiplied alpha, by design.
16441744Private Sub StashClipboardData (ByVal copyMerged As Boolean )
16451745
16461746 'The specific data we stash varies according to a few different parameters.
@@ -1671,11 +1771,13 @@ Private Sub StashClipboardData(ByVal copyMerged As Boolean)
16711771
16721772 End If
16731773
1674- 'If we already have a stash file, remove it
1774+ 'Remove any previously stashed file(s). (This can happen if the user copies, but doesn't paste.)
16751775 If (LenB(m_StashFile) <> 0 ) Then Files.FileDeleteIfExists m_StashFile
1776+ If (LenB(m_StashFileLayer) <> 0 ) Then Files.FileDeleteIfExists m_StashFileLayer
1777+ If (LenB(m_StashFileVector) <> 0 ) Then Files.FileDeleteIfExists m_StashFileVector
16761778
1677- 'Grab a new temporary path name (TODO: use system-generated temp file names)
1678- If (LenB(m_StashFile) = 0 ) Then m_StashFile = UserPrefs.GetTempPath() & "cbStash. tmpdib"
1779+ 'Grab a new temporary filename for pixel data
1780+ If (LenB(m_StashFile) = 0 ) Then m_StashFile = OS.UniqueTempFilename(customExtension:= " tmpdib")
16791781
16801782 PDDebug.LogAction "Writing clipboard stash now..."
16811783 If (Not m_ClipboardDIB Is Nothing ) Then
@@ -1686,6 +1788,28 @@ Private Sub StashClipboardData(ByVal copyMerged As Boolean)
16861788 'Erase the temporary DIB
16871789 m_ClipboardDIB.EraseDIB
16881790
1791+ 'Erase any stored layer and vector data
1792+ m_StashedLayer = vbNullString
1793+ m_StashedVector = vbNullString
1794+
1795+ 'If the cut/copy action is an entire source layer, also cache the layer header (and vector data if relevant).
1796+ If (Not PDImages.GetActiveImage.IsSelectionActive) And (Not copyMerged) Then
1797+ 'Private m_StashedLayer As String, m_StashedVector As String
1798+
1799+ If (LenB(m_StashFileLayer) = 0 ) Then m_StashFileLayer = OS.UniqueTempFilename(customExtension:="txt" )
1800+ Files.FileSaveAsText PDImages.GetActiveImage.GetActiveLayer.GetLayerHeaderAsXML(), m_StashFileLayer
1801+
1802+ If PDImages.GetActiveImage.GetActiveLayer.IsLayerVector() Then
1803+ If (LenB(m_StashFileVector) = 0 ) Then m_StashFileVector = OS.UniqueTempFilename(customExtension:="txt" )
1804+ Files.FileSaveAsText PDImages.GetActiveImage.GetActiveLayer.GetVectorDataAsXML(), m_StashFileVector
1805+ End If
1806+
1807+ 'If we're *not* saving layer data, blank out those filenames so we know not to use them later
1808+ Else
1809+ m_StashFileLayer = vbNullString
1810+ m_StashFileVector = vbNullString
1811+ End If
1812+
16891813 PDDebug.LogAction "Clipboard stashed successfully."
16901814
16911815 Else
@@ -1694,7 +1818,10 @@ Private Sub StashClipboardData(ByVal copyMerged As Boolean)
16941818
16951819End Sub
16961820
1697- 'Retrieve previously stashed data. If this function returns TRUE, the stashed data has been successfully loaded into m_ClipboardDIB.
1821+ 'Retrieve previously stashed data. If this function returns TRUE:
1822+ ' 1) stashed pixel data has been successfully loaded into m_ClipboardDIB (always guaranteed)
1823+ ' 2) optionally, stashed layer header (if any) has been successfully loaded into m_stashedLayer
1824+ ' 3) optionally, stashed vector data (if any) has been successfully loaded into m_stashedVector
16981825Private Function UnstashClipboardData () As Boolean
16991826
17001827 UnstashClipboardData = False
@@ -1711,22 +1838,28 @@ Private Function UnstashClipboardData() As Boolean
17111838
17121839 End If
17131840
1841+ If Files.FileExists(m_StashFileLayer) Then Files.FileLoadAsString m_StashFileLayer, m_StashedLayer
1842+ If Files.FileExists(m_StashFileVector) Then Files.FileLoadAsString m_StashFileVector, m_StashedVector
1843+
17141844 End If
17151845
17161846End Function
17171847
1718- 'If you know it's safe to destroy PD's clipboard cache, you can do so via this function. Note that this action is not reversible,
1719- ' so make sure it's what you want/need to do.
1848+ 'If you know it's safe to destroy PD's clipboard cache, you can do so via this function.
1849+ ' Note that this action is *not* reversible, so make sure it's what you want/need to do.
17201850Private Sub DestroyStashedData ()
17211851 If (LenB(m_StashFile) <> 0 ) Then Files.FileDeleteIfExists m_StashFile
1852+ If (LenB(m_StashFileLayer) <> 0 ) Then Files.FileDeleteIfExists m_StashFileLayer
1853+ If (LenB(m_StashFileVector) <> 0 ) Then Files.FileDeleteIfExists m_StashFileVector
17221854End Sub
17231855
17241856Friend Function IsPDDataOnClipboard () As Boolean
17251857 IsPDDataOnClipboard = m_Clipboard.IsOurDataOnTheClipboard()
17261858End Function
17271859
17281860'Want to render PD's current clipboard stash to the clipboard, WITHOUT delayed rendering? Call this function.
1729- ' (PD does this prior to shutdown, because WM_RENDERALLFORMATS does not play nicely with the order VB unloads objects / destroys windows.)
1861+ ' (PD does this prior to shutdown, because WM_RENDERALLFORMATS does not play nicely with the order
1862+ ' VB unloads objects / destroys windows.)
17301863Friend Sub RenderAllClipboardFormatsManually ()
17311864
17321865 PDDebug.LogAction "pdClipboardMain received notification to render all clipboard formats. Rendering now..."
@@ -2136,5 +2269,7 @@ Private Sub Class_Terminate()
21362269
21372270 'Perform a failsafe check against un-destroyed stash data
21382271 If (LenB(m_StashFile) <> 0 ) Then Files.FileDeleteIfExists m_StashFile
2272+ If (LenB(m_StashFileLayer) <> 0 ) Then Files.FileDeleteIfExists m_StashFileLayer
2273+ If (LenB(m_StashFileVector) <> 0 ) Then Files.FileDeleteIfExists m_StashFileVector
21392274
21402275End Sub
0 commit comments