[Win32utils-devel] Multiple clipboard formats

Heesob Park phasis at gmail.com
Mon Apr 27 02:22:11 EDT 2009


Hi,

2009/4/26 Daniel Berger <djberg96 at gmail.com>:
> Hi,
>
> I already made a change since 0.5.0. The Clipboard.register_format now
> returns the format number returned by the RegisterClipboardFormat()
> function. This change is in CVS.
>
> It works fine, but one thing I wasn't sure of was how to register multiple
> clipboard formats simultaneously. Specifically, I was looking at this
> article:
>
> http://www.codeguru.com/cpp/w-p/clipboard/print.php/c3015
>
> Where he registers both RTF and Csv formats, and when he copies and pastes
> to Word or Excel, it just does the right thing.
>
Here is both CF_HTML and CF_TEXT format handling sample code:

require 'win32/clipboard'
include Win32

module Win32
	class HtmlClipboard < Clipboard
		
		MARKER_BLOCK_OUTPUT =
			"Version:1.0\r\n" \
			"StartHTML:%09d\r\n" \
			"EndHTML:%09d\r\n" \
			"StartFragment:%09d\r\n" \
			"EndFragment:%09d\r\n" \
			"StartSelection:%09d\r\n" \
			"EndSelection:%09d\r\n" \
			"SourceURL:%s\r\n"

		MARKER_BLOCK_EX =
			'Version:(\S+)\s+' \
			'StartHTML:(\d+)\s+' \
			'EndHTML:(\d+)\s+' \
			'StartFragment:(\d+)\s+' \
			'EndFragment:(\d+)\s+' \
			'StartSelection:(\d+)\s+' \
			'EndSelection:(\d+)\s+' \
			'SourceURL:(\S+)'
		MARKER_BLOCK_EX_RE = Regexp.new(MARKER_BLOCK_EX,Regexp::MULTILINE)

		MARKER_BLOCK =
			'Version:(\S+)\s+' \
			'StartHTML:(\d+)\s+' \
			'EndHTML:(\d+)\s+' \
			'StartFragment:(\d+)\s+' \
			'EndFragment:(\d+)\s+' \
			'SourceURL:(\S+)'
		MARKER_BLOCK_RE = Regexp.new(MARKER_BLOCK,Regexp::MULTILINE)

		DEFAULT_HTML_BODY =
			"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\r\n" \
			"<HTML><BODY><!--StartFragment-->%s<!--EndFragment--></BODY></HTML>"		

		CF_HTML = RegisterClipboardFormat("HTML Format")
		
		def initialize
			@html = nil
			@fragment = nil
			@selection = nil
			@source = nil
			@version = nil
		end
	
		def self.has_html_format?
			format_available?(CF_HTML)
		end
		
		def self.data
			begin
				self.open
				if IsClipboardFormatAvailable(CF_HTML)
					handle = GetClipboardData(CF_HTML)
                    clip_data = 0.chr * GlobalSize(handle)
                    memcpy(clip_data, handle, clip_data.size)
                    clip_data.strip!
					clip_data = decode_data(clip_data)
				else
					clip_data = ''
				end
			ensure
				self.close
			end			
			clip_data
		end
		
		def self.decode_data(src)
			if (matches = MARKER_BLOCK_EX_RE.match(src))			
				@prefix = matches[0]
				@version = matches[1]
				@html = src[matches[2].to_i ... matches[3].to_i]
				@fragment = src[matches[4].to_i ... matches[5].to_i]
				@selection = src[matches[6].to_i ... matches[7].to_i]
				@source = matches[8]
			elsif (matches = MARKER_BLOCK_RE.match(src))			
				@prefix = matches[0]
				@version = matches[1]
				@html = src[matches[2].to_i ... matches[3].to_i]
				@fragment = src[matches[4].to_i ... matches[5].to_i]
				@source = matches[6]			
				@selection = @fragment
			end
			@fragment
		end
		
		def self.dump_data
			clip_data = data
			puts "prefix=>>>#{@prefix}<<<END"
			puts "version=>>>#{@version}<<<END"
			puts "selection=>>>#{@selection}<<<END"
			puts "fragment=>>>#{@fragment}<<<END"
			puts "html=>>>#{@html}<<<END"
			puts "source=>>>#{@source}<<<END"
		end
		
		def self.encode_data(html,fragment_start,fragment_end,selection_start,selection_end,source)
			dummy_prefix = MARKER_BLOCK_OUTPUT % [0,0,0,0,0,0,source]
			len_prefix = dummy_prefix.length
			prefix = MARKER_BLOCK_OUTPUT % [len_prefix, html.length + len_prefix,
				fragment_start+len_prefix,fragment_end+len_prefix,
				selection_start+len_prefix,selection_end+len_prefix,
				source]
			prefix + html
		end
		
		def self.set_data(fragment,selection=nil,html=nil,source=nil)
			selection ||= fragment
			html ||= DEFAULT_HTML_BODY % fragment
			source ||= 'file://'+__FILE__
			fragment_start = html.index(fragment)
			fragment_end = fragment_start + fragment.length
			selection_start = html.index(selection)
			selection_end = selection_start + selection.length

			clip_data = encode_data(html,fragment_start,fragment_end,selection_start,selection_end,source)
			self.open
			EmptyClipboard()

			# Global Allocate a movable piece of memory.
			hmem = GlobalAlloc(GHND, clip_data.length + 4)
			mem  = GlobalLock(hmem)
			memcpy(mem, clip_data, clip_data.length)

			clip_data2 = fragment.gsub(/<[^>]+?>/,'')
			hmem2 = GlobalAlloc(GHND, clip_data2.length + 4)
			mem2  = GlobalLock(hmem2)
			memcpy(mem2, clip_data2, clip_data2.length)

			# Set the new data
			begin
				if SetClipboardData(CF_HTML, hmem) == 0
					raise Error, "SetClipboardData() failed: " + get_last_error
				end
				if SetClipboardData(CF_TEXT, hmem2) == 0
					raise Error, "SetClipboardData() failed: " + get_last_error
				end
			ensure
				GlobalFree(hmem)
				GlobalFree(hmem2)
				self.close
			end
			self			
		end
		
	end
end

if $0 == __FILE__
	data = "<p>Writing to the clipboard is <strong>easy</strong> with
this code.</p>"
	HtmlClipboard.set_data(data)
	if HtmlClipboard.data == data
		puts "passed"
	else
		puts "failed"
	end
end

> I'm also wondering if we need to modify the Clipboard.set_data method so
> that it accepts a nil argument. According to MSD, the 'hMem' parameter can
> be NULL, indicating that the window provides data in the specified clipboard
> format (renders the format) upon request. If a window delays rendering, it
> must process the WM_RENDERFORMAT and WM_RENDERALLFORMATS messages.
>
> I'm debating what sort of interface we could provide for this. Any
> suggestions?
>
I think, in real world applications, delayed rendering is unreliable
and useless feature.
Can you guarantee the clipboard owner is always active?

Regards,

Park Heesob


More information about the win32utils-devel mailing list