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
31 using System
.Drawing
.Imaging
;
33 using System
.ComponentModel
;
34 using System
.Runtime
.InteropServices
;
35 using System
.Runtime
.Serialization
;
36 using System
.Reflection
;
38 namespace System
.Windows
.Forms
{
39 [Editor("System.Drawing.Design.CursorEditor, " + Consts
.AssemblySystem_Drawing_Design
, typeof(System
.Drawing
.Design
.UITypeEditor
))]
41 [TypeConverter(typeof(CursorConverter
))]
42 public sealed class Cursor
: IDisposable
, ISerializable
{
43 #region Internal Structs
44 [StructLayout(LayoutKind
.Sequential
)]
45 private struct CursorDir
{
46 internal ushort idReserved
; // Reserved
47 internal ushort idType
; // resource type (2 for cursors)
48 internal ushort idCount
; // how many cursors
49 internal CursorEntry
[] idEntries
; // the entries for each cursor
52 [StructLayout(LayoutKind
.Sequential
)]
53 private struct CursorEntry
{
54 internal byte width
; // Width of cursor
55 internal byte height
; // Height of cursor
56 internal byte colorCount
; // colors in cursor
57 internal byte reserved
; // Reserved
58 internal ushort xHotspot
; // Hotspot X
59 internal ushort yHotspot
; // Hotspot Y
60 internal ushort bitCount
; // Bits per pixel
61 internal uint sizeInBytes
; // size of (CursorInfoHeader + ANDBitmap + ORBitmap)
62 internal uint fileOffset
; // position in file
65 [StructLayout(LayoutKind
.Sequential
)]
66 private struct CursorInfoHeader
{
69 internal int biHeight
;
70 internal ushort biPlanes
;
71 internal ushort biBitCount
;
72 internal uint biCompression
;
73 internal uint biSizeImage
;
74 internal int biXPelsPerMeter
;
75 internal int biYPelsPerMeter
;
76 internal uint biClrUsed
;
77 internal uint biClrImportant
;
80 [StructLayout(LayoutKind
.Sequential
)]
81 private struct CursorImage
{
82 internal CursorInfoHeader cursorHeader
; // image header
83 internal uint[] cursorColors
; // colors table
84 internal byte[] cursorXOR
; // bits for XOR mask
85 internal byte[] cursorAND
; // bits for AND mask
87 #endregion // Internal structs
89 #region Local Variables
90 private static Cursor current
;
91 private CursorDir cursor_dir
;
92 private CursorImage
[] cursor_data
;
95 internal IntPtr handle
;
99 private Bitmap cursor
;
100 internal string name
;
101 #endregion // Local Variables
103 #region Public Constructors
104 private void CreateCursor(System
.IO
.Stream stream
) {
105 InitFromStream(stream
);
106 this.shape
= ToBitmap(true, false);
107 this.mask
= ToBitmap(false, false);
108 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
);
109 this.shape
.Dispose();
114 if (handle
!= IntPtr
.Zero
) {
115 this.cursor
= ToBitmap(true, true);
119 private Cursor(SerializationInfo info
, StreamingContext context
) {
129 // This is supposed to take a Win32 handle
130 public Cursor(IntPtr handle
) {
131 this.handle
= handle
;
134 public Cursor(System
.IO
.Stream stream
) {
135 CreateCursor(stream
);
138 public Cursor(string fileName
) : this (new FileStream (fileName
, FileMode
.Open
)) {
141 public Cursor(Type type
, string resource
) {
142 using (Stream s
= type
.Assembly
.GetManifestResourceStream (type
, resource
)) {
149 // Try a different way, previous failed
150 using (Stream s
= Assembly
.GetExecutingAssembly().GetManifestResourceStream(resource
)) {
156 throw new FileNotFoundException ("Resource name was not found: `" + resource
+ "'");
158 #endregion // Public Constructors
160 #region Public Static Properties
161 public static Rectangle Clip
{
168 XplatUI
.GrabInfo(out handle
, out confined
, out rect
);
169 if (handle
!= IntPtr
.Zero
) {
173 XplatUI
.GetDisplaySize(out size
);
176 rect
.Width
= size
.Width
;
177 rect
.Height
= size
.Height
;
181 [MonoTODO("First need to add ability to set cursor clip rectangle to XplatUI drivers to implement this property")]
187 [MonoTODO("Implement setting a null cursor, and add XplatUI method to get current cursor")]
188 public static Cursor Current
{
190 if (current
!= null) {
193 return Cursors
.Default
;
197 if (current
!= value) {
199 if (current
== null){
200 // FIXME - define and set empty cursor
201 XplatUI
.OverrideCursor(IntPtr
.Zero
);
203 XplatUI
.OverrideCursor(current
.handle
);
208 public static Point Position
{
213 XplatUI
.GetCursorPos (IntPtr
.Zero
, out x
, out y
);
214 return new Point (x
, y
);
218 XplatUI
.SetCursorPos(IntPtr
.Zero
, value.X
, value.Y
);
221 #endregion // Public Static Properties
223 #region Public Instance Properties
224 public IntPtr Handle
{
235 #endregion // Public Instance Properties
237 #region Public Static Methods
238 public static void Hide() {
239 XplatUI
.ShowCursor(false);
242 public static void Show() {
243 XplatUI
.ShowCursor(false);
246 public static bool operator !=(Cursor left
, Cursor right
) {
247 if ((object)left
== (object)right
) {
251 if ((object)left
== null || (object)right
== null) {
255 if (left
.handle
== right
.handle
) {
262 public static bool operator ==(Cursor left
, Cursor right
) {
263 if ((object)left
== (object)right
) {
267 if ((object)left
== null || (object)right
== null) {
271 if (left
.handle
== right
.handle
) {
276 #endregion // Public Static Methods
278 #region Public Instance Methods
279 public IntPtr
CopyHandle() {
283 public void Dispose() {
284 if (this.cursor
!= null) {
285 this.cursor
.Dispose();
289 if (this.shape
!= null) {
290 this.shape
.Dispose();
294 if (this.mask
!= null) {
299 GC
.SuppressFinalize (this);
302 public void Draw(Graphics g
, Rectangle targetRect
) {
303 if (this.cursor
!= null) {
304 g
.DrawImage(this.cursor
, targetRect
);
308 public void DrawStretched(Graphics g
, Rectangle targetRect
) {
309 if (this.cursor
!= null) {
310 g
.DrawImage(this.cursor
, targetRect
, new Rectangle(0, 0, this.cursor
.Width
, this.cursor
.Height
), GraphicsUnit
.Pixel
);
314 public override bool Equals(object obj
) {
315 if ( !(obj
is Cursor
)) {
319 if (((Cursor
)obj
).handle
== this.handle
) {
326 public override int GetHashCode() {
327 return base.GetHashCode ();
330 public override string ToString() {
332 return "[Cursor:" + name
+ "]";
335 throw new FormatException("Cannot convert custom cursors to string.");
338 void ISerializable
.GetObjectData(SerializationInfo si
, StreamingContext context
) {
343 ms
= new MemoryStream();
344 wr
= new BinaryWriter(ms
);
345 ci
= cursor_data
[this.id
];
347 // Build the headers, first the CursorDir
348 wr
.Write((ushort)0); // Reserved
349 wr
.Write((ushort)2); // Resource type
350 wr
.Write((ushort)1); // Count
352 // Next the CursorEntry
353 wr
.Write((byte)cursor_dir
.idEntries
[this.id
].width
);
354 wr
.Write((byte)cursor_dir
.idEntries
[this.id
].height
);
355 wr
.Write((byte)cursor_dir
.idEntries
[this.id
].colorCount
);
356 wr
.Write((byte)cursor_dir
.idEntries
[this.id
].reserved
);
357 wr
.Write((ushort)cursor_dir
.idEntries
[this.id
].xHotspot
);
358 wr
.Write((ushort)cursor_dir
.idEntries
[this.id
].yHotspot
);
359 wr
.Write((uint)(40 + (ci
.cursorColors
.Length
* 4) + ci
.cursorXOR
.Length
+ ci
.cursorAND
.Length
));
360 wr
.Write((uint)(6 + 16)); // CursorDir + CursorEntry size
362 // Then the CursorInfoHeader
363 wr
.Write(ci
.cursorHeader
.biSize
);
364 wr
.Write(ci
.cursorHeader
.biWidth
);
365 wr
.Write(ci
.cursorHeader
.biHeight
);
366 wr
.Write(ci
.cursorHeader
.biPlanes
);
367 wr
.Write(ci
.cursorHeader
.biBitCount
);
368 wr
.Write(ci
.cursorHeader
.biCompression
);
369 wr
.Write(ci
.cursorHeader
.biSizeImage
);
370 wr
.Write(ci
.cursorHeader
.biXPelsPerMeter
);
371 wr
.Write(ci
.cursorHeader
.biYPelsPerMeter
);
372 wr
.Write(ci
.cursorHeader
.biClrUsed
);
373 wr
.Write(ci
.cursorHeader
.biClrImportant
);
374 for (int i
= 0; i
< ci
.cursorColors
.Length
; i
++) {
375 wr
.Write(ci
.cursorColors
[i
]);
377 wr
.Write(ci
.cursorXOR
);
378 wr
.Write(ci
.cursorAND
);
381 si
.AddValue ("CursorData", ms
.ToArray());
383 #endregion // Public Instance Methods
385 #region Private Methods w
386 private void InitFromStream(Stream stream
) {
391 //read the cursor header
392 if (stream
== null || stream
.Length
== 0) {
393 throw new System
.ArgumentException ("The argument 'stream' must be a picture that can be used as a cursor", "stream");
396 BinaryReader reader
= new BinaryReader (stream
);
398 cursor_dir
= new CursorDir ();
399 cursor_dir
.idReserved
= reader
.ReadUInt16();
400 cursor_dir
.idType
= reader
.ReadUInt16();
401 if (cursor_dir
.idReserved
!= 0 || !(cursor_dir
.idType
== 2 || cursor_dir
.idType
== 1))
402 throw new System
.ArgumentException ("Invalid Argument, format error", "stream");
404 entry_count
= reader
.ReadUInt16();
405 cursor_dir
.idCount
= entry_count
;
406 cursor_dir
.idEntries
= new CursorEntry
[entry_count
];
407 cursor_data
= new CursorImage
[entry_count
];
409 //now read in the CursorEntry structures
410 for (int i
=0; i
< entry_count
; i
++){
411 ce
= new CursorEntry();
413 ce
.width
= reader
.ReadByte();
414 ce
.height
= reader
.ReadByte();
415 ce
.colorCount
= reader
.ReadByte();
416 ce
.reserved
= reader
.ReadByte();
417 ce
.xHotspot
= reader
.ReadUInt16();
418 ce
.yHotspot
= reader
.ReadUInt16();
419 if (cursor_dir
.idType
== 1) {
420 ce
.xHotspot
= (ushort)(ce
.width
/ 2);
421 ce
.yHotspot
= (ushort)(ce
.height
/ 2);
423 ce
.sizeInBytes
= reader
.ReadUInt32();
424 ce
.fileOffset
= reader
.ReadUInt32();
426 cursor_dir
.idEntries
[i
] = ce
;
429 // If we have more than one pick the largest cursor
431 for (int j
=0; j
< entry_count
; j
++){
432 if (cursor_dir
.idEntries
[j
].sizeInBytes
>= largest
) {
433 largest
= cursor_dir
.idEntries
[j
].sizeInBytes
;
435 this.size
.Height
= cursor_dir
.idEntries
[j
].height
;
436 this.size
.Width
= cursor_dir
.idEntries
[j
].width
;
440 //now read in the cursor data
441 for (int j
= 0; j
< entry_count
; j
++) {
443 CursorInfoHeader cih
;
445 BinaryReader cih_reader
;
452 curdata
= new CursorImage();
453 cih
= new CursorInfoHeader();
455 stream
.Seek (cursor_dir
.idEntries
[j
].fileOffset
, SeekOrigin
.Begin
);
456 buffer
= new byte [cursor_dir
.idEntries
[j
].sizeInBytes
];
457 stream
.Read (buffer
, 0, buffer
.Length
);
459 cih_reader
= new BinaryReader(new MemoryStream(buffer
));
461 cih
.biSize
= cih_reader
.ReadUInt32 ();
462 if (cih
.biSize
!= 40) {
463 throw new System
.ArgumentException ("Invalid cursor file", "stream");
465 cih
.biWidth
= cih_reader
.ReadInt32 ();
466 cih
.biHeight
= cih_reader
.ReadInt32 ();
467 cih
.biPlanes
= cih_reader
.ReadUInt16 ();
468 cih
.biBitCount
= cih_reader
.ReadUInt16 ();
469 cih
.biCompression
= cih_reader
.ReadUInt32 ();
470 cih
.biSizeImage
= cih_reader
.ReadUInt32 ();
471 cih
.biXPelsPerMeter
= cih_reader
.ReadInt32 ();
472 cih
.biYPelsPerMeter
= cih_reader
.ReadInt32 ();
473 cih
.biClrUsed
= cih_reader
.ReadUInt32 ();
474 cih
.biClrImportant
= cih_reader
.ReadUInt32 ();
476 curdata
.cursorHeader
= cih
;
478 //Read the number of colors used and corresponding memory occupied by
479 //color table. Fill this memory chunk into rgbquad[]
480 switch (cih
.biBitCount
){
481 case 1: num_colors
= 2; break;
482 case 4: num_colors
= 16; break;
483 case 8: num_colors
= 256; break;
484 default: num_colors
= 0; break;
487 curdata
.cursorColors
= new uint[num_colors
];
488 for (int i
= 0; i
< num_colors
; i
++) {
489 curdata
.cursorColors
[i
] = cih_reader
.ReadUInt32 ();
492 //XOR mask is immediately after ColorTable and its size is
493 //icon height* no. of bytes per line
495 //cursor height is half of BITMAPINFOHEADER.biHeight, since it contains
496 //both XOR as well as AND mask bytes
497 cursor_height
= cih
.biHeight
/2;
499 //bytes per line should should be uint aligned
500 bytes_per_line
= ((((cih
.biWidth
* cih
.biPlanes
* cih
.biBitCount
)+ 31)>>5)<<2);
502 //Determine the XOR array Size
503 xor_size
= bytes_per_line
* cursor_height
;
504 curdata
.cursorXOR
= new byte[xor_size
];
505 for (int i
= 0; i
< xor_size
; i
++) {
506 curdata
.cursorXOR
[i
] = cih_reader
.ReadByte();
509 //Determine the AND array size
510 and_size
= (int)(cih_reader
.BaseStream
.Length
- cih_reader
.BaseStream
.Position
);
511 curdata
.cursorAND
= new byte[and_size
];
512 for (int i
= 0; i
< and_size
; i
++) {
513 curdata
.cursorAND
[i
] = cih_reader
.ReadByte();
516 cursor_data
[j
] = curdata
;
523 private Bitmap
ToBitmap(bool xor
, bool transparent
) {
525 CursorInfoHeader cih
;
533 if (cursor_data
== null) {
534 return new Bitmap(32, 32);
537 ci
= cursor_data
[this.id
];
538 cih
= ci
.cursorHeader
;
539 biHeight
= cih
.biHeight
/ 2;
542 // The AND mask is 1bit - very straightforward
543 bmp
= new Bitmap(cih
.biWidth
, biHeight
, PixelFormat
.Format1bppIndexed
);
545 pal
.Entries
[0] = Color
.FromArgb(0, 0, 0);
546 pal
.Entries
[1] = Color
.FromArgb(unchecked((int)0xffffffffff));
547 bits
= bmp
.LockBits(new Rectangle(0, 0, bmp
.Width
, bmp
.Height
), ImageLockMode
.WriteOnly
, bmp
.PixelFormat
);
549 for (int y
= 0; y
< biHeight
; y
++) {
550 Marshal
.Copy(ci
.cursorAND
, bits
.Stride
* y
, (IntPtr
)(bits
.Scan0
.ToInt64() + bits
.Stride
* (biHeight
- 1 - y
)), bits
.Stride
);
553 bmp
.UnlockBits(bits
);
555 ncolors
= (int)cih
.biClrUsed
;
557 if (cih
.biBitCount
< 24) {
558 ncolors
= (int)(1 << cih
.biBitCount
);
562 switch(cih
.biBitCount
) {
563 case 1: { // Monochrome
564 bmp
= new Bitmap(cih
.biWidth
, biHeight
, PixelFormat
.Format1bppIndexed
);
569 bmp
= new Bitmap(cih
.biWidth
, biHeight
, PixelFormat
.Format4bppIndexed
);
574 bmp
= new Bitmap(cih
.biWidth
, biHeight
, PixelFormat
.Format8bppIndexed
);
580 bmp
= new Bitmap(cih
.biWidth
, biHeight
, PixelFormat
.Format32bppArgb
);
585 throw new Exception("Unexpected number of bits:" + cih
.biBitCount
.ToString());
589 if (cih
.biBitCount
< 24) {
590 pal
= bmp
.Palette
; // Managed palette
592 for (int i
= 0; i
< ci
.cursorColors
.Length
; i
++) {
593 pal
.Entries
[i
] = Color
.FromArgb((int)ci
.cursorColors
[i
] | unchecked((int)0xff000000));
598 bytesPerLine
= (int)((((cih
.biWidth
* cih
.biBitCount
) + 31) & ~
31) >> 3);
599 bits
= bmp
.LockBits(new Rectangle(0, 0, bmp
.Width
, bmp
.Height
), ImageLockMode
.WriteOnly
, bmp
.PixelFormat
);
601 for (int y
= 0; y
< biHeight
; y
++) {
602 Marshal
.Copy(ci
.cursorXOR
, bytesPerLine
* y
, (IntPtr
)(bits
.Scan0
.ToInt64() + bits
.Stride
* (biHeight
- 1 - y
)), bytesPerLine
);
605 bmp
.UnlockBits(bits
);
609 bmp
= new Bitmap(bmp
); // This makes a 32bpp image out of an indexed one
610 // Apply the mask to make properly transparent
611 for (int y
= 0; y
< biHeight
; y
++) {
612 for (int x
= 0; x
< cih
.biWidth
/ 8; x
++) {
613 for (int bit
= 7; bit
>= 0; bit
--) {
614 if (((ci
.cursorAND
[y
* cih
.biWidth
/ 8 +x
] >> bit
) & 1) != 0) {
615 bmp
.SetPixel(x
*8 + 7-bit
, biHeight
- y
- 1, Color
.Transparent
);
624 #endregion // Private Methods