2007-02-18 Marek Sieradzki <marek.sieradzki@gmail.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms.X11Internal / X11ThreadQueue.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-2006 Novell, Inc.
21 //
22 // Authors:
23 //  Jackson Harper (jackson@ximian.com)
24 //  Peter Dennis Bartok (pbartok@novell.com)
25 //  Chris Toshok (toshok@ximian.com)
26 //
27
28 using System;
29 using System.Threading;
30 using System.Collections;
31
32 namespace System.Windows.Forms.X11Internal {
33
34         internal class X11ThreadQueue {
35
36                 XEventQueue xqueue;
37                 PaintQueue paint_queue;
38                 ConfigureQueue configure_queue;
39                 ArrayList timer_list;
40                 Thread thread;
41                 bool quit_posted;
42                 bool dispatch_idle;
43                 bool need_dispatch_idle = true;
44                 object lockobj = new object ();
45
46                 static readonly int InitialXEventQueueSize = 128;
47                 static readonly int InitialHwndQueueSize = 50;
48
49                 public X11ThreadQueue (Thread thread)
50                 {
51                         xqueue = new XEventQueue (InitialXEventQueueSize);
52                         paint_queue = new PaintQueue (InitialHwndQueueSize);
53                         configure_queue = new ConfigureQueue (InitialHwndQueueSize);
54                         timer_list = new ArrayList ();
55                         this.thread = thread;
56                         this.quit_posted = false;
57                         this.dispatch_idle = true;
58                 }
59
60                 public int CountUnlocked {
61                         get { return xqueue.Count + paint_queue.Count; }
62                 }
63
64                 public Thread Thread {
65                         get { return thread; }
66                 }
67
68                 public void EnqueueUnlocked (XEvent xevent)
69                 {
70                         switch (xevent.type) {
71                         case XEventName.KeyPress:
72                         case XEventName.KeyRelease:
73                         case XEventName.ButtonPress:
74                         case XEventName.ButtonRelease:
75                                 NeedDispatchIdle = true;
76                                 break;
77                         case XEventName.MotionNotify:
78                                 if (xqueue.Count > 0) {
79                                         XEvent peek = xqueue.Peek ();
80                                         if (peek.AnyEvent.type == XEventName.MotionNotify)
81                                                 return; // we've already got a pending motion notify.
82                                 }
83
84                                 // otherwise fall through and enqueue
85                                 // the event.
86                                 break;
87                         }
88
89                         xqueue.Enqueue (xevent);
90                         // wake up any thread blocking in DequeueUnlocked
91                         Monitor.PulseAll (lockobj);
92                 }
93
94                 public void Enqueue (XEvent xevent)
95                 {
96                         lock (lockobj) {
97                                 EnqueueUnlocked (xevent);
98                         }
99                 }
100
101                 public bool Dequeue (out XEvent xevent)
102                 {
103                 StartOver:
104                         bool got_xevent = false;
105
106                         lock (lockobj) {
107                                 if (xqueue.Count > 0) {
108                                         got_xevent = true;
109                                         xevent = xqueue.Dequeue ();
110                                 }
111                                 else
112                                         xevent = new XEvent (); /* not strictly needed, but mcs complains */
113                         }
114
115                         if (got_xevent) {
116                                 if (xevent.AnyEvent.type == XEventName.Expose) {
117 #if spew
118                                         Console.Write ("E");
119                                         Console.Out.Flush ();
120 #endif
121                                         X11Hwnd hwnd = (X11Hwnd)Hwnd.GetObjectFromWindow (xevent.AnyEvent.window);
122                                         hwnd.AddExpose (xevent.AnyEvent.window == hwnd.ClientWindow,
123                                                         xevent.ExposeEvent.x, xevent.ExposeEvent.y,
124                                                         xevent.ExposeEvent.width, xevent.ExposeEvent.height);
125                                         goto StartOver;
126                                 }
127                                 else if (xevent.AnyEvent.type == XEventName.ConfigureNotify) {
128 #if spew
129                                         Console.Write ("C");
130                                         Console.Out.Flush ();
131 #endif
132                                         X11Hwnd hwnd = (X11Hwnd)Hwnd.GetObjectFromWindow (xevent.AnyEvent.window);
133                                         hwnd.AddConfigureNotify (xevent);
134                                         goto StartOver;
135                                 }
136                                 else {
137 #if spew
138                                         Console.Write ("X");
139                                         Console.Out.Flush ();
140 #endif
141                                         /* it was an event we can deal with directly, return it */
142                                         return true;
143                                 }
144                         }
145                         else {
146                                 if (paint_queue.Count > 0) {
147                                         xevent = paint_queue.Dequeue ();
148 #if spew
149                                         Console.Write ("e");
150                                         Console.Out.Flush ();
151 #endif
152                                         return true;
153                                 }
154                                 else if (configure_queue.Count > 0) {
155                                         xevent = configure_queue.Dequeue ();
156 #if spew
157                                         Console.Write ("c");
158                                         Console.Out.Flush ();
159 #endif
160                                         return true;
161                                 }
162                         }
163
164                         if (dispatch_idle && need_dispatch_idle) {
165                                 OnIdle (EventArgs.Empty);
166                                 need_dispatch_idle = false;
167                         }
168
169                         lock (lockobj) {
170                                 if (CountUnlocked > 0)
171                                         goto StartOver;
172
173                                 if (Monitor.Wait (lockobj, NextTimeout (), true)) {
174                                         // the lock was reaquired before the
175                                         // timeout.  meaning an event was
176                                         // enqueued by X11Display.XEventThread.
177                                         goto StartOver;
178                                 }
179                                 else {
180                                         CheckTimers ();
181                                         return false;
182                                 }
183                         }
184                 }
185
186                 public void RemovePaint (Hwnd hwnd)
187                 {
188                         paint_queue.Remove (hwnd);
189                 }
190
191                 public void AddPaint (Hwnd hwnd)
192                 {
193                         paint_queue.Enqueue (hwnd);
194                 }
195
196                 public void AddConfigure (Hwnd hwnd)
197                 {
198                         configure_queue.Enqueue (hwnd);
199                 }
200
201                 public ConfigureQueue Configure {
202                         get { return configure_queue; }
203                 }
204
205                 public PaintQueue Paint {
206                         get { return paint_queue; }
207                 }
208
209                 public void Lock ()
210                 {
211                         Monitor.Enter (lockobj);
212                 }
213
214                 public void Unlock ()
215                 {
216                         Monitor.Exit (lockobj);
217                 }
218
219                 private int NextTimeout ()
220                 {
221                         int timeout = Int32.MaxValue; 
222                         DateTime now = DateTime.UtcNow;
223
224                         foreach (Timer timer in timer_list) {
225                                 int next = (int) (timer.Expires - now).TotalMilliseconds;
226                                 if (next < 0)
227                                         return 0; // Have a timer that has already expired
228
229                                 if (next < timeout)
230                                         timeout = next;
231                         }
232
233                         if (timeout < Timer.Minimum) {
234                                 timeout = Timer.Minimum;
235                         }
236
237                         if (timeout == Int32.MaxValue)
238                                 timeout = Timeout.Infinite;
239
240                         return timeout;
241                 }
242
243                 public void CheckTimers ()
244                 {
245                         int count;
246                         DateTime now = DateTime.UtcNow;
247
248                         count = timer_list.Count;
249
250                         if (count == 0)
251                                 return;
252
253                         for (int i = 0; i < timer_list.Count; i++) {
254                                 Timer timer;
255
256                                 timer = (Timer) timer_list [i];
257
258                                 if (timer.Enabled && timer.Expires <= now) {
259                                         timer.Update (now);
260                                         timer.FireTick ();
261                                 }
262                         }
263                 }
264
265                 public void SetTimer (Timer timer)
266                 {
267                         lock (lockobj) {
268                                 timer_list.Add (timer);
269
270                                 // we need to wake up any thread waiting in DequeueUnlocked,
271                                 // since it might need to wait for a different amount of time.
272                                 Monitor.PulseAll (lockobj);
273                         }
274
275                 }
276
277                 public void KillTimer (Timer timer)
278                 {
279                         lock (lockobj) {
280                                 timer_list.Remove (timer);
281
282                                 // we need to wake up any thread waiting in DequeueUnlocked,
283                                 // since it might need to wait for a different amount of time.
284                                 Monitor.PulseAll (lockobj);
285                         }
286                 }
287
288                 public event EventHandler Idle;
289                 public void OnIdle (EventArgs e)
290                 {
291                         if (Idle != null)
292                                 Idle (thread, e);
293                 }
294
295                 public bool NeedDispatchIdle {
296                         get { return need_dispatch_idle; }
297                         set { need_dispatch_idle = value; }
298                 }
299
300                 public bool DispatchIdle {
301                         get { return dispatch_idle; }
302                         set { dispatch_idle = value; }
303                 }
304
305                 public bool PostQuitState {
306                         get { return quit_posted; }
307                         set { quit_posted = value; }
308                 }
309
310                 public abstract class HwndEventQueue {
311                         protected ArrayList hwnds;
312 #if DebugHwndEventQueue
313                         protected ArrayList stacks;
314 #endif
315                         public HwndEventQueue (int size)
316                         {
317                                 hwnds = new ArrayList (size);
318 #if DebugHwndEventQueue
319                                 stacks = new ArrayList (size);
320 #endif
321                         }
322
323                         public int Count {
324                                 get { return hwnds.Count; }
325                         }
326
327                         public void Enqueue (Hwnd hwnd)
328                         {
329                                 if (hwnds.Contains (hwnd)) {
330 #if DebugHwndEventQueue
331                                         Console.WriteLine ("hwnds can only appear in the queue once.");
332                                         Console.WriteLine (Environment.StackTrace);
333                                         Console.WriteLine ("originally added here:");
334                                         Console.WriteLine (stacks[hwnds.IndexOf (hwnd)]);
335 #endif
336
337                                         return;
338                                 }
339                                 hwnds.Add(hwnd);
340 #if DebugHwndEventQueue
341                                 stacks.Add(Environment.StackTrace);
342 #endif
343                         }
344
345                         public void Remove(Hwnd hwnd)
346                         {
347 #if DebugHwndEventQueue
348                                 int index = hwnds.IndexOf(hwnd);
349                                 if (index != -1)
350                                         stacks.RemoveAt(index);
351 #endif
352                                 hwnds.Remove(hwnd);
353                         }
354
355                         protected abstract XEvent Peek ();
356
357                         public virtual XEvent Dequeue ()
358                         {
359                                 if (hwnds.Count == 0)
360                                         throw new Exception ("Attempt to dequeue empty queue.");
361
362                                 return Peek ();
363                         }
364                 }
365
366
367                 public class ConfigureQueue : HwndEventQueue
368                 {
369                         public ConfigureQueue (int size) : base (size)
370                         {
371                         }
372
373                         protected override XEvent Peek ()
374                         {
375                                 X11Hwnd hwnd = (X11Hwnd)hwnds[0];
376
377                                 XEvent xevent = new XEvent ();
378                                 xevent.AnyEvent.type = XEventName.ConfigureNotify;
379
380                                 xevent.ConfigureEvent.window = hwnd.ClientWindow;
381                                 xevent.ConfigureEvent.x = hwnd.X;
382                                 xevent.ConfigureEvent.y = hwnd.Y;
383                                 xevent.ConfigureEvent.width = hwnd.Width;
384                                 xevent.ConfigureEvent.height = hwnd.Height;
385                                 
386                                 return xevent;
387                         }
388
389                         public override XEvent Dequeue ()
390                         {
391                                 XEvent xev = base.Dequeue ();
392
393
394                                 hwnds.RemoveAt(0);
395 #if DebugHwndEventQueue
396                                 stacks.RemoveAt(0);
397 #endif
398
399                                 return xev;
400                         }
401                 }
402
403                 public class PaintQueue : HwndEventQueue
404                 {
405                         public PaintQueue (int size) : base (size)
406                         {
407                         }
408
409                         protected override XEvent Peek ()
410                         {
411                                 X11Hwnd hwnd = (X11Hwnd)hwnds[0];
412
413                                 XEvent xevent = new XEvent ();
414
415                                 xevent.AnyEvent.type = XEventName.Expose;
416
417                                 if (hwnd.PendingExpose) {
418                                         xevent.ExposeEvent.window = hwnd.ClientWindow;
419                                 } else {
420                                         xevent.ExposeEvent.window = hwnd.WholeWindow;
421                                         xevent.ExposeEvent.x = hwnd.nc_invalid.X;
422                                         xevent.ExposeEvent.y = hwnd.nc_invalid.Y;
423                                         xevent.ExposeEvent.width = hwnd.nc_invalid.Width;
424                                         xevent.ExposeEvent.height = hwnd.nc_invalid.Height;
425                                 }
426
427                                 return xevent;
428                         }
429
430                         // don't override Dequeue like ConfigureQueue does.
431                 }
432
433                 /* a circular queue for holding X events for processing by GetMessage */
434                 private class XEventQueue {
435
436                         XEvent[] xevents;
437                         int head;
438                         int tail;
439                         int size;
440                         
441                         public XEventQueue (int initial_size)
442                         {
443                                 if (initial_size % 2 != 0)
444                                         throw new Exception ("XEventQueue must be a power of 2 size");
445
446                                 xevents = new XEvent [initial_size];
447                         }
448
449                         public int Count {
450                                 get { return size; }
451                         }
452
453                         public void Enqueue (XEvent xevent)
454                         {
455                                 if (size == xevents.Length)
456                                         Grow ();
457
458                                 xevents [tail] = xevent;
459                                 tail = (tail + 1) & (xevents.Length - 1);
460                                 size++;
461                         }
462
463                         public XEvent Dequeue ()
464                         {
465                                 if (size < 1)
466                                         throw new Exception ("Attempt to dequeue empty queue.");
467
468                                 XEvent res = xevents [head];
469                                 head = (head + 1) & (xevents.Length - 1);
470                                 size--;
471                                 return res;
472                         }
473
474                         public XEvent Peek()
475                         {
476                                 if (size < 1)
477                                         throw new Exception ("Attempt to peek at empty queue.");
478
479                                 return xevents[head];
480                         }
481
482                         private void Grow ()
483                         {
484                                 int newcap = (xevents.Length * 2);
485                                 XEvent [] na = new XEvent [newcap];
486
487                                 if (head + size > xevents.Length) {
488                                         Array.Copy (xevents, head, na, 0, xevents.Length - head);
489                                         Array.Copy (xevents, 0, na, xevents.Length - head, head + size - xevents.Length);
490                                 }
491                                 else {
492                                         Array.Copy (xevents, head, na, 0, size);
493                                 }
494
495                                 xevents = na;
496                                 head = 0;
497                                 tail = head + size;
498                         }
499                 }
500         }
501 }
502