1+ using System ;
2+ using System . Collections . Generic ;
3+ using System . Drawing ;
4+ using System . Drawing . Imaging ;
5+ using System . IO ;
6+ using System . Linq ;
7+ using System . Text ;
8+ using System . Text . RegularExpressions ;
9+ using System . Windows . Forms ;
10+ using PasteIntoFile . Properties ;
11+
12+ namespace PasteIntoFile {
13+
14+ /// <summary>
15+ /// This is the base class to hold clipboard contents, metadata, and perform actions with it
16+ /// </summary>
17+ public abstract class BaseContent {
18+
19+ /// <summary>
20+ /// List of known file extensions for this content type
21+ /// The first extension is considered the default
22+ /// </summary>
23+ public abstract string [ ] Extensions { get ; }
24+
25+ /// <summary>
26+ /// A friendly description of the contents
27+ /// </summary>
28+ public abstract string Description { get ; }
29+
30+ /// <summary>
31+ /// The actual data content
32+ /// </summary>
33+ protected object Data ;
34+
35+ /// <summary>
36+ /// The default extension for this content type
37+ /// </summary>
38+ public string DefaultExtension {
39+ get {
40+ var ext = Extensions ;
41+ return ext . Length > 0 ? ext . First ( ) : "" ;
42+ }
43+ }
44+
45+ /// <summary>
46+ /// Saves the content to a file
47+ /// </summary>
48+ /// <param name="path">Full path where to save (incl. filename and extension)</param>
49+ /// <param name="extension">format to use for saving</param>
50+ public abstract void SaveAs ( string path , string extension ) ;
51+
52+ }
53+
54+
55+ public class ImageContent : BaseContent {
56+ public ImageContent ( Image image ) {
57+ Data = image ;
58+ }
59+ public Image Image => Data as Image ;
60+ public override string [ ] Extensions => new [ ] { "png" , "bpm" , "emf" , "gif" , "ico" , "jpg" , "tif" , "wmf" } ;
61+ public override string Description => string . Format ( Resources . str_preview_image , Image . Width , Image . Height ) ;
62+ public override void SaveAs ( string path , string extension ) {
63+ ImageFormat imageFormat ;
64+ switch ( extension ) {
65+ case "bpm" : imageFormat = ImageFormat . Bmp ; break ;
66+ case "emf" : imageFormat = ImageFormat . Emf ; break ;
67+ case "gif" : imageFormat = ImageFormat . Gif ; break ;
68+ case "ico" : imageFormat = ImageFormat . Icon ; break ;
69+ case "jpg" : imageFormat = ImageFormat . Jpeg ; break ;
70+ case "tif" : imageFormat = ImageFormat . Tiff ; break ;
71+ case "wmf" : imageFormat = ImageFormat . Wmf ; break ;
72+ default : imageFormat = ImageFormat . Png ; break ;
73+ }
74+ Image . Save ( path , imageFormat ) ;
75+ }
76+ }
77+
78+
79+ public abstract class TextLikeContent : BaseContent {
80+ public TextLikeContent ( string text ) {
81+ Data = text ;
82+ }
83+ public string Text => Data as string ;
84+ }
85+
86+
87+ public class TextContent : TextLikeContent {
88+ public TextContent ( string text ) : base ( text ) { }
89+ public override string [ ] Extensions => new [ ] { "txt" , "md" , "log" , "bat" , "ps1" , "java" , "js" , "cpp" , "cs" , "py" , "css" , "html" , "php" , "json" , "csv" } ;
90+ public override string Description => string . Format ( Resources . str_preview_text , Text . Length , Text . Split ( '\n ' ) . Length ) ;
91+ public override void SaveAs ( string path , string extension ) {
92+ File . WriteAllText ( path , Text , Encoding . UTF8 ) ;
93+ }
94+ }
95+
96+
97+ public class HtmlContent : TextLikeContent {
98+ public HtmlContent ( string text ) : base ( text ) { }
99+ public override string [ ] Extensions => new [ ] { "html" , "htm" , "xhtml" } ;
100+ public override string Description => Resources . str_preview_html ;
101+ public override void SaveAs ( string path , string extension ) {
102+ var html = Text ;
103+ if ( ! html . StartsWith ( "<!DOCTYPE html>" ) )
104+ html = "<!DOCTYPE html>\n " + html ;
105+ File . WriteAllText ( path , html , Encoding . UTF8 ) ;
106+ }
107+ }
108+
109+
110+ public class CsvContent : TextLikeContent {
111+ public CsvContent ( string text ) : base ( text ) { }
112+ public override string [ ] Extensions => new [ ] { "csv" , "tsv" , "tab" } ;
113+ public override string Description => Resources . str_preview_csv ;
114+ public override void SaveAs ( string path , string extension ) {
115+ File . WriteAllText ( path , Text , Encoding . UTF8 ) ;
116+ }
117+ }
118+
119+
120+ public class SylkContent : TextLikeContent {
121+ public SylkContent ( string text ) : base ( text ) { }
122+ public override string [ ] Extensions => new [ ] { "slk" } ;
123+ public override string Description => Resources . str_preview_sylk ;
124+ public override void SaveAs ( string path , string extension ) {
125+ File . WriteAllText ( path , Text , Encoding . ASCII ) ;
126+ }
127+ }
128+
129+
130+ public class DifContent : TextLikeContent {
131+ public DifContent ( string text ) : base ( text ) { }
132+ public override string [ ] Extensions => new [ ] { "dif" } ;
133+ public override string Description => Resources . str_preview_dif ;
134+ public override void SaveAs ( string path , string extension ) {
135+ File . WriteAllText ( path , Text , Encoding . ASCII ) ;
136+ }
137+ }
138+
139+
140+ public class RtfContent : TextLikeContent {
141+ public RtfContent ( string text ) : base ( text ) { }
142+ public override string [ ] Extensions => new [ ] { "rtf" } ;
143+ public override string Description => Resources . str_preview_rtf ;
144+ public override void SaveAs ( string path , string extension ) {
145+ File . WriteAllText ( path , Text , Encoding . ASCII ) ;
146+ }
147+ }
148+
149+
150+ public class UrlContent : TextLikeContent {
151+ public UrlContent ( string text ) : base ( text ) { }
152+ public override string [ ] Extensions => new [ ] { "url" } ;
153+ public override string Description => Resources . str_preview_url ;
154+ public override void SaveAs ( string path , string extension ) {
155+ File . WriteAllLines ( path , new [ ] {
156+ @"[InternetShortcut]" ,
157+ @"URL=" + Text
158+ } , Encoding . UTF8 ) ;
159+ }
160+ }
161+
162+
163+
164+
165+
166+ /// <summary>
167+ /// Class to hold all supported clipboard content
168+ /// and provide concise methods to access it
169+ /// </summary>
170+ public class ClipboardContents {
171+
172+ public DateTime Timestamp ;
173+ public readonly IList < BaseContent > Contents = new List < BaseContent > ( ) ;
174+
175+ /// <summary>
176+ /// Return contents matching the given extension, fall back to text content if available
177+ /// </summary>
178+ /// <param name="ext"></param>
179+ /// <returns></returns>
180+ public BaseContent ForExtension ( string ext ) {
181+ foreach ( var content in Contents ) {
182+ if ( content . Extensions . Contains ( ext ) )
183+ return content ;
184+ }
185+ // if ext is not compatible with text, return null ...
186+ foreach ( var reserved in new BaseContent [ ]
187+ { new ImageContent ( null ) , new UrlContent ( null ) } ) {
188+ if ( reserved . Extensions . Contains ( ext ) )
189+ return null ;
190+ }
191+ // ... otherwise default to text
192+ return Contents . OfType < TextContent > ( ) . FirstOrDefault ( ) ;
193+ }
194+
195+ /// <summary>
196+ /// Return contents of specific type
197+ /// </summary>
198+ /// <param name="type">Any type of BaseContent or its subclasses</param>
199+ /// <returns></returns>
200+ public BaseContent ForContentType ( Type type ) {
201+ foreach ( var content in Contents ) {
202+ if ( content . GetType ( ) == type )
203+ return content ;
204+ }
205+ return null ;
206+ }
207+
208+ /// <summary>
209+ /// Determines the primary type of data in this container according to a custom prioritisation order
210+ /// </summary>
211+ public BaseContent PrimaryContent => ForContentType ( typeof ( ImageContent ) ) ??
212+ ForContentType ( typeof ( TextContent ) ) ??
213+ ForContentType ( typeof ( BaseContent ) ) ;
214+
215+ /// <summary>
216+ /// Static constructor to create an instance from the current clipboard data
217+ /// </summary>
218+ /// <returns></returns>
219+ public static ClipboardContents FromClipboard ( ) {
220+ var container = new ClipboardContents {
221+ Timestamp = DateTime . Now
222+ } ;
223+
224+ // Read all supported clipboard data
225+ // https://docs.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats
226+
227+ if ( Clipboard . ContainsImage ( ) )
228+ container . Contents . Add ( new ImageContent ( Clipboard . GetImage ( ) ) ) ;
229+ if ( Clipboard . ContainsData ( DataFormats . Html ) )
230+ container . Contents . Add ( new HtmlContent ( ReadClipboardHtml ( ) ) ) ;
231+ if ( Clipboard . ContainsData ( DataFormats . CommaSeparatedValue ) )
232+ container . Contents . Add ( new CsvContent ( ReadClipboardString ( DataFormats . CommaSeparatedValue ) ) ) ;
233+ if ( Clipboard . ContainsData ( DataFormats . SymbolicLink ) )
234+ container . Contents . Add ( new SylkContent ( ReadClipboardString ( DataFormats . SymbolicLink ) ) ) ;
235+ if ( Clipboard . ContainsData ( DataFormats . Rtf ) )
236+ container . Contents . Add ( new RtfContent ( ReadClipboardString ( DataFormats . Rtf ) ) ) ;
237+ if ( Clipboard . ContainsData ( DataFormats . Dif ) )
238+ container . Contents . Add ( new DifContent ( ReadClipboardString ( DataFormats . Dif ) ) ) ;
239+
240+ if ( Clipboard . ContainsFileDropList ( ) && ! Clipboard . ContainsText ( ) )
241+ // save list of file paths instead
242+ container . Contents . Add ( new TextContent ( string . Join ( "\n " , Clipboard . GetFileDropList ( ) . Cast < string > ( ) . ToList ( ) ) ) ) ;
243+
244+ if ( Clipboard . ContainsText ( ) && Uri . IsWellFormedUriString ( Clipboard . GetText ( ) . Trim ( ) , UriKind . RelativeOrAbsolute ) )
245+ container . Contents . Add ( new UrlContent ( Clipboard . GetText ( ) . Trim ( ) ) ) ;
246+
247+ // make sure text content comes last, as it may includes extensions from previous formats
248+ if ( Clipboard . ContainsText ( ) )
249+ container . Contents . Add ( new TextContent ( Clipboard . GetText ( ) ) ) ;
250+
251+
252+ return container ;
253+ }
254+
255+ private static string ReadClipboardHtml ( ) {
256+ var content = Clipboard . GetText ( TextDataFormat . Html ) ;
257+ var match = Regex . Match ( content , @"StartHTML:(?<startHTML>\d*).*EndHTML:(?<endHTML>\d*)" , RegexOptions . Singleline ) ;
258+ if ( match . Success ) {
259+ var startHtml = Math . Max ( int . Parse ( match . Groups [ "startHTML" ] . Value ) , 0 ) ;
260+ var endHtml = Math . Min ( int . Parse ( match . Groups [ "endHTML" ] . Value ) , content . Length ) ;
261+ return content . Substring ( startHtml , endHtml - startHtml ) ;
262+ }
263+ return null ;
264+ }
265+
266+ private static string ReadClipboardString ( string format ) {
267+ var data = Clipboard . GetData ( format ) ;
268+ switch ( data ) {
269+ case string str :
270+ return str ;
271+ case MemoryStream stream :
272+ return new StreamReader ( stream ) . ReadToEnd ( ) . TrimEnd ( '\0 ' ) ;
273+ default :
274+ return null ;
275+ }
276+ }
277+
278+ }
279+ }
0 commit comments