1///////////////////////////////////////////////////////////////////////////////
2// Name:        src/msw/renderer.cpp
3// Purpose:     implementation of wxRendererNative for Windows
4// Author:      Vadim Zeitlin
5// Modified by:
6// Created:     20.07.2003
7// RCS-ID:      $Id: renderer.cpp 53524 2008-05-10 00:05:20Z RD $
8// Copyright:   (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9// License:     wxWindows licence
10///////////////////////////////////////////////////////////////////////////////
11
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
19
20// for compilers that support precompilation, includes "wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
24    #pragma hdrstop
25#endif
26
27#ifndef WX_PRECOMP
28    #include "wx/string.h"
29    #include "wx/window.h"
30    #include "wx/dc.h"
31    #include "wx/settings.h"
32#endif //WX_PRECOMP
33
34#include "wx/splitter.h"
35#include "wx/renderer.h"
36#include "wx/msw/private.h"
37#include "wx/msw/uxtheme.h"
38
39#if wxUSE_GRAPHICS_CONTEXT
40// TODO remove this dependency (gdiplus needs the macros)
41#ifndef max
42#define max(a,b)            (((a) > (b)) ? (a) : (b))
43#endif
44
45#ifndef min
46#define min(a,b)            (((a) < (b)) ? (a) : (b))
47#endif
48
49#include "gdiplus.h"
50using namespace Gdiplus;
51#endif
52
53// tmschema.h is in Win32 Platform SDK and might not be available with earlier
54// compilers
55#ifndef CP_DROPDOWNBUTTON
56    #define BP_PUSHBUTTON      1
57    #define BP_RADIOBUTTON     2
58    #define BP_CHECKBOX        3
59    #define RBS_UNCHECKEDNORMAL 1
60    #define RBS_CHECKEDNORMAL   (RBS_UNCHECKEDNORMAL + 4)
61    #define RBS_MIXEDNORMAL     (RBS_CHECKEDNORMAL + 4)
62    #define CBS_UNCHECKEDNORMAL 1
63    #define CBS_CHECKEDNORMAL   (CBS_UNCHECKEDNORMAL + 4)
64    #define CBS_MIXEDNORMAL     (CBS_CHECKEDNORMAL + 4)
65
66    #define PBS_NORMAL          1
67    #define PBS_HOT             2
68    #define PBS_PRESSED         3
69    #define PBS_DISABLED        4
70    #define PBS_DEFAULTED       5
71
72    #define CP_DROPDOWNBUTTON  1
73
74    #define CBXS_NORMAL        1
75    #define CBXS_HOT           2
76    #define CBXS_PRESSED       3
77    #define CBXS_DISABLED      4
78
79    #define TVP_GLYPH           2
80
81    #define GLPS_CLOSED         1
82    #define GLPS_OPENED         2
83
84    #define HP_HEADERITEM       1
85
86    #define HIS_NORMAL          1
87    #define HIS_HOT             2
88    #define HIS_PRESSED         3
89
90    #define TMT_HEIGHT          2417
91
92    #define HP_HEADERSORTARROW  4
93    #define HSAS_SORTEDUP       1
94    #define HSAS_SORTEDDOWN     2
95
96    #define EP_EDITTEXT         1
97    #define ETS_NORMAL          1
98    #define ETS_HOT             2
99    #define ETS_SELECTED        3
100    #define ETS_DISABLED        4
101    #define ETS_FOCUSED         5
102    #define ETS_READONLY        6
103    #define ETS_ASSIST          7
104    #define TMT_FILLCOLOR       3802
105    #define TMT_TEXTCOLOR       3803
106    #define TMT_BORDERCOLOR     3801
107    #define TMT_EDGEFILLCOLOR   3808
108#endif
109
110#if defined(__WXWINCE__) && !defined(DFCS_FLAT)
111    #define DFCS_FLAT 0
112#endif
113
114
115// ----------------------------------------------------------------------------
116// If the DC is a wxGCDC then pull out the HDC from the GraphicsContext when
117// it is needed, and handle the Release when done.
118
119class GraphicsHDC
120{
121public:
122    GraphicsHDC(wxDC* dc)
123    {
124#if wxUSE_GRAPHICS_CONTEXT
125        m_graphics = NULL;
126        wxGCDC* gcdc = wxDynamicCast(dc, wxGCDC);
127        if (gcdc) {
128            m_graphics = (Graphics*)gcdc->GetGraphicsContext()->GetNativeContext();
129            m_hdc = m_graphics->GetHDC();
130        }
131        else
132#endif
133            m_hdc = GetHdcOf(*dc);
134    }
135
136    ~GraphicsHDC()
137    {
138#if wxUSE_GRAPHICS_CONTEXT
139        if (m_graphics)
140            m_graphics->ReleaseHDC(m_hdc);
141#endif
142    }
143
144    operator HDC() const { return m_hdc; }
145
146private:
147    HDC         m_hdc;
148#if wxUSE_GRAPHICS_CONTEXT
149    Graphics*   m_graphics;
150#endif
151};
152
153// ----------------------------------------------------------------------------
154// wxRendererMSW: wxRendererNative implementation for "old" Win32 systems
155// ----------------------------------------------------------------------------
156
157class WXDLLEXPORT wxRendererMSW : public wxDelegateRendererNative
158{
159public:
160    wxRendererMSW() { }
161
162    static wxRendererNative& Get();
163
164    virtual void DrawComboBoxDropButton(wxWindow *win,
165                                        wxDC& dc,
166                                        const wxRect& rect,
167                                        int flags = 0);
168
169    virtual void DrawPushButton(wxWindow *win,
170                                wxDC& dc,
171                                const wxRect& rect,
172                                int flags = 0);
173
174private:
175    DECLARE_NO_COPY_CLASS(wxRendererMSW)
176};
177
178// ----------------------------------------------------------------------------
179// wxRendererXP: wxRendererNative implementation for Windows XP and later
180// ----------------------------------------------------------------------------
181
182#if wxUSE_UXTHEME
183
184class WXDLLEXPORT wxRendererXP : public wxDelegateRendererNative
185{
186public:
187    wxRendererXP() : wxDelegateRendererNative(wxRendererMSW::Get()) { }
188
189    static wxRendererNative& Get();
190
191    virtual int DrawHeaderButton(wxWindow *win,
192                                  wxDC& dc,
193                                  const wxRect& rect,
194                                  int flags = 0,
195                                  wxHeaderSortIconType sortArrow = wxHDR_SORT_ICON_NONE,
196                                  wxHeaderButtonParams* params = NULL);
197    virtual int GetHeaderButtonHeight(wxWindow *win);
198
199    virtual void DrawTreeItemButton(wxWindow *win,
200                                    wxDC& dc,
201                                    const wxRect& rect,
202                                    int flags = 0);
203    virtual void DrawSplitterBorder(wxWindow *win,
204                                    wxDC& dc,
205                                    const wxRect& rect,
206                                    int flags = 0);
207    virtual void DrawSplitterSash(wxWindow *win,
208                                  wxDC& dc,
209                                  const wxSize& size,
210                                  wxCoord position,
211                                  wxOrientation orient,
212                                  int flags = 0);
213    virtual void DrawComboBoxDropButton(wxWindow *win,
214                                        wxDC& dc,
215                                        const wxRect& rect,
216                                        int flags = 0);
217    virtual void DrawCheckBox(wxWindow *win,
218                              wxDC& dc,
219                              const wxRect& rect,
220                              int flags = 0);
221
222    virtual void DrawPushButton(wxWindow *win,
223                                wxDC& dc,
224                                const wxRect& rect,
225                                int flags = 0);
226
227    virtual wxSplitterRenderParams GetSplitterParams(const wxWindow *win);
228private:
229    DECLARE_NO_COPY_CLASS(wxRendererXP)
230};
231
232#endif // wxUSE_UXTHEME
233
234// ============================================================================
235// wxRendererNative and wxRendererMSW implementation
236// ============================================================================
237
238/* static */
239wxRendererNative& wxRendererNative::GetDefault()
240{
241#if wxUSE_UXTHEME
242    wxUxThemeEngine *themeEngine = wxUxThemeEngine::Get();
243    if ( themeEngine && themeEngine->IsAppThemed() )
244        return wxRendererXP::Get();
245#endif // wxUSE_UXTHEME
246
247    return wxRendererMSW::Get();
248}
249
250/* static */
251wxRendererNative& wxRendererMSW::Get()
252{
253    static wxRendererMSW s_rendererMSW;
254
255    return s_rendererMSW;
256}
257
258void
259wxRendererMSW::DrawComboBoxDropButton(wxWindow * WXUNUSED(win),
260                                      wxDC& dc,
261                                      const wxRect& rect,
262                                      int flags)
263{
264    RECT r;
265    r.left = rect.GetLeft();
266    r.top = rect.GetTop();
267    r.bottom = rect.y + rect.height;
268    r.right = rect.x + rect.width;
269
270    int style = DFCS_SCROLLCOMBOBOX;
271    if ( flags & wxCONTROL_DISABLED )
272        style |= DFCS_INACTIVE;
273    if ( flags & wxCONTROL_PRESSED )
274        style |= DFCS_PUSHED | DFCS_FLAT;
275
276    ::DrawFrameControl(GraphicsHDC(&dc), &r, DFC_SCROLL, style);
277}
278
279void
280wxRendererMSW::DrawPushButton(wxWindow * WXUNUSED(win),
281                              wxDC& dc,
282                              const wxRect& rectOrig,
283                              int flags)
284{
285    wxRect rect(rectOrig);
286
287    int style = DFCS_BUTTONPUSH;
288    if ( flags & wxCONTROL_DISABLED )
289        style |= DFCS_INACTIVE;
290    if ( flags & wxCONTROL_PRESSED )
291        style |= DFCS_PUSHED | DFCS_FLAT;
292    if ( flags & wxCONTROL_ISDEFAULT )
293    {
294        // DrawFrameControl() doesn't seem to support default buttons so we
295        // have to draw the border ourselves
296        wxDCPenChanger pen(dc, *wxBLACK_PEN);
297        wxDCBrushChanger brush(dc, *wxTRANSPARENT_BRUSH);
298        dc.DrawRectangle(rect);
299        rect.Deflate(1);
300    }
301
302    RECT rc;
303    wxCopyRectToRECT(rect, rc);
304
305    ::DrawFrameControl(GraphicsHDC(&dc), &rc, DFC_BUTTON, style);
306}
307
308// ============================================================================
309// wxRendererXP implementation
310// ============================================================================
311
312#if wxUSE_UXTHEME
313
314/* static */
315wxRendererNative& wxRendererXP::Get()
316{
317    static wxRendererXP s_rendererXP;
318
319    return s_rendererXP;
320}
321
322// NOTE: There is no guarantee that the button drawn fills the entire rect (XP
323// default theme, for example), so the caller should have cleared button's
324// background before this call. This is quite likely a wxMSW-specific thing.
325void
326wxRendererXP::DrawComboBoxDropButton(wxWindow * win,
327                                      wxDC& dc,
328                                      const wxRect& rect,
329                                      int flags)
330{
331    wxUxThemeHandle hTheme(win, L"COMBOBOX");
332    if ( !hTheme )
333    {
334        m_rendererNative.DrawComboBoxDropButton(win, dc, rect, flags);
335        return;
336    }
337
338    RECT r;
339    wxCopyRectToRECT(rect, r);
340
341    int state;
342    if ( flags & wxCONTROL_PRESSED )
343        state = CBXS_PRESSED;
344    else if ( flags & wxCONTROL_CURRENT )
345        state = CBXS_HOT;
346    else if ( flags & wxCONTROL_DISABLED )
347        state = CBXS_DISABLED;
348    else
349        state = CBXS_NORMAL;
350
351    wxUxThemeEngine::Get()->DrawThemeBackground
352                            (
353                                hTheme,
354                                GraphicsHDC(&dc),
355                                CP_DROPDOWNBUTTON,
356                                state,
357                                &r,
358                                NULL
359                            );
360
361}
362
363int
364wxRendererXP::DrawHeaderButton(wxWindow *win,
365                               wxDC& dc,
366                               const wxRect& rect,
367                               int flags,
368                               wxHeaderSortIconType sortArrow,
369                               wxHeaderButtonParams* params)
370{
371    wxUxThemeHandle hTheme(win, L"HEADER");
372    if ( !hTheme )
373    {
374        return m_rendererNative.DrawHeaderButton(win, dc, rect, flags, sortArrow, params);
375    }
376
377    RECT r;
378    wxCopyRectToRECT(rect, r);
379
380    int state;
381    if ( flags & wxCONTROL_PRESSED )
382        state = HIS_PRESSED;
383    else if ( flags & wxCONTROL_CURRENT )
384        state = HIS_HOT;
385    else
386        state = HIS_NORMAL;
387    wxUxThemeEngine::Get()->DrawThemeBackground
388                            (
389                                hTheme,
390                                GraphicsHDC(&dc),
391                                HP_HEADERITEM,
392                                state,
393                                &r,
394                                NULL
395                            );
396
397    // NOTE: Using the theme to draw HP_HEADERSORTARROW doesn't do anything.
398    // Why?  If this can be fixed then draw the sort arrows using the theme
399    // and then clear those flags before calling DrawHeaderButtonContents.
400
401    // Add any extras that are specified in flags and params
402    return DrawHeaderButtonContents(win, dc, rect, flags, sortArrow, params);
403}
404
405
406int
407wxRendererXP::GetHeaderButtonHeight(wxWindow *win)
408{
409    wxUxThemeHandle hTheme(win, L"HEADER");
410    if ( !hTheme )
411    {
412        return m_rendererNative.GetHeaderButtonHeight(win);
413    }
414
415    HRESULT hr;
416    int value = -1;
417
418    hr = wxUxThemeEngine::Get()->GetThemeMetric( hTheme,
419                                                 NULL,
420                                                 HP_HEADERITEM,
421                                                 HIS_NORMAL,
422                                                 TMT_HEIGHT,
423                                                 &value );
424    if ( hr == S_OK )
425        return value;
426    else
427        return 20;
428}
429
430
431void
432wxRendererXP::DrawTreeItemButton(wxWindow *win,
433                                 wxDC& dc,
434                                 const wxRect& rect,
435                                 int flags)
436{
437    wxUxThemeHandle hTheme(win, L"TREEVIEW");
438    if ( !hTheme )
439    {
440        m_rendererNative.DrawTreeItemButton(win, dc, rect, flags);
441        return;
442    }
443
444    RECT r;
445    wxCopyRectToRECT(rect, r);
446
447    int state = flags & wxCONTROL_EXPANDED ? GLPS_OPENED : GLPS_CLOSED;
448    wxUxThemeEngine::Get()->DrawThemeBackground
449                            (
450                                hTheme,
451                                GraphicsHDC(&dc),
452                                TVP_GLYPH,
453                                state,
454                                &r,
455                                NULL
456                            );
457}
458
459void
460wxRendererXP::DrawCheckBox(wxWindow *win,
461                           wxDC& dc,
462                           const wxRect& rect,
463                           int flags)
464{
465    wxUxThemeHandle hTheme(win, L"BUTTON");
466    if ( !hTheme )
467    {
468        m_rendererNative.DrawCheckBox(win, dc, rect, flags);
469        return;
470    }
471
472    RECT r;
473    wxCopyRectToRECT(rect, r);
474
475    int state;
476    if ( flags & wxCONTROL_CHECKED )
477        state = CBS_CHECKEDNORMAL;
478    else if ( flags & wxCONTROL_UNDETERMINED )
479        state = CBS_MIXEDNORMAL;
480    else
481        state = CBS_UNCHECKEDNORMAL;
482
483    // CBS_XXX is followed by CBX_XXXGOT, then CBS_XXXPRESSED and DISABLED
484    if ( flags & wxCONTROL_CURRENT )
485        state += 1;
486    else if ( flags & wxCONTROL_PRESSED )
487        state += 2;
488    else if ( flags & wxCONTROL_DISABLED )
489        state += 3;
490
491    wxUxThemeEngine::Get()->DrawThemeBackground
492                            (
493                                hTheme,
494                                GraphicsHDC(&dc),
495                                BP_CHECKBOX,
496                                state,
497                                &r,
498                                NULL
499                            );
500}
501
502
503
504void
505wxRendererXP::DrawPushButton(wxWindow * win,
506                             wxDC& dc,
507                             const wxRect& rect,
508                             int flags)
509{
510    wxUxThemeHandle hTheme(win, L"BUTTON");
511    if ( !hTheme )
512    {
513        m_rendererNative.DrawPushButton(win, dc, rect, flags);
514        return;
515    }
516
517    RECT r;
518    wxCopyRectToRECT(rect, r);
519
520    int state;
521    if ( flags & wxCONTROL_PRESSED )
522        state = PBS_PRESSED;
523    else if ( flags & wxCONTROL_CURRENT )
524        state = PBS_HOT;
525    else if ( flags & wxCONTROL_DISABLED )
526        state = PBS_DISABLED;
527    else if ( flags & wxCONTROL_ISDEFAULT )
528        state = PBS_DEFAULTED;
529    else
530        state = PBS_NORMAL;
531
532    wxUxThemeEngine::Get()->DrawThemeBackground
533                            (
534                                hTheme,
535                                GraphicsHDC(&dc),
536                                BP_PUSHBUTTON,
537                                state,
538                                &r,
539                                NULL
540                            );
541}
542
543// ----------------------------------------------------------------------------
544// splitter drawing
545// ----------------------------------------------------------------------------
546
547// the width of the sash: this is the same as used by Explorer...
548static const wxCoord SASH_WIDTH = 4;
549
550wxSplitterRenderParams
551wxRendererXP::GetSplitterParams(const wxWindow * win)
552{
553    if ( win->HasFlag(wxSP_NO_XP_THEME) )
554        return m_rendererNative.GetSplitterParams(win);
555    else
556        return wxSplitterRenderParams(SASH_WIDTH, 0, false);
557}
558
559void
560wxRendererXP::DrawSplitterBorder(wxWindow * win,
561                                 wxDC& dc,
562                                 const wxRect& rect,
563                                 int flags)
564{
565    if ( win->HasFlag(wxSP_NO_XP_THEME) )
566    {
567        m_rendererNative.DrawSplitterBorder(win, dc, rect, flags);
568    }
569}
570
571void
572wxRendererXP::DrawSplitterSash(wxWindow *win,
573                               wxDC& dc,
574                               const wxSize& size,
575                               wxCoord position,
576                               wxOrientation orient,
577                               int flags)
578{
579    if ( !win->HasFlag(wxSP_NO_XP_THEME) )
580    {
581        dc.SetPen(*wxTRANSPARENT_PEN);
582        dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)));
583        if ( orient == wxVERTICAL )
584        {
585            dc.DrawRectangle(position, 0, SASH_WIDTH, size.y);
586        }
587        else // wxHORIZONTAL
588        {
589            dc.DrawRectangle(0, position, size.x, SASH_WIDTH);
590        }
591
592        return;
593    }
594
595    m_rendererNative.DrawSplitterSash(win, dc, size, position, orient, flags);
596}
597
598// ----------------------------------------------------------------------------
599// Other renderer functions to be merged in to wxRenderer class in 2.9, but
600// they are standalone functions here to protect the ABI.
601// ----------------------------------------------------------------------------
602
603// Uses the theme to draw the border and fill for something like a wxTextCtrl
604void wxRenderer_DrawTextCtrl(wxWindow* win, wxDC& dc, const wxRect& rect, int flags)
605{
606    wxColour fill;
607    wxColour bdr;
608    COLORREF cref;
609
610#if wxUSE_UXTHEME
611    wxUxThemeHandle hTheme(win, L"EDIT");
612    if (hTheme)
613    {
614        wxUxThemeEngine::Get()->GetThemeColor(hTheme, EP_EDITTEXT,
615                                              ETS_NORMAL, TMT_FILLCOLOR, &cref);
616        fill = wxRGBToColour(cref);
617
618        int etsState;
619        if ( flags & wxCONTROL_DISABLED )
620            etsState = ETS_DISABLED;
621        else
622            etsState = ETS_NORMAL;
623
624        wxUxThemeEngine::Get()->GetThemeColor(hTheme, EP_EDITTEXT,
625                                              etsState, TMT_BORDERCOLOR, &cref);
626        bdr = wxRGBToColour(cref);
627    }
628    else
629#endif
630    {
631        fill = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
632        bdr = *wxBLACK;
633    }
634
635    dc.SetPen( bdr );
636    dc.SetBrush( fill );
637    dc.DrawRectangle(rect);
638}
639
640
641// Draw the equivallent of a wxComboBox
642void wxRenderer_DrawComboBox(wxWindow* win, wxDC& dc, const wxRect& rect, int flags)
643{
644    // Draw the main part of the control same as TextCtrl
645    wxRenderer_DrawTextCtrl(win, dc, rect, flags);
646
647    // Draw the button inside the border, on the right side
648    wxRect br(rect);
649    br.height -= 2;
650    br.x += br.width - br.height - 1;
651    br.width = br.height;
652    br.y += 1;
653
654    wxRendererNative::Get().DrawComboBoxDropButton(win, dc, br, flags);
655}
656
657
658void wxRenderer_DrawChoice(wxWindow* win, wxDC& dc,
659                           const wxRect& rect, int flags)
660{
661    wxRenderer_DrawComboBox(win, dc, rect, flags);
662}
663
664
665// Draw a themed radio button
666void wxRenderer_DrawRadioButton(wxWindow* win, wxDC& dc, const wxRect& rect, int flags)
667{
668#if wxUSE_UXTHEME
669    wxUxThemeHandle hTheme(win, L"BUTTON");
670    if ( !hTheme )
671#endif
672    {
673        // ??? m_rendererNative.DrawRadioButton(win, dc, rect, flags);
674        return;
675    }
676
677#if wxUSE_UXTHEME
678    RECT r;
679    wxCopyRectToRECT(rect, r);
680
681    int state;
682    if ( flags & wxCONTROL_CHECKED )
683        state = RBS_CHECKEDNORMAL;
684    else if ( flags & wxCONTROL_UNDETERMINED )
685        state = RBS_MIXEDNORMAL;
686    else
687        state = RBS_UNCHECKEDNORMAL;
688
689    // RBS_XXX is followed by RBX_XXXGOT, then RBS_XXXPRESSED and DISABLED
690    if ( flags & wxCONTROL_CURRENT )
691        state += 1;
692    else if ( flags & wxCONTROL_PRESSED )
693        state += 2;
694    else if ( flags & wxCONTROL_DISABLED )
695        state += 3;
696
697    wxUxThemeEngine::Get()->DrawThemeBackground
698                            (
699                                hTheme,
700                                GraphicsHDC(&dc),
701                                BP_RADIOBUTTON,
702                                state,
703                                &r,
704                                NULL
705                            );
706#endif
707}
708
709#endif // wxUSE_UXTHEME
710