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-2010 Novell, Inc.
23 // Peter Bartok pbartok@novell.com
28 using System.Drawing.Imaging;
30 using System.ComponentModel;
31 using System.Runtime.InteropServices;
32 using System.Runtime.Serialization;
33 using System.Reflection;
35 namespace System.Windows.Forms {
36 [Editor("System.Drawing.Design.CursorEditor, " + Consts.AssemblySystem_Drawing_Design, typeof(System.Drawing.Design.UITypeEditor))]
38 [TypeConverter(typeof(CursorConverter))]
39 public sealed class Cursor : IDisposable, ISerializable {
40 #region Internal Structs
41 [StructLayout (LayoutKind.Sequential)]
42 private struct CursorDir {
43 internal ushort idReserved; // Reserved
44 internal ushort idType; // resource type (2 for cursors)
45 internal ushort idCount; // how many cursors
46 internal CursorEntry[] idEntries; // the entries for each cursor
49 [StructLayout (LayoutKind.Sequential)]
50 private struct CursorEntry {
51 internal byte width; // Width of cursor
52 internal byte height; // Height of cursor
53 internal byte colorCount; // colors in cursor
54 internal byte reserved; // Reserved
55 internal ushort xHotspot; // Hotspot X
56 internal ushort yHotspot; // Hotspot Y
57 internal ushort bitCount; // Bits per pixel
58 internal uint sizeInBytes; // size of (CursorInfoHeader + ANDBitmap + ORBitmap)
59 internal uint fileOffset; // position in file
62 [StructLayout(LayoutKind.Sequential)]
63 private struct CursorInfoHeader {
66 internal int biHeight;
67 internal ushort biPlanes;
68 internal ushort biBitCount;
69 internal uint biCompression;
70 internal uint biSizeImage;
71 internal int biXPelsPerMeter;
72 internal int biYPelsPerMeter;
73 internal uint biClrUsed;
74 internal uint biClrImportant;
77 [StructLayout(LayoutKind.Sequential)]
78 private struct CursorImage {
79 internal CursorInfoHeader cursorHeader; // image header
80 internal uint[] cursorColors; // colors table
81 internal byte[] cursorXOR; // bits for XOR mask
82 internal byte[] cursorAND; // bits for AND mask
84 #endregion // Internal structs
86 #region Local Variables
87 private static Cursor current;
88 private CursorDir cursor_dir;
89 private CursorImage[] cursor_data;
92 internal IntPtr handle;
96 private Bitmap cursor;
98 private StdCursor std_cursor = (StdCursor) (-1);
102 #endregion // Local Variables
104 #region Public Constructors
105 private void CreateCursor (Stream stream)
107 InitFromStream(stream);
108 this.shape = ToBitmap(true, false);
109 this.mask = ToBitmap(false, false);
110 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);
111 this.shape.Dispose();
116 if (handle != IntPtr.Zero) {
117 this.cursor = ToBitmap(true, true);
121 internal Cursor (StdCursor cursor) : this (XplatUI.DefineStdCursor (cursor))
126 private Cursor(SerializationInfo info, StreamingContext context)
139 // This is supposed to take a Win32 handle
140 public Cursor (IntPtr handle)
142 this.handle = handle;
145 public Cursor (Stream stream)
147 CreateCursor(stream);
150 public Cursor (string fileName)
152 using (FileStream fs = File.OpenRead (fileName)) {
157 public Cursor(Type type, string resource) {
158 using (Stream s = type.Assembly.GetManifestResourceStream (type, resource)) {
165 // Try a different way, previous failed
166 using (Stream s = Assembly.GetExecutingAssembly ().GetManifestResourceStream (resource)) {
172 throw new FileNotFoundException ("Resource name was not found: `" + resource + "'");
174 #endregion // Public Constructors
176 #region Public Static Properties
177 public static Rectangle Clip {
184 XplatUI.GrabInfo (out handle, out confined, out rect);
185 if (handle != IntPtr.Zero) {
189 XplatUI.GetDisplaySize (out size);
192 rect.Width = size.Width;
193 rect.Height = size.Height;
197 [MonoTODO ("Stub, does nothing")]
198 [MonoInternalNote ("First need to add ability to set cursor clip rectangle to XplatUI drivers to implement this property")]
204 public static Cursor Current {
208 return Cursors.Default;
212 if (current == value)
216 if (current == null){
217 // FIXME - define and set empty cursor
218 XplatUI.OverrideCursor(IntPtr.Zero);
220 XplatUI.OverrideCursor(current.handle);
224 public static Point Position {
229 XplatUI.GetCursorPos (IntPtr.Zero, out x, out y);
230 return new Point (x, y);
234 XplatUI.SetCursorPos(IntPtr.Zero, value.X, value.Y);
237 #endregion // Public Static Properties
239 #region Public Instance Properties
240 public IntPtr Handle {
246 [MonoTODO ("Implemented for Win32, X11 always returns 0,0")]
247 public Point HotSpot {
249 int cursor_w, cursor_h, hot_x, hot_y;
250 XplatUI.GetCursorInfo (Handle, out cursor_w, out cursor_h, out hot_x, out hot_y);
252 return new Point (hot_x, hot_y);
262 [Localizable (false)]
264 [TypeConverter (typeof (StringConverter))]
265 [DefaultValue (null)]
266 [MWFCategory ("Data")]
268 get { return this.tag; }
269 set { this.tag = value; }
272 #endregion // Public Instance Properties
274 #region Public Static Methods
275 public static void Hide ()
277 XplatUI.ShowCursor(false);
280 public static void Show ()
282 XplatUI.ShowCursor(true);
285 public static bool operator != (Cursor left, Cursor right) {
286 if ((object)left == (object)right)
289 if ((object)left == null || (object)right == null)
292 if (left.handle == right.handle)
298 public static bool operator ==(Cursor left, Cursor right)
300 if ((object)left == (object)right)
303 if ((object)left == null || (object)right == null)
306 if (left.handle == right.handle)
311 #endregion // Public Static Methods
313 #region Public Instance Methods
314 public IntPtr CopyHandle() {
318 public void Dispose ()
320 if (cursor != null) {
335 GC.SuppressFinalize (this);
338 public void Draw (Graphics g, Rectangle targetRect)
340 if (cursor == null && std_cursor != (StdCursor) (-1))
341 cursor = XplatUI.DefineStdCursorBitmap (std_cursor);
343 if (cursor != null) {
344 // Size of the targetRect is not considered at all
345 g.DrawImage (cursor, targetRect.X, targetRect.Y);
349 public void DrawStretched (Graphics g, Rectangle targetRect)
351 if (cursor == null && std_cursor != (StdCursor)(-1))
352 cursor = XplatUI.DefineStdCursorBitmap (std_cursor);
354 if (cursor != null) {
355 g.DrawImage (cursor, targetRect, new Rectangle(0, 0, cursor.Width, cursor.Height), GraphicsUnit.Pixel);
359 public override bool Equals (object obj)
361 if (!(obj is Cursor))
364 if (((Cursor)obj).handle == handle)
370 public override int GetHashCode()
372 return base.GetHashCode ();
375 public override string ToString()
378 return "[Cursor:" + name + "]";
381 throw new FormatException("Cannot convert custom cursors to string.");
384 void ISerializable.GetObjectData (SerializationInfo si, StreamingContext context)
390 ms = new MemoryStream ();
391 wr = new BinaryWriter (ms);
392 ci = cursor_data [this.id];
394 // Build the headers, first the CursorDir
395 wr.Write ((ushort) 0); // Reserved
396 wr.Write ((ushort) 2); // Resource type
397 wr.Write ((ushort) 1); // Count
399 // Next the CursorEntry
400 wr.Write ((byte)cursor_dir.idEntries [this.id].width);
401 wr.Write ((byte)cursor_dir.idEntries [this.id].height);
402 wr.Write ((byte)cursor_dir.idEntries [this.id].colorCount);
403 wr.Write ((byte)cursor_dir.idEntries [this.id].reserved);
404 wr.Write ((ushort)cursor_dir.idEntries [this.id].xHotspot);
405 wr.Write ((ushort)cursor_dir.idEntries [this.id].yHotspot);
406 wr.Write ((uint)(40 + (ci.cursorColors.Length * 4) + ci.cursorXOR.Length + ci.cursorAND.Length));
407 wr.Write ((uint)(6 + 16)); // CursorDir + CursorEntry size
409 // Then the CursorInfoHeader
410 wr.Write (ci.cursorHeader.biSize);
411 wr.Write (ci.cursorHeader.biWidth);
412 wr.Write (ci.cursorHeader.biHeight);
413 wr.Write (ci.cursorHeader.biPlanes);
414 wr.Write (ci.cursorHeader.biBitCount);
415 wr.Write (ci.cursorHeader.biCompression);
416 wr.Write (ci.cursorHeader.biSizeImage);
417 wr.Write (ci.cursorHeader.biXPelsPerMeter);
418 wr.Write (ci.cursorHeader.biYPelsPerMeter);
419 wr.Write (ci.cursorHeader.biClrUsed);
420 wr.Write (ci.cursorHeader.biClrImportant);
422 for (int i = 0; i < ci.cursorColors.Length; i++)
423 wr.Write(ci.cursorColors[i]);
425 wr.Write (ci.cursorXOR);
426 wr.Write (ci.cursorAND);
429 si.AddValue ("CursorData", ms.ToArray ());
431 #endregion // Public Instance Methods
433 #region Private Methods
434 private void InitFromStream (Stream stream)
440 //read the cursor header
441 if (stream == null || stream.Length == 0)
442 throw new ArgumentException ("The argument 'stream' must be a picture that can be used as a cursor", "stream");
444 BinaryReader reader = new BinaryReader (stream);
446 cursor_dir = new CursorDir ();
447 cursor_dir.idReserved = reader.ReadUInt16();
448 cursor_dir.idType = reader.ReadUInt16();
449 if (cursor_dir.idReserved != 0 || !(cursor_dir.idType == 2 || cursor_dir.idType == 1))
450 throw new ArgumentException ("Invalid Argument, format error", "stream");
452 entry_count = reader.ReadUInt16();
453 cursor_dir.idCount = entry_count;
454 cursor_dir.idEntries = new CursorEntry[entry_count];
455 cursor_data = new CursorImage[entry_count];
457 //now read in the CursorEntry structures
458 for (int i=0; i < entry_count; i++){
459 ce = new CursorEntry();
461 ce.width = reader.ReadByte();
462 ce.height = reader.ReadByte();
463 ce.colorCount = reader.ReadByte();
464 ce.reserved = reader.ReadByte();
465 ce.xHotspot = reader.ReadUInt16();
466 ce.yHotspot = reader.ReadUInt16();
467 if (cursor_dir.idType == 1) {
468 ce.xHotspot = (ushort)(ce.width / 2);
469 ce.yHotspot = (ushort)(ce.height / 2);
471 ce.sizeInBytes = reader.ReadUInt32();
472 ce.fileOffset = reader.ReadUInt32();
474 cursor_dir.idEntries[i] = ce;
477 // If we have more than one pick the largest cursor
479 for (int j = 0; j < entry_count; j++){
480 if (cursor_dir.idEntries[j].sizeInBytes >= largest) {
481 largest = cursor_dir.idEntries[j].sizeInBytes;
483 this.size.Height = cursor_dir.idEntries[j].height;
484 this.size.Width = cursor_dir.idEntries[j].width;
488 //now read in the cursor data
489 for (int j = 0; j < entry_count; j++) {
491 CursorInfoHeader cih;
493 BinaryReader cih_reader;
500 curdata = new CursorImage();
501 cih = new CursorInfoHeader();
503 stream.Seek (cursor_dir.idEntries[j].fileOffset, SeekOrigin.Begin);
504 buffer = new byte [cursor_dir.idEntries[j].sizeInBytes];
505 stream.Read (buffer, 0, buffer.Length);
507 cih_reader = new BinaryReader(new MemoryStream(buffer));
509 cih.biSize = cih_reader.ReadUInt32 ();
510 if (cih.biSize != 40) {
511 throw new ArgumentException ("Invalid cursor file", "stream");
513 cih.biWidth = cih_reader.ReadInt32 ();
514 cih.biHeight = cih_reader.ReadInt32 ();
515 cih.biPlanes = cih_reader.ReadUInt16 ();
516 cih.biBitCount = cih_reader.ReadUInt16 ();
517 cih.biCompression = cih_reader.ReadUInt32 ();
518 cih.biSizeImage = cih_reader.ReadUInt32 ();
519 cih.biXPelsPerMeter = cih_reader.ReadInt32 ();
520 cih.biYPelsPerMeter = cih_reader.ReadInt32 ();
521 cih.biClrUsed = cih_reader.ReadUInt32 ();
522 cih.biClrImportant = cih_reader.ReadUInt32 ();
524 curdata.cursorHeader = cih;
526 //Read the number of colors used and corresponding memory occupied by
527 //color table. Fill this memory chunk into rgbquad[]
528 switch (cih.biBitCount){
529 case 1: num_colors = 2; break;
530 case 4: num_colors = 16; break;
531 case 8: num_colors = 256; break;
532 default: num_colors = 0; break;
535 curdata.cursorColors = new uint[num_colors];
536 for (int i = 0; i < num_colors; i++) {
537 curdata.cursorColors[i] = cih_reader.ReadUInt32 ();
540 //XOR mask is immediately after ColorTable and its size is
541 //icon height* no. of bytes per line
543 //cursor height is half of BITMAPINFOHEADER.biHeight, since it contains
544 //both XOR as well as AND mask bytes
545 cursor_height = cih.biHeight/2;
547 //bytes per line should should be uint aligned
548 bytes_per_line = ((((cih.biWidth * cih.biPlanes * cih.biBitCount)+ 31)>>5)<<2);
550 //Determine the XOR array Size
551 xor_size = bytes_per_line * cursor_height;
552 curdata.cursorXOR = new byte[xor_size];
553 for (int i = 0; i < xor_size; i++) {
554 curdata.cursorXOR[i] = cih_reader.ReadByte();
557 //Determine the AND array size
558 and_size = (int)(cih_reader.BaseStream.Length - cih_reader.BaseStream.Position);
559 curdata.cursorAND = new byte[and_size];
560 for (int i = 0; i < and_size; i++) {
561 curdata.cursorAND[i] = cih_reader.ReadByte();
564 cursor_data[j] = curdata;
571 private Bitmap ToBitmap(bool xor, bool transparent)
574 CursorInfoHeader cih;
582 if (cursor_data == null)
583 return new Bitmap(32, 32);
585 ci = cursor_data[this.id];
586 cih = ci.cursorHeader;
587 biHeight = cih.biHeight / 2;
590 // The AND mask is 1bit - very straightforward
591 bmp = new Bitmap(cih.biWidth, biHeight, PixelFormat.Format1bppIndexed);
593 pal.Entries[0] = Color.FromArgb(0, 0, 0);
594 pal.Entries[1] = Color.FromArgb(unchecked((int)0xffffffffff));
595 bits = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
597 for (int y = 0; y < biHeight; y++) {
598 Marshal.Copy(ci.cursorAND, bits.Stride * y, (IntPtr)(bits.Scan0.ToInt64() + bits.Stride * (biHeight - 1 - y)), bits.Stride);
601 bmp.UnlockBits(bits);
603 ncolors = (int)cih.biClrUsed;
605 if (cih.biBitCount < 24) {
606 ncolors = (int)(1 << cih.biBitCount);
610 switch(cih.biBitCount) {
611 case 1: { // Monochrome
612 bmp = new Bitmap (cih.biWidth, biHeight, PixelFormat.Format1bppIndexed);
617 bmp = new Bitmap (cih.biWidth, biHeight, PixelFormat.Format4bppIndexed);
622 bmp = new Bitmap (cih.biWidth, biHeight, PixelFormat.Format8bppIndexed);
628 bmp = new Bitmap (cih.biWidth, biHeight, PixelFormat.Format32bppArgb);
633 throw new Exception("Unexpected number of bits:" + cih.biBitCount.ToString());
636 if (cih.biBitCount < 24) {
637 pal = bmp.Palette; // Managed palette
638 for (int i = 0; i < ci.cursorColors.Length; i++)
639 pal.Entries[i] = Color.FromArgb((int)ci.cursorColors[i] | unchecked((int)0xff000000));
643 bytesPerLine = (int)((((cih.biWidth * cih.biBitCount) + 31) & ~31) >> 3);
644 bits = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
646 for (int y = 0; y < biHeight; y++)
647 Marshal.Copy(ci.cursorXOR, bytesPerLine * y, (IntPtr)(bits.Scan0.ToInt64() + bits.Stride * (biHeight - 1 - y)), bytesPerLine);
649 bmp.UnlockBits(bits);
653 bmp = new Bitmap(bmp); // This makes a 32bpp image out of an indexed one
654 // Apply the mask to make properly transparent
655 for (int y = 0; y < biHeight; y++) {
656 for (int x = 0; x < cih.biWidth / 8; x++) {
657 for (int bit = 7; bit >= 0; bit--) {
658 if (((ci.cursorAND[y * cih.biWidth / 8 +x] >> bit) & 1) != 0)
659 bmp.SetPixel(x*8 + 7-bit, biHeight - y - 1, Color.Transparent);
667 #endregion // Private Methods