OSDN Git Service

Support group affiliation with component package granularity.
[mingw/mingw-get.git] / src / dmhguix.cpp
1 /*
2  * dmhguix.cpp
3  *
4  * $Id$
5  *
6  * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
7  * Copyright (C) 2009-2013, MinGW.org Project
8  *
9  *
10  * Implementation of GUI specific API extensions, providing support
11  * for the DMH_SUBSYSTEM_GUI diagnostic message handling subsystem.
12  *
13  *
14  * This is free software.  Permission is granted to copy, modify and
15  * redistribute this software, under the provisions of the GNU General
16  * Public License, Version 3, (or, at your option, any later version),
17  * as published by the Free Software Foundation; see the file COPYING
18  * for licensing details.
19  *
20  * Note, in particular, that this software is provided "as is", in the
21  * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
22  * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
23  * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
24  * MinGW Project, accept liability for any damages, however caused,
25  * arising from the use of this software.
26  *
27  */
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <stdarg.h>
31
32 #include "dmhcore.h"
33 #include "pkgimpl.h"
34
35 #define WIN32_LEAN_AND_MEAN
36 #include <windows.h>
37
38 /* Establish limits on the buffer size for any EDITTEXT control which is
39  * used to emulate the VDU device of a pseudo-TTY diagnostic console.
40  */
41 #define DMH_PTY_MIN_BUFSIZ   4096
42 #define DMH_PTY_MAX_BUFSIZ  32768
43 #define DMH_PTY_GUARDBYTES      1
44
45 class dmhTypePTY
46 {
47   /* An auxiliary class, which may be associated with a DMH controller,
48    * to control the emulated VDU device of a pseudo-TTY console.
49    */
50   public:
51     dmhTypePTY( HWND );
52     int printf( const char *, va_list );
53     ~dmhTypePTY();
54
55   private:
56     HWND console_hook;
57     static HFONT console_font;
58     char *console_buffer, *caret; size_t max;
59     int putchar( int );
60 };
61
62 /* When we assign an emulated PTY display to a DMH data stream, in a
63  * GUI context, we will wish to also assign a monospaced font object,
64  * (Lucida Console, for preference).  We will create this font object
65  * once, on making the first PTY assignment, and will maintain this
66  * static reference to locate it; we initialise the reference to NULL,
67  * indicating the requirement to create the font object.
68  */
69 HFONT dmhTypePTY::console_font = NULL;
70
71 static int CALLBACK dmhWordBreak( const char *buf, int cur, int max, int op )
72 {
73   /* This trivial word-break call-back is assigned in place of the default
74    * handler for any EDITTEXT control used as a pseudo-TTY; it emulates the
75    * behaviour of a dumb VT-100 display, wrapping text at the right extent
76    * of the "screen", regardless of proximity to any natural word-break.
77    *
78    * Note that this call-back function must conform to the prototype which
79    * has been specified by Microsoft, even though it uses neither the "buf"
80    * nor the "max" arguments; we simply confirm, on request, that each and
81    * every character in "buf" may be considered as a word-break delimiter,
82    * and that the "cur" position in "buf" is a potential word-break point.
83    */
84   return (op == WB_ISDELIMITER) ? TRUE : cur;
85 }
86
87 dmhTypePTY::dmhTypePTY( HWND console ): console_hook( console )
88 {
89   /* Construct a PTY controller and assign it to the EDITTEXT control which
90    * we assume, without checking, has been passed as "console".
91    */
92   if( console_font == NULL )
93   {
94     /* The font object for use with PTY diagnostic streams has yet to be
95      * created; create it now.
96      */
97     LOGFONT font_info;
98     console_font = (HFONT)(SendMessage( console, WM_GETFONT, 0, 0 ));
99     GetObject( console_font, sizeof( LOGFONT ), &font_info );
100     strcpy( (char *)(&(font_info.lfFaceName)), "Lucida Console" );
101     font_info.lfHeight = (font_info.lfHeight * 9) / 10;
102     font_info.lfWidth = (font_info.lfWidth * 9) / 10;
103     console_font = CreateFontIndirect( &font_info );
104   }
105   if( console_font != NULL )
106     /*
107      * When we have a valid font object, we instruct the EDITTEXT control
108      * to use it, within its device context.
109      */
110     SendMessage( console, WM_SETFONT, (WPARAM)(console_font), TRUE );
111
112   /* Override the default EDITTEXT word-wrapping behaviour, so that we
113    * more accurately emulate the display behaviour of a VT-100 device.
114    */
115   SendMessage( console, EM_SETWORDBREAKPROC, 0, (LPARAM)(dmhWordBreak) );
116
117   /* Finally, establish a working buffer for output to the PTY, and set
118    * the initial caret position for the output text stream.
119    */
120   caret = console_buffer = (char *)(malloc( max = DMH_PTY_MIN_BUFSIZ ));
121 }
122
123 int dmhTypePTY::printf( const char *fmt, va_list argv )
124 {
125   /* Handler for printf() style output to a DMH pseudo-TTY stream.
126    *
127    * We begin by formatting the specified arguments, while saving
128    * the result into a local transfer buffer; (as a side effect,
129    * this also sets the output character count)...
130    */
131   char buf[1 + vsnprintf( NULL, 0, fmt, argv )];
132   int retval = vsnprintf( buf, sizeof( buf ), fmt, argv );
133   for( char *bufptr = buf; *bufptr; ++bufptr )
134   {
135     /* We transfer the content of the local buffer to the "device"
136      * buffer; (we do this character by character, because the DMH
137      * output stream encodes line breaks as God intended -- using
138      * '\n' only -- but the EDITTEXT control which emulates the PTY
139      * "device" requires Lucifer's "\r\n" encoding; thus '\r' and
140      * '\n' require special handling).
141      */
142     if( *bufptr == '\r' )
143     {
144       /* An explicit '\r' in the data stream should return the
145        * caret to the start of the current PHYSICAL line in the
146        * EDITTEXT display.
147        */
148       char *mark = caret;
149       while( (mark > console_buffer) && (*--mark != '\n') )
150         caret = mark;
151
152       /* The '\r' character isn't actually written to the
153        * output buffer; discount it.
154        */
155       --retval;
156     }
157     else
158     { /* Any other character is transferred to the "device" buffer...
159        */
160       if( *bufptr == '\n' )
161       {
162         /* ...inserting, (and counting), an implied '\r' before each
163          * '\n', to comply with Lucifer's encoding standard.
164          */
165         putchar( '\r' );
166         ++retval;
167       }
168       putchar( *bufptr );
169     }
170   }
171   /* When all output has been transferred to the "device" buffer, we
172    * terminate and flush it to the EDITTEXT control.
173    */
174   *caret = '\0';
175   SendMessage( console_hook, WM_SETTEXT, 0, (LPARAM)(console_buffer) );
176
177   /* As we write it, ensure that the last line written remains visible,
178    * with the caret positioned after it.
179    */
180   SendMessage( console_hook, EM_SETSEL, caret - console_buffer, (LPARAM)(-1) );
181   SendMessage( console_hook, EM_SCROLLCARET, (WPARAM)(0), (LPARAM)(0) );
182
183   /* Finally, we repaint the display window, and return the output
184    * character count.
185    */
186   InvalidateRect( console_hook, NULL, FALSE );
187   UpdateWindow( console_hook );
188   return retval;
189 }
190
191 int dmhTypePTY::putchar( int charval )
192 {
193   /* Helper method for appending a single character to the PTY "device"
194    * buffer; this will allocate an expanding buffer, (up to the maximum
195    * specified size limit, with reservation of sufficient guard bytes to
196    * ensure that output may be safely terminated), as may be required.
197    */
198   size_t offset;
199   const size_t guarded_max = max - DMH_PTY_GUARDBYTES;
200   if( (offset = caret - console_buffer) >= guarded_max )
201   {
202     /* The current "device" buffer is full; compute a new size, for
203      * possible buffer expansion.
204      */
205     size_t newsize;
206     if( (newsize = max + DMH_PTY_MIN_BUFSIZ) > DMH_PTY_MAX_BUFSIZ )
207       newsize = DMH_PTY_MAX_BUFSIZ;
208
209     if( newsize > max )
210     {
211       /* The buffer has not yet grown to its maximum allowed size;
212        * attempt to allocate additional memory, to expand it.
213        */
214       char *newbuf;
215       if( (newbuf = (char *)(realloc( console_buffer, newsize ))) != NULL )
216       {
217         /* Allocation was successful; complete assignment of the new
218          * buffer, and adjust the caret to preserve its position, at
219          * its original offset within the new buffer.
220          */
221         max = newsize;
222         caret = (console_buffer = newbuf) + offset;
223       }
224     }
225     if( offset >= guarded_max )
226     {
227       /* The buffer has reached its maximum permitted size, (or there
228        * was insufficient free memory to expand it), and there still
229        * isn't room to accommodate another character; locate the start
230        * of the SECOND logical line within it...
231        */
232       char *mark = console_buffer, *endptr = caret;
233       while( *mark && (mark < caret) && (*mark++ != '\n') )
234         ;
235       /* ...then copy it, and all following lines, to the start of the
236        * buffer, so deleting the FIRST logical line, and thus free up
237        * an equivalent amount of space at the end.
238        *
239        * Note: we might get away with a single memcpy() of the entire
240        * portion of the buffer, from the SECOND line onward, backwards
241        * to overwrite the FIRST line, but that would entail copying of
242        * overlapping memory regions, which is technically described as
243        * causing undefined behaviour; to avoid any possible problems,
244        * we identify a block size equal to the space occupied by the
245        * first line...
246        */
247       size_t residual = caret - mark;
248       size_t block_size = mark - (caret = console_buffer);
249       while( residual >= block_size )
250       {
251         /* ...then copy the residual, in chunks of that size, (so that
252          * there is never any overlap), until we reduce the residual to
253          * less than the block size...
254          */
255         memcpy( caret, mark, block_size );
256         residual -= block_size; caret = mark; mark += block_size;
257       }
258       if( residual > 0 )
259       {
260         /* ...finishing up by copying any lesser sized final residual,
261          * and leaving the caret at the start of the space so freed.
262          */
263         memcpy( caret, mark, residual );
264         caret += residual;
265       }
266     }
267   }
268   /* Finally, store the current character into the "device" buffer, and
269    * adjust the caret position in readiness for the next.
270    */
271   return *caret++ = charval;
272 }
273
274 dmhTypePTY::~dmhTypePTY()
275 {
276   /* When closing a PTY controller handle, we may free the memory which
277    * we allocated for its "device" buffer.  (Note that we DO NOT delete
278    * the assocaiated font object, because the associated EDITTEXT control
279    * may outlive its controller, and will continue to need it; however,
280    * it does not need the buffer, because it maintains its own private
281    * copy of the content).
282    */
283   free( (void *)(console_buffer) );
284 }
285
286 class dmhTypeGUI: public dmhTypeGeneric
287 {
288   /* Diagnostic message handler for use in GUI applications.
289    */
290   public:
291     dmhTypeGUI( const char* );
292     virtual uint16_t control( const uint16_t, const uint16_t );
293     virtual int notify( const dmh_severity, const char*, va_list );
294     virtual int printf( const char*, va_list );
295     virtual void set_console_hook( void * );
296
297   private:
298     HWND owner; uint16_t mode;
299     char *msgbuf; size_t msglen;
300     int dispatch_message( int );
301     dmhTypePTY *console_hook;
302 };
303
304 #if IMPLEMENTATION_LEVEL == PACKAGE_BASE_COMPONENT
305
306 EXTERN_C
307 dmhTypeGeneric *dmh_init_gui( const char * ) __declspec(dllexport);
308 dmhTypeGeneric *dmh_init_gui( const char *progname )
309 {
310   /* Hook required by the dmh_init() function, to support run-time
311    * binding of this DMH_SUBSYSTEM_GUI implementation.
312    */
313   return new dmhTypeGUI( progname );
314 }
315
316 #endif /* IMPLEMENTATION_LEVEL == PACKAGE_BASE_COMPONENT */
317
318 /* Constructor serves to initialise the message handler,
319  * creating the class instance, and storing the specified
320  * program name within it.
321  */
322 dmhTypeGUI::dmhTypeGUI( const char* name ):dmhTypeGeneric( name ),
323   /*
324    * This handler also requires initialisation of the
325    * message box content collector...
326    */
327   owner( NULL ), mode( 0 ), msgbuf( NULL ), msglen( 0 ),
328   /*
329    * ...and must set the default state for pseudo-console
330    * association to "none".
331    */
332   console_hook( NULL ){}
333
334 uint16_t dmhTypeGUI::control( const uint16_t request, const uint16_t mask )
335 {
336   /* Select optional features of the GUI class message handler...
337    */
338   if( request & DMH_DISPATCH_DIGEST )
339     /*
340      * ...and dispatch "flush" requests for messages which have
341      * been collected, when operating in "digest mode".
342      */
343     dispatch_message( msglen );
344   return mode = request | (mode & mask);
345 }
346
347 void dmhTypeGUI::set_console_hook( void *console )
348 {
349   /* Helper method to assign an emulated PTY "device" to the DMH stream;
350    * (note that any prior PTY assignment is first discarded).
351    */
352   delete console_hook;
353   console_hook = (console == NULL) ? NULL : new dmhTypePTY( (HWND)(console) );
354 }
355
356 int dmhTypeGUI::notify( const dmh_severity code, const char *fmt, va_list argv )
357 {
358   /* Message dispatcher for GUI applications; this formats messages
359    * for display within a conventional MessageBox dialogue.
360    */
361   if( console_hook != NULL )
362   {
363     /* When the DMH stream has an associated PTY, then we direct this
364      * notification to it, analogously to the CLI use of a TTY.
365      */
366     return dmh_printf( notification_format, progname, severity_tag( code ) )
367       + console_hook->printf( fmt, argv );
368   }
369
370   /* Conversely, when there is no associated PTY, then we lay out the
371    * notification for display in a windows message box.
372    */
373   int newlen = 1 + vsnprintf( NULL, 0, fmt, argv );
374   if( mode & DMH_COMPILE_DIGEST )
375   {
376     /* The message handler is operating in "digest mode"; individual
377      * messages are collected into a dynamically extensible buffer,
378      * until a control request flushes the entire collection to a
379      * single MessageBox.
380      */
381     char *newbuf;
382     if( (newbuf = (char *)(realloc( msgbuf, newlen + msglen ))) != NULL )
383     {
384       /* The message buffer has been successfully extended
385        * to accommodate the incoming message...
386        */
387       if( (mode & DMH_SEVERITY_MASK) < code )
388         /*
389          * Adjust the severity code to match the maximum
390          * of all messages currently collected...
391          */
392         mode = code | (mode & ~DMH_SEVERITY_MASK);
393
394       /* ...and append the text of the incoming message
395        * to the buffered collection.
396        */
397       msglen += vsprintf( (msgbuf = newbuf) + msglen, fmt, argv );
398     }
399   }
400   else if( (msgbuf = (char *)(malloc( newlen ))) != NULL )
401   {
402     /* The handler is operating in "one shot" mode; record
403      * the severity of the incoming message and dispatch it
404      * immediately, in its own separate MessageBox.
405      */
406     mode = code | (mode & ~DMH_SEVERITY_MASK);
407     return dispatch_message( vsprintf( msgbuf, fmt, argv ) );
408   }
409 }
410
411 static inline HWND last_active_popup( HWND owner )
412 {
413   /* Local helper function to ensure that diagnostic MessageBox
414    * dialogues are assigned to the most recently active pop-up,
415    * if any, invoked by the primary owner application window.
416    */
417   HWND popup = GetLastActivePopup( owner );
418   if( IsWindow( popup ) )
419     return popup;
420   return owner;
421 }
422
423 int dmhTypeGUI::dispatch_message( int len )
424 {
425   /* Helper used by both the control() and notify() methods,
426    * for dispatching messages, either individually or as a
427    * "digest mode" collection, to a MessageBox.
428    */
429   if( msgbuf != NULL )
430   {
431     /* The MessageBox will exhibit only an "OK" button...
432      */
433     int status = MB_OK;
434     switch( mode & DMH_SEVERITY_MASK )
435     {
436       /* ...and will be adorned, as appropriate to the
437        * recorded message severity, with...
438        */
439       case DMH_INFO:
440         /* ...an icon which is indicative of an
441          * informational message...
442          */
443         status |= MB_ICONINFORMATION;
444         break;
445
446       case DMH_WARNING:
447         /* ...a warning message...
448          */
449         status |= MB_ICONWARNING;
450         break;
451         
452       default:
453         /* ...or, if neither of the above is
454          * appropriate, an error message.
455          */
456         status |= MB_ICONERROR;
457     }
458     if( owner == NULL )
459       /*
460        * The owner window has yet to be identified; try to match
461        * it, on the basis of the window class name, to the top
462        * level application window.
463        */
464       owner = FindWindow( progname, NULL );
465
466     /* Dispatch the message...
467      */
468     MessageBox( last_active_popup( owner ), msgbuf, progname, status );
469
470     /* ...then release the heap memory used to format it.
471      */
472     free( (void *)(msgbuf) );
473     msgbuf = NULL; msglen = 0;
474   }
475   /* Always return the number of characters in the message,
476    * as notified by the calling method.
477    */
478   return len;
479 }
480
481 int dmhTypeGUI::printf( const char *fmt, va_list argv )
482 {
483   /* Display arbitrary text messages via the diagnostic message handler.
484    */
485   if( console_hook != NULL )
486     /*
487      * When the DMH stream has an associated PTY, then we simply
488      * emit the message via that.
489      */
490     return console_hook->printf( fmt, argv );
491
492   /* Conversely, when there is no associated PTY, we redirect the
493    * message for display in a windows message box, in the guise of
494    * a DMH_INFO notification.
495    */
496   return notify( DMH_INFO, fmt, argv );
497 }
498
499 /* $RCSfile$: end of file */