* TextBoxBase.cs: Take HideSelection into account when
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / Cursor.cs
blob11428b77521cb641c3feb7767597d500ac6ac158
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.
20 // Copyright (c) 2004 Novell, Inc.
22 // Authors:
23 // Peter Bartok pbartok@novell.com
27 // NOT COMPLETE
29 using System;
30 using System.Drawing;
31 using System.Drawing.Imaging;
32 using System.IO;
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))]
40 [Serializable]
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
63 };
65 [StructLayout(LayoutKind.Sequential)]
66 private struct CursorInfoHeader {
67 internal uint biSize;
68 internal int biWidth;
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;
93 private int id;
95 internal IntPtr handle;
96 private Size size;
97 private Bitmap shape;
98 private Bitmap mask;
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();
110 this.shape = null;
111 this.mask.Dispose();
112 this.mask = null;
114 if (handle != IntPtr.Zero) {
115 this.cursor = ToBitmap(true, true);
119 private Cursor(SerializationInfo info, StreamingContext context) {
122 private Cursor() {
125 ~Cursor() {
126 Dispose();
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)) {
143 if (s != null) {
144 CreateCursor(s);
145 return;
149 // Try a different way, previous failed
150 using (Stream s = Assembly.GetExecutingAssembly().GetManifestResourceStream(resource)) {
151 if (s != null) {
152 CreateCursor(s);
153 return;
156 throw new FileNotFoundException ("Resource name was not found: `" + resource + "'");
158 #endregion // Public Constructors
160 #region Public Static Properties
161 public static Rectangle Clip {
162 get {
163 IntPtr handle;
164 bool confined;
165 Rectangle rect;
166 Size size;
168 XplatUI.GrabInfo(out handle, out confined, out rect);
169 if (handle != IntPtr.Zero) {
170 return rect;
173 XplatUI.GetDisplaySize(out size);
174 rect.X = 0;
175 rect.Y = 0;
176 rect.Width = size.Width;
177 rect.Height = size.Height;
178 return rect;
181 [MonoTODO("First need to add ability to set cursor clip rectangle to XplatUI drivers to implement this property")]
182 set {
187 [MonoTODO("Implement setting a null cursor, and add XplatUI method to get current cursor")]
188 public static Cursor Current {
189 get {
190 if (current != null) {
191 return current;
193 return Cursors.Default;
196 set {
197 if (current != value) {
198 current = value;
199 if (current == null){
200 // FIXME - define and set empty cursor
201 XplatUI.OverrideCursor(IntPtr.Zero);
202 } else
203 XplatUI.OverrideCursor(current.handle);
208 public static Point Position {
209 get {
210 int x;
211 int y;
213 XplatUI.GetCursorPos (IntPtr.Zero, out x, out y);
214 return new Point (x, y);
217 set {
218 XplatUI.SetCursorPos(IntPtr.Zero, value.X, value.Y);
221 #endregion // Public Static Properties
223 #region Public Instance Properties
224 public IntPtr Handle {
225 get {
226 return handle;
230 public Size Size {
231 get {
232 return size;
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) {
248 return false;
251 if ((object)left == null || (object)right == null) {
252 return true;
255 if (left.handle == right.handle) {
256 return false;
258 return true;
262 public static bool operator ==(Cursor left, Cursor right) {
263 if ((object)left == (object)right) {
264 return true;
267 if ((object)left == null || (object)right == null) {
268 return false;
271 if (left.handle == right.handle) {
272 return true;
274 return false;
276 #endregion // Public Static Methods
278 #region Public Instance Methods
279 public IntPtr CopyHandle() {
280 return handle;
283 public void Dispose() {
284 if (this.cursor != null) {
285 this.cursor.Dispose();
286 this.cursor = null;
289 if (this.shape != null) {
290 this.shape.Dispose();
291 this.shape = null;
294 if (this.mask != null) {
295 this.mask.Dispose();
296 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)) {
316 return false;
319 if (((Cursor)obj).handle == this.handle) {
320 return true;
323 return false;
326 public override int GetHashCode() {
327 return base.GetHashCode ();
330 public override string ToString() {
331 if (name != null) {
332 return "[Cursor:" + name + "]";
335 throw new FormatException("Cannot convert custom cursors to string.");
338 void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context) {
339 MemoryStream ms;
340 BinaryWriter wr;
341 CursorImage ci;
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);
379 wr.Flush();
381 si.AddValue ("CursorData", ms.ToArray());
383 #endregion // Public Instance Methods
385 #region Private Methods w
386 private void InitFromStream(Stream stream) {
387 ushort entry_count;
388 CursorEntry ce;
389 uint largest;
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
430 largest = 0;
431 for (int j=0; j < entry_count; j++){
432 if (cursor_dir.idEntries[j].sizeInBytes >= largest) {
433 largest = cursor_dir.idEntries[j].sizeInBytes;
434 this.id = (ushort)j;
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++) {
442 CursorImage curdata;
443 CursorInfoHeader cih;
444 byte[] buffer;
445 BinaryReader cih_reader;
446 int num_colors;
447 int cursor_height;
448 int bytes_per_line;
449 int xor_size;
450 int and_size;
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;
517 cih_reader.Close();
520 reader.Close();
523 private Bitmap ToBitmap(bool xor, bool transparent) {
524 CursorImage ci;
525 CursorInfoHeader cih;
526 int ncolors;
527 Bitmap bmp;
528 BitmapData bits;
529 ColorPalette pal;
530 int biHeight;
531 int bytesPerLine;
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;
541 if (!xor) {
542 // The AND mask is 1bit - very straightforward
543 bmp = new Bitmap(cih.biWidth, biHeight, PixelFormat.Format1bppIndexed);
544 pal = bmp.Palette;
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);
554 } else {
555 ncolors = (int)cih.biClrUsed;
556 if (ncolors == 0) {
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);
565 break;
568 case 4: { // 4bpp
569 bmp = new Bitmap(cih.biWidth, biHeight, PixelFormat.Format4bppIndexed);
570 break;
573 case 8: { // 8bpp
574 bmp = new Bitmap(cih.biWidth, biHeight, PixelFormat.Format8bppIndexed);
575 break;
578 case 24:
579 case 32: { // 32bpp
580 bmp = new Bitmap(cih.biWidth, biHeight, PixelFormat.Format32bppArgb);
581 break;
584 default: {
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));
595 bmp.Palette = pal;
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);
608 if (transparent) {
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);
622 return bmp;
624 #endregion // Private Methods