* TreeView.cs: Don't draw the selected node when we lose
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Cursor.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2004 Novell, Inc.
21 //
22 // Authors:
23 //      Peter Bartok    pbartok@novell.com
24 //
25
26
27 // NOT COMPLETE
28
29 using System;
30 using System.Drawing;
31 using System.IO;
32 using System.ComponentModel;
33 using System.Runtime.InteropServices;
34 using System.Runtime.Serialization;
35
36 namespace System.Windows.Forms {
37         [Editor("System.Drawing.Design.CursorEditor, " + Consts.AssemblySystem_Drawing_Design, typeof(System.Drawing.Design.UITypeEditor))]
38         [Serializable]
39         [TypeConverter(typeof(CursorConverter))]
40         public sealed class Cursor : IDisposable, ISerializable {
41                 #region Internal Structs
42                 [StructLayout(LayoutKind.Sequential)]
43                 private  struct CursorDir {
44                         internal ushort         idReserved;     // Reserved
45                         internal ushort         idType;         // resource type (2 for cursors)
46                         internal ushort         idCount;        // how many cursors
47                         internal CursorEntry[]  idEntries;      // the entries for each cursor
48                 };
49                 
50                 [StructLayout(LayoutKind.Sequential)]
51                 private  struct CursorEntry {
52                         internal byte           width;          // Width of cursor
53                         internal byte           height;         // Height of cursor
54                         internal byte           colorCount;     // colors in cursor
55                         internal byte           reserved;       // Reserved
56                         internal ushort         xHotspot;       // Hotspot X
57                         internal ushort         yHotspot;       // Hotspot Y
58                         internal ushort         bitCount;       // Bits per pixel
59                         internal uint           sizeInBytes;    // size of (CursorInfoHeader + ANDBitmap + ORBitmap)
60                         internal uint           fileOffset;     // position in file 
61                 }; 
62
63                 [StructLayout(LayoutKind.Sequential)]
64                 private  struct CursorInfoHeader {
65                         internal uint           biSize; 
66                         internal int            biWidth; 
67                         internal int            biHeight; 
68                         internal ushort         biPlanes; 
69                         internal ushort         biBitCount; 
70                         internal uint           biCompression; 
71                         internal uint           biSizeImage; 
72                         internal int            biXPelsPerMeter; 
73                         internal int            biYPelsPerMeter; 
74                         internal uint           biClrUsed; 
75                         internal uint           biClrImportant; 
76                 };
77
78                 [StructLayout(LayoutKind.Sequential)]
79                 private struct CursorImage {
80                         internal CursorInfoHeader       cursorHeader;   // image header
81                         internal uint[]                 cursorColors;   // colors table
82                         internal byte[]                 cursorXOR;      // bits for XOR mask
83                         internal byte[]                 cursorAND;      // bits for AND mask
84                 };
85                 #endregion      // Internal structs
86
87                 #region Local Variables
88                 private static Cursor   current;
89                 private CursorDir       cursor_dir;
90                 private CursorImage[]   cursor_data;
91                 private int             id;
92
93                 internal IntPtr         handle;
94                 private Size            size;
95                 private Bitmap          shape;
96                 private Bitmap          mask;
97                 private Bitmap          cursor;
98                 internal string         name;
99                 #endregion      // Local Variables
100
101                 #region Public Constructors
102                 private void CreateCursor(System.IO.Stream stream) {
103                         InitFromStream(stream);
104                         this.shape = ToBitmap(true, false);
105                         this.mask = ToBitmap(false, false);
106                         handle = XplatUI.DefineCursor(shape, mask, Color.FromArgb(255, 255, 255), Color.FromArgb(255, 255, 255), cursor_dir.idEntries[id].xHotspot, cursor_dir.idEntries[id].yHotspot);
107                         this.shape.Dispose();
108                         this.shape = null;
109                         this.mask.Dispose();
110                         this.mask = null;
111
112                         if (handle != IntPtr.Zero) {
113                                 this.cursor = ToBitmap(true, true);
114                         }
115                 }
116
117                 private Cursor(SerializationInfo info, StreamingContext context) {
118                 }
119
120                 private Cursor() {
121                 }
122
123                 ~Cursor() {
124                         Dispose();
125                 }
126
127                 // This is supposed to take a Win32 handle
128                 public Cursor(IntPtr handle) {
129                         this.handle = handle;
130                 }
131
132                 public Cursor(System.IO.Stream stream) {
133                         CreateCursor(stream);
134                 }
135
136                 public Cursor(string fileName) : this (new FileStream (fileName, FileMode.Open)) {
137                 }
138
139                 public Cursor(Type type, string resource) {
140                         using (Stream s = type.Assembly.GetManifestResourceStream (type, resource)) {
141                                 if (s == null) {
142                                         throw new FileNotFoundException ("Resource name was not found: `" + resource + "'");
143                                 }
144                                 CreateCursor(s);
145                         }
146                 }
147                 #endregion      // Public Constructors
148
149                 #region Public Static Properties
150                 public static Rectangle Clip {
151                         get {
152                                 IntPtr          handle;
153                                 bool            confined;
154                                 Rectangle       rect;
155                                 Size            size;
156
157                                 XplatUI.GrabInfo(out handle, out confined, out rect);
158                                 if (handle != IntPtr.Zero) {
159                                         return rect;
160                                 }
161
162                                 XplatUI.GetDisplaySize(out size);
163                                 rect.X = 0;
164                                 rect.Y = 0;
165                                 rect.Width = size.Width;
166                                 rect.Height = size.Height;
167                                 return rect;
168                         }
169
170                         [MonoTODO("First need to add ability to set cursor clip rectangle to XplatUI drivers to implement this property")]
171                         set {
172                                 ;
173                         }
174                 }
175
176                 [MonoTODO("Implement setting a null cursor, and add XplatUI method to get current cursor")]
177                 public static Cursor Current {
178                         get {
179                                 if (current != null) {
180                                         return current;
181                                 }
182                                 return Cursors.Default;
183                         }
184
185                         set {
186                                 if (current != value) {
187                                         current = value;
188                                         if (value != null){
189                                                 // FIXME - define and set empty cursor
190                                                 current = null;
191                                                 XplatUI.OverrideCursor(IntPtr.Zero);
192                                         } else
193                                                 XplatUI.OverrideCursor(current.handle);
194                                 }
195                         }
196                 }
197
198                 public static Point Position {
199                         get {
200                                 int x;
201                                 int y;
202
203                                 XplatUI.GetCursorPos (IntPtr.Zero, out x, out y);
204                                 return new Point (x, y);
205                         }
206
207                         set {
208                                 XplatUI.SetCursorPos(IntPtr.Zero, value.X, value.Y);
209                         }
210                 }
211                 #endregion      // Public Static Properties
212
213                 #region Public Instance Properties
214                 public IntPtr Handle {
215                         get {
216                                 return handle;
217                         }
218                 }
219
220                 public Size Size {
221                         get {
222                                 return size;
223                         }
224                 }
225                 #endregion      // Public Instance Properties
226
227                 #region Public Static Methods
228                 public static void Hide() {
229                         XplatUI.ShowCursor(false);
230                 }
231
232                 public static void Show() {
233                         XplatUI.ShowCursor(false);
234                 }
235
236                 public static bool operator !=(Cursor left, Cursor right) {
237                         if ((object)left == (object)right) {
238                                 return false;
239                         }
240
241                         if ((object)left == null || (object)right == null) {
242                                 return true;
243                         }
244
245                         if (left.handle == right.handle) {
246                                 return false;
247                         }
248                         return true;
249                 }
250
251
252                 public static bool operator ==(Cursor left, Cursor right) {
253                         if ((object)left == (object)right) {
254                                 return true;
255                         }
256
257                         if ((object)left == null || (object)right == null) {
258                                 return false;
259                         }
260
261                         if (left.handle == right.handle) {
262                                 return true;
263                         }
264                         return false;
265                 }
266                 #endregion      // Public Static Methods
267
268                 #region Public Instance Methods
269                 public IntPtr CopyHandle() {
270                         return handle;
271                 }
272
273                 public void Dispose() {
274                         if (this.cursor != null) {
275                                 this.cursor.Dispose();
276                                 this.cursor = null;
277                         }
278
279                         if (this.shape != null) {
280                                 this.shape.Dispose();
281                                 this.shape = null;
282                         }
283
284                         if (this.mask != null) {
285                                 this.mask.Dispose();
286                                 this.mask = null;
287                         }
288
289                         GC.SuppressFinalize (this);
290                 }
291
292                 public void Draw(Graphics g, Rectangle targetRect) {
293                         if (this.cursor != null) {
294                                 g.DrawImage(this.cursor, targetRect);
295                         }
296                 }
297
298                 public void DrawStretched(Graphics g, Rectangle targetRect) {
299                         if (this.cursor != null) {
300                                 g.DrawImage(this.cursor, targetRect, new Rectangle(0, 0, this.cursor.Width, this.cursor.Height), GraphicsUnit.Pixel);
301                         }
302                 }
303
304                 public override bool Equals(object obj) {
305                         if ( !(obj is Cursor)) {
306                                 return false;
307                         }
308
309                         if (((Cursor)obj).handle == this.handle) {
310                                 return true;
311                         }
312
313                         return false;
314                 }
315
316                 public override int GetHashCode() {
317                         return base.GetHashCode ();
318                 }
319
320                 public override string ToString() {
321                         if (name != null) {
322                                 return "[Cursor:" + name + "]";
323                         }
324
325                         throw new FormatException("Cannot convert custom cursors to string.");
326                 }
327
328                 void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context) {
329                         MemoryStream    ms;
330                         BinaryWriter    wr;
331                         CursorImage     ci;
332
333                         ms = new MemoryStream();
334                         wr = new BinaryWriter(ms);
335                         ci = cursor_data[this.id];
336
337                         // Build the headers, first the CursorDir
338                         wr.Write((ushort)0);    // Reserved
339                         wr.Write((ushort)2);    // Resource type
340                         wr.Write((ushort)1);    // Count
341
342                         // Next the CursorEntry
343                         wr.Write((byte)cursor_dir.idEntries[this.id].width);
344                         wr.Write((byte)cursor_dir.idEntries[this.id].height);
345                         wr.Write((byte)cursor_dir.idEntries[this.id].colorCount);
346                         wr.Write((byte)cursor_dir.idEntries[this.id].reserved);
347                         wr.Write((ushort)cursor_dir.idEntries[this.id].xHotspot);
348                         wr.Write((ushort)cursor_dir.idEntries[this.id].yHotspot);
349                         wr.Write((uint)(40 + (ci.cursorColors.Length * 4) + ci.cursorXOR.Length + ci.cursorAND.Length));
350                         wr.Write((uint)(6 + 16));       // CursorDir + CursorEntry size
351
352                         // Then the CursorInfoHeader
353                         wr.Write(ci.cursorHeader.biSize);
354                         wr.Write(ci.cursorHeader.biWidth);
355                         wr.Write(ci.cursorHeader.biHeight);
356                         wr.Write(ci.cursorHeader.biPlanes);
357                         wr.Write(ci.cursorHeader.biBitCount);
358                         wr.Write(ci.cursorHeader.biCompression);
359                         wr.Write(ci.cursorHeader.biSizeImage);
360                         wr.Write(ci.cursorHeader.biXPelsPerMeter);
361                         wr.Write(ci.cursorHeader.biYPelsPerMeter);
362                         wr.Write(ci.cursorHeader.biClrUsed);
363                         wr.Write(ci.cursorHeader.biClrImportant);
364                         for (int i = 0; i < ci.cursorColors.Length; i++) {
365                                 wr.Write(ci.cursorColors[i]);
366                         }
367                         wr.Write(ci.cursorXOR);
368                         wr.Write(ci.cursorAND);
369                         wr.Flush();
370
371                         si.AddValue ("CursorData", ms.ToArray());
372                 }
373                 #endregion      // Public Instance Methods
374
375                 #region Private Methods           w
376                 private void InitFromStream(Stream stream) {
377                         ushort          entry_count;
378                         CursorEntry     ce;
379                         uint            largest;
380
381                         //read the cursor header
382                         if (stream == null || stream.Length == 0) {
383                                 throw new System.ArgumentException ("The argument 'stream' must be a picture that can be used as a cursor", "stream");
384                         }
385                         
386                         BinaryReader reader = new BinaryReader (stream);
387             
388                         cursor_dir = new CursorDir ();
389                         cursor_dir.idReserved = reader.ReadUInt16();
390                         if (cursor_dir.idReserved != 0) {
391                                 throw new System.ArgumentException ("Invalid Argument", "stream");
392                         }
393                         
394                         cursor_dir.idType = reader.ReadUInt16();
395                         if (cursor_dir.idType != 2) { //must be 2
396                                 throw new System.ArgumentException ("Invalid Argument", "stream");
397                         }
398
399                         entry_count = reader.ReadUInt16();
400                         cursor_dir.idCount = entry_count;
401                         cursor_dir.idEntries = new CursorEntry[entry_count];
402                         cursor_data = new CursorImage[entry_count];
403
404                         //now read in the CursorEntry structures
405                         for (int i=0; i < entry_count; i++){
406                                 ce = new CursorEntry();
407
408                                 ce.width = reader.ReadByte();
409                                 ce.height = reader.ReadByte();
410                                 ce.colorCount = reader.ReadByte();
411                                 ce.reserved = reader.ReadByte();
412                                 ce.xHotspot = reader.ReadUInt16();
413                                 ce.yHotspot = reader.ReadUInt16();
414                                 ce.sizeInBytes = reader.ReadUInt32();
415                                 ce.fileOffset = reader.ReadUInt32();
416
417                                 cursor_dir.idEntries[i] = ce;
418                         }
419
420                         // If we have more than one pick the largest cursor
421                         largest = 0;
422                         for (int j=0; j < entry_count; j++){
423                                 if (cursor_dir.idEntries[j].sizeInBytes >= largest)     {
424                                         largest = cursor_dir.idEntries[j].sizeInBytes;
425                                         this.id = (ushort)j;
426                                         this.size.Height = cursor_dir.idEntries[j].height;
427                                         this.size.Width = cursor_dir.idEntries[j].width;
428                                 }
429                         }
430
431                         //now read in the cursor data
432                         for (int j = 0; j < entry_count; j++) {
433                                 CursorImage             curdata;
434                                 CursorInfoHeader        cih;
435                                 byte[]                  buffer;
436                                 BinaryReader            cih_reader;
437                                 int                     num_colors;
438                                 int                     cursor_height;
439                                 int                     bytes_per_line;
440                                 int                     xor_size;
441                                 int                     and_size;
442
443                                 curdata = new CursorImage();
444                                 cih = new CursorInfoHeader();
445                                 
446                                 stream.Seek (cursor_dir.idEntries[j].fileOffset, SeekOrigin.Begin);
447                                 buffer = new byte [cursor_dir.idEntries[j].sizeInBytes];
448                                 stream.Read (buffer, 0, buffer.Length);
449
450                                 cih_reader = new BinaryReader(new MemoryStream(buffer));
451
452                                 cih.biSize = cih_reader.ReadUInt32 ();
453                                 if (cih.biSize != 40) {
454                                         throw new System.ArgumentException ("Invalid cursor file", "stream");
455                                 }
456                                 cih.biWidth = cih_reader.ReadInt32 ();
457                                 cih.biHeight = cih_reader.ReadInt32 ();
458                                 cih.biPlanes = cih_reader.ReadUInt16 ();
459                                 cih.biBitCount = cih_reader.ReadUInt16 ();
460                                 cih.biCompression = cih_reader.ReadUInt32 ();
461                                 cih.biSizeImage = cih_reader.ReadUInt32 ();
462                                 cih.biXPelsPerMeter = cih_reader.ReadInt32 ();
463                                 cih.biYPelsPerMeter = cih_reader.ReadInt32 ();
464                                 cih.biClrUsed = cih_reader.ReadUInt32 ();
465                                 cih.biClrImportant = cih_reader.ReadUInt32 ();
466
467                                 curdata.cursorHeader = cih;
468
469                                 //Read the number of colors used and corresponding memory occupied by
470                                 //color table. Fill this memory chunk into rgbquad[]
471                                 switch (cih.biBitCount){
472                                         case 1: num_colors = 2; break;
473                                         case 4: num_colors = 16; break;
474                                         case 8: num_colors = 256; break;
475                                         default: num_colors = 0; break;
476                                 }
477                                 
478                                 curdata.cursorColors = new uint[num_colors];
479                                 for (int i = 0; i < num_colors; i++) {
480                                         curdata.cursorColors[i] = cih_reader.ReadUInt32 ();
481                                 }
482
483                                 //XOR mask is immediately after ColorTable and its size is 
484                                 //icon height* no. of bytes per line
485                                 
486                                 //cursor height is half of BITMAPINFOHEADER.biHeight, since it contains
487                                 //both XOR as well as AND mask bytes
488                                 cursor_height = cih.biHeight/2;
489                                 
490                                 //bytes per line should should be uint aligned
491                                 bytes_per_line = ((((cih.biWidth * cih.biPlanes * cih.biBitCount)+ 31)>>5)<<2);
492                                 
493                                 //Determine the XOR array Size
494                                 xor_size = bytes_per_line * cursor_height;
495                                 curdata.cursorXOR = new byte[xor_size];
496                                 for (int i = 0; i < xor_size; i++) {
497                                         curdata.cursorXOR[i] = cih_reader.ReadByte();
498                                 }
499                                 
500                                 //Determine the AND array size
501                                 and_size = (int)(cih_reader.BaseStream.Length - cih_reader.BaseStream.Position);
502                                 curdata.cursorAND = new byte[and_size];
503                                 for (int i = 0; i < and_size; i++) {
504                                         curdata.cursorAND[i] = cih_reader.ReadByte();
505                                 }
506                                 
507                                 cursor_data[j] = curdata;
508                                 cih_reader.Close();
509                         }                       
510
511                         reader.Close();
512                 }
513
514                 private Bitmap ToBitmap (bool xor, bool transparent) {
515                         Bitmap bmp;
516
517                         if (cursor_data != null) {
518                                 MemoryStream            stream;
519                                 BinaryWriter            writer;
520                                 CursorImage             ci;
521                                 uint                    offset;
522                                 uint                    filesize;
523                                 ushort                  reserved12;
524                                 CursorInfoHeader        cih;
525                                 int                     color_count;
526
527                                 stream = new MemoryStream();
528                                 writer = new BinaryWriter (stream);
529
530                                 ci = cursor_data[this.id];
531
532                                 try {
533                                         // write bitmap file header
534                                         writer.Write ('B');
535                                         writer.Write ('M');
536
537                                         // write the file size
538                                         // file size = bitmapfileheader + bitmapinfo + colorpalette + image bits
539                                         // sizeof bitmapfileheader = 14 bytes
540                                         // sizeof bitmapinfo = 40 bytes
541                                         if (xor) {
542                                                 offset = (uint)(14 + 40 + ci.cursorColors.Length * 4);
543                                                 filesize = (uint)(offset + ci.cursorXOR.Length);
544                                         } else {
545                                                 offset = (uint)(14 + 40 + 8);   // AND mask is always monochrome
546                                                 filesize = (uint)(offset + ci.cursorAND.Length);
547                                         }
548                                         writer.Write(filesize);
549                                         
550                                         // write reserved words
551                                         reserved12 = 0;
552                                         writer.Write(reserved12);
553                                         writer.Write(reserved12);
554
555                                         // write offset
556                                         writer.Write (offset);
557
558                                         // write bitmapfile header
559                                         cih = ci.cursorHeader;
560                                         writer.Write(cih.biSize);
561                                         writer.Write(cih.biWidth);
562                                         writer.Write(cih.biHeight/2);
563                                         writer.Write(cih.biPlanes);
564                                         if (xor) {
565                                                 writer.Write(cih.biBitCount);
566                                         } else {
567                                                 writer.Write((ushort)1);
568                                         }
569                                         writer.Write(cih.biCompression);
570                                         if (xor) {
571                                                 writer.Write(ci.cursorXOR.Length);
572                                         } else {
573                                                 writer.Write(ci.cursorAND.Length);
574                                         }
575                                         writer.Write(cih.biXPelsPerMeter);
576                                         writer.Write(cih.biYPelsPerMeter);
577                                         writer.Write(cih.biClrUsed);
578                                         writer.Write(cih.biClrImportant);
579
580                                         // write color table
581                                         if (xor) {
582                                                 color_count = ci.cursorColors.Length;
583                                                 for (int j = 0; j < color_count; j++) {
584                                                         writer.Write (ci.cursorColors[j]);
585                                                 }
586                                         } else {
587                                                 writer.Write((uint)0x00000000);
588                                                 writer.Write((uint)0x00ffffff);
589                                         }
590
591                                         // write image bits
592                                         if (xor) {
593                                                 writer.Write(ci.cursorXOR);
594                                         } else {
595                                                 writer.Write(ci.cursorAND);
596                                         }
597                                         writer.Flush();
598
599                                         // create bitmap from stream and return
600                                         bmp = new Bitmap(stream);
601
602                                         if (transparent) {
603                                                 bmp = new Bitmap(bmp);  // This makes a 32bpp image out of an indexed one
604                                                 // Apply the mask to make properly transparent
605                                                 for (int y = 0; y < cih.biHeight/2; y++) {
606                                                         for (int x = 0; x < cih.biWidth / 8; x++) {
607                                                                 for (int bit = 7; bit >= 0; bit--) {
608                                                                         if (((ci.cursorAND[y * cih.biWidth / 8 +x] >> bit) & 1) != 0) {
609                                                                                 bmp.SetPixel(x*8 + 7-bit, cih.biHeight/2 - y - 1, Color.Transparent);
610                                                                         }
611                                                                 }
612                                                         }
613                                                 }
614                                         }
615                                 } catch (Exception e) {
616                                         throw e;
617                                 } finally {
618                                         writer.Close();
619                                 }
620                         } else {
621                                 bmp = new Bitmap (32, 32);
622                         }
623
624                         return bmp;
625                 }
626
627                 #endregion      // Private Methods
628         }
629 }