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:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
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.
20 // Copyright (c) 2004 Novell, Inc.
23 // Peter Bartok pbartok@novell.com
32 using System.ComponentModel;
33 using System.Runtime.InteropServices;
34 using System.Runtime.Serialization;
36 namespace System.Windows.Forms {
37 [Editor("System.Drawing.Design.CursorEditor, " + Consts.AssemblySystem_Drawing_Design, typeof(System.Drawing.Design.UITypeEditor))]
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
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
63 [StructLayout(LayoutKind.Sequential)]
64 private struct CursorInfoHeader {
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;
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
85 #endregion // Internal structs
87 #region Local Variables
88 private static Cursor current;
89 private CursorDir cursor_dir;
90 private CursorImage[] cursor_data;
93 internal IntPtr handle;
97 private Bitmap cursor;
99 #endregion // Local Variables
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();
112 if (handle != IntPtr.Zero) {
113 this.cursor = ToBitmap(true, true);
117 private Cursor(SerializationInfo info, StreamingContext context) {
127 // This is supposed to take a Win32 handle
128 public Cursor(IntPtr handle) {
129 this.handle = handle;
132 public Cursor(System.IO.Stream stream) {
133 CreateCursor(stream);
136 public Cursor(string fileName) : this (new FileStream (fileName, FileMode.Open)) {
139 public Cursor(Type type, string resource) {
140 using (Stream s = type.Assembly.GetManifestResourceStream (type, resource)) {
142 throw new FileNotFoundException ("Resource name was not found: `" + resource + "'");
147 #endregion // Public Constructors
149 #region Public Static Properties
150 public static Rectangle Clip {
157 XplatUI.GrabInfo(out handle, out confined, out rect);
158 if (handle != IntPtr.Zero) {
162 XplatUI.GetDisplaySize(out size);
165 rect.Width = size.Width;
166 rect.Height = size.Height;
170 [MonoTODO("First need to add ability to set cursor clip rectangle to XplatUI drivers to implement this property")]
176 [MonoTODO("Implement setting a null cursor, and add XplatUI method to get current cursor")]
177 public static Cursor Current {
179 if (current != null) {
182 return Cursors.Default;
186 if (current != value) {
189 // FIXME - define and set empty cursor
191 XplatUI.OverrideCursor(IntPtr.Zero);
193 XplatUI.OverrideCursor(current.handle);
198 public static Point Position {
203 XplatUI.GetCursorPos (IntPtr.Zero, out x, out y);
204 return new Point (x, y);
208 XplatUI.SetCursorPos(IntPtr.Zero, value.X, value.Y);
211 #endregion // Public Static Properties
213 #region Public Instance Properties
214 public IntPtr Handle {
225 #endregion // Public Instance Properties
227 #region Public Static Methods
228 public static void Hide() {
229 XplatUI.ShowCursor(false);
232 public static void Show() {
233 XplatUI.ShowCursor(false);
236 public static bool operator !=(Cursor left, Cursor right) {
237 if ((object)left == (object)right) {
241 if ((object)left == null || (object)right == null) {
245 if (left.handle == right.handle) {
252 public static bool operator ==(Cursor left, Cursor right) {
253 if ((object)left == (object)right) {
257 if ((object)left == null || (object)right == null) {
261 if (left.handle == right.handle) {
266 #endregion // Public Static Methods
268 #region Public Instance Methods
269 public IntPtr CopyHandle() {
273 public void Dispose() {
274 if (this.cursor != null) {
275 this.cursor.Dispose();
279 if (this.shape != null) {
280 this.shape.Dispose();
284 if (this.mask != null) {
289 GC.SuppressFinalize (this);
292 public void Draw(Graphics g, Rectangle targetRect) {
293 if (this.cursor != null) {
294 g.DrawImage(this.cursor, targetRect);
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);
304 public override bool Equals(object obj) {
305 if ( !(obj is Cursor)) {
309 if (((Cursor)obj).handle == this.handle) {
316 public override int GetHashCode() {
317 return base.GetHashCode ();
320 public override string ToString() {
322 return "[Cursor:" + name + "]";
325 throw new FormatException("Cannot convert custom cursors to string.");
328 void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context) {
333 ms = new MemoryStream();
334 wr = new BinaryWriter(ms);
335 ci = cursor_data[this.id];
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
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
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]);
367 wr.Write(ci.cursorXOR);
368 wr.Write(ci.cursorAND);
371 si.AddValue ("CursorData", ms.ToArray());
373 #endregion // Public Instance Methods
375 #region Private Methods w
376 private void InitFromStream(Stream stream) {
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");
386 BinaryReader reader = new BinaryReader (stream);
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");
394 cursor_dir.idType = reader.ReadUInt16();
395 if (cursor_dir.idType != 2) { //must be 2
396 throw new System.ArgumentException ("Invalid Argument", "stream");
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];
404 //now read in the CursorEntry structures
405 for (int i=0; i < entry_count; i++){
406 ce = new CursorEntry();
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();
417 cursor_dir.idEntries[i] = ce;
420 // If we have more than one pick the largest cursor
422 for (int j=0; j < entry_count; j++){
423 if (cursor_dir.idEntries[j].sizeInBytes >= largest) {
424 largest = cursor_dir.idEntries[j].sizeInBytes;
426 this.size.Height = cursor_dir.idEntries[j].height;
427 this.size.Width = cursor_dir.idEntries[j].width;
431 //now read in the cursor data
432 for (int j = 0; j < entry_count; j++) {
434 CursorInfoHeader cih;
436 BinaryReader cih_reader;
443 curdata = new CursorImage();
444 cih = new CursorInfoHeader();
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);
450 cih_reader = new BinaryReader(new MemoryStream(buffer));
452 cih.biSize = cih_reader.ReadUInt32 ();
453 if (cih.biSize != 40) {
454 throw new System.ArgumentException ("Invalid cursor file", "stream");
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 ();
467 curdata.cursorHeader = cih;
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;
478 curdata.cursorColors = new uint[num_colors];
479 for (int i = 0; i < num_colors; i++) {
480 curdata.cursorColors[i] = cih_reader.ReadUInt32 ();
483 //XOR mask is immediately after ColorTable and its size is
484 //icon height* no. of bytes per line
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;
490 //bytes per line should should be uint aligned
491 bytes_per_line = ((((cih.biWidth * cih.biPlanes * cih.biBitCount)+ 31)>>5)<<2);
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();
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();
507 cursor_data[j] = curdata;
514 private Bitmap ToBitmap (bool xor, bool transparent) {
517 if (cursor_data != null) {
524 CursorInfoHeader cih;
527 stream = new MemoryStream();
528 writer = new BinaryWriter (stream);
530 ci = cursor_data[this.id];
533 // write bitmap file header
537 // write the file size
538 // file size = bitmapfileheader + bitmapinfo + colorpalette + image bits
539 // sizeof bitmapfileheader = 14 bytes
540 // sizeof bitmapinfo = 40 bytes
542 offset = (uint)(14 + 40 + ci.cursorColors.Length * 4);
543 filesize = (uint)(offset + ci.cursorXOR.Length);
545 offset = (uint)(14 + 40 + 8); // AND mask is always monochrome
546 filesize = (uint)(offset + ci.cursorAND.Length);
548 writer.Write(filesize);
550 // write reserved words
552 writer.Write(reserved12);
553 writer.Write(reserved12);
556 writer.Write (offset);
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);
565 writer.Write(cih.biBitCount);
567 writer.Write((ushort)1);
569 writer.Write(cih.biCompression);
571 writer.Write(ci.cursorXOR.Length);
573 writer.Write(ci.cursorAND.Length);
575 writer.Write(cih.biXPelsPerMeter);
576 writer.Write(cih.biYPelsPerMeter);
577 writer.Write(cih.biClrUsed);
578 writer.Write(cih.biClrImportant);
582 color_count = ci.cursorColors.Length;
583 for (int j = 0; j < color_count; j++) {
584 writer.Write (ci.cursorColors[j]);
587 writer.Write((uint)0x00000000);
588 writer.Write((uint)0x00ffffff);
593 writer.Write(ci.cursorXOR);
595 writer.Write(ci.cursorAND);
599 // create bitmap from stream and return
600 bmp = new Bitmap(stream);
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);
615 } catch (Exception e) {
621 bmp = new Bitmap (32, 32);
627 #endregion // Private Methods