1///////////////////////////////////////////////////////////////////////////////
2// Name:        generic/htmllbox.cpp
3// Purpose:     implementation of wxHtmlListBox
4// Author:      Vadim Zeitlin
5// Modified by:
6// Created:     31.05.03
7// RCS-ID:      $Id: htmllbox.cpp 44026 2006-12-21 18:24:27Z VS $
8// Copyright:   (c) 2003 Vadim Zeitlin <vadim@wxwindows.org>
9// License:     wxWindows license
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/dcclient.h"
29#endif //WX_PRECOMP
30
31#if wxUSE_HTML
32
33#include "wx/htmllbox.h"
34
35#include "wx/html/htmlcell.h"
36#include "wx/html/winpars.h"
37
38// this hack forces the linker to always link in m_* files
39#include "wx/html/forcelnk.h"
40FORCE_WXHTML_MODULES()
41
42// ----------------------------------------------------------------------------
43// constants
44// ----------------------------------------------------------------------------
45
46// small border always added to the cells:
47static const wxCoord CELL_BORDER = 2;
48
49const wxChar wxHtmlListBoxNameStr[] = wxT("htmlListBox");
50const wxChar wxSimpleHtmlListBoxNameStr[] = wxT("simpleHtmlListBox");
51
52// ============================================================================
53// private classes
54// ============================================================================
55
56// ----------------------------------------------------------------------------
57// wxHtmlListBoxCache
58// ----------------------------------------------------------------------------
59
60// this class is used by wxHtmlListBox to cache the parsed representation of
61// the items to avoid doing it anew each time an item must be drawn
62class wxHtmlListBoxCache
63{
64private:
65    // invalidate a single item, used by Clear() and InvalidateRange()
66    void InvalidateItem(size_t n)
67    {
68        m_items[n] = (size_t)-1;
69        delete m_cells[n];
70        m_cells[n] = NULL;
71    }
72
73public:
74    wxHtmlListBoxCache()
75    {
76        for ( size_t n = 0; n < SIZE; n++ )
77        {
78            m_items[n] = (size_t)-1;
79            m_cells[n] = NULL;
80        }
81
82        m_next = 0;
83    }
84
85    ~wxHtmlListBoxCache()
86    {
87        for ( size_t n = 0; n < SIZE; n++ )
88        {
89            delete m_cells[n];
90        }
91    }
92
93    // completely invalidate the cache
94    void Clear()
95    {
96        for ( size_t n = 0; n < SIZE; n++ )
97        {
98            InvalidateItem(n);
99        }
100    }
101
102    // return the cached cell for this index or NULL if none
103    wxHtmlCell *Get(size_t item) const
104    {
105        for ( size_t n = 0; n < SIZE; n++ )
106        {
107            if ( m_items[n] == item )
108                return m_cells[n];
109        }
110
111        return NULL;
112    }
113
114    // returns true if we already have this item cached
115    bool Has(size_t item) const { return Get(item) != NULL; }
116
117    // ensure that the item is cached
118    void Store(size_t item, wxHtmlCell *cell)
119    {
120        delete m_cells[m_next];
121        m_cells[m_next] = cell;
122        m_items[m_next] = item;
123
124        // advance to the next item wrapping around if there are no more
125        if ( ++m_next == SIZE )
126            m_next = 0;
127    }
128
129    // forget the cached value of the item(s) between the given ones (inclusive)
130    void InvalidateRange(size_t from, size_t to)
131    {
132        for ( size_t n = 0; n < SIZE; n++ )
133        {
134            if ( m_items[n] >= from && m_items[n] <= to )
135            {
136                InvalidateItem(n);
137            }
138        }
139    }
140
141private:
142    // the max number of the items we cache
143    enum { SIZE = 50 };
144
145    // the index of the LRU (oldest) cell
146    size_t m_next;
147
148    // the parsed representation of the cached item or NULL
149    wxHtmlCell *m_cells[SIZE];
150
151    // the index of the currently cached item (only valid if m_cells != NULL)
152    size_t m_items[SIZE];
153};
154
155// ----------------------------------------------------------------------------
156// wxHtmlListBoxStyle
157// ----------------------------------------------------------------------------
158
159// just forward wxDefaultHtmlRenderingStyle callbacks to the main class so that
160// they could be overridden by the user code
161class wxHtmlListBoxStyle : public wxDefaultHtmlRenderingStyle
162{
163public:
164    wxHtmlListBoxStyle(const wxHtmlListBox& hlbox) : m_hlbox(hlbox) { }
165
166    virtual wxColour GetSelectedTextColour(const wxColour& colFg)
167    {
168        return m_hlbox.GetSelectedTextColour(colFg);
169    }
170
171    virtual wxColour GetSelectedTextBgColour(const wxColour& colBg)
172    {
173        return m_hlbox.GetSelectedTextBgColour(colBg);
174    }
175
176private:
177    const wxHtmlListBox& m_hlbox;
178
179    DECLARE_NO_COPY_CLASS(wxHtmlListBoxStyle)
180};
181
182// ----------------------------------------------------------------------------
183// event tables
184// ----------------------------------------------------------------------------
185
186BEGIN_EVENT_TABLE(wxHtmlListBox, wxVListBox)
187    EVT_SIZE(wxHtmlListBox::OnSize)
188    EVT_MOTION(wxHtmlListBox::OnMouseMove)
189    EVT_LEFT_DOWN(wxHtmlListBox::OnLeftDown)
190END_EVENT_TABLE()
191
192// ============================================================================
193// implementation
194// ============================================================================
195
196IMPLEMENT_ABSTRACT_CLASS(wxHtmlListBox, wxVListBox)
197
198
199// ----------------------------------------------------------------------------
200// wxHtmlListBox creation
201// ----------------------------------------------------------------------------
202
203wxHtmlListBox::wxHtmlListBox()
204    : wxHtmlWindowMouseHelper(this)
205{
206    Init();
207}
208
209// normal constructor which calls Create() internally
210wxHtmlListBox::wxHtmlListBox(wxWindow *parent,
211                             wxWindowID id,
212                             const wxPoint& pos,
213                             const wxSize& size,
214                             long style,
215                             const wxString& name)
216    : wxHtmlWindowMouseHelper(this)
217{
218    Init();
219
220    (void)Create(parent, id, pos, size, style, name);
221}
222
223void wxHtmlListBox::Init()
224{
225    m_htmlParser = NULL;
226    m_htmlRendStyle = new wxHtmlListBoxStyle(*this);
227    m_cache = new wxHtmlListBoxCache;
228}
229
230bool wxHtmlListBox::Create(wxWindow *parent,
231                           wxWindowID id,
232                           const wxPoint& pos,
233                           const wxSize& size,
234                           long style,
235                           const wxString& name)
236{
237    return wxVListBox::Create(parent, id, pos, size, style, name);
238}
239
240wxHtmlListBox::~wxHtmlListBox()
241{
242    delete m_cache;
243
244    if ( m_htmlParser )
245    {
246        delete m_htmlParser->GetDC();
247        delete m_htmlParser;
248    }
249
250    delete m_htmlRendStyle;
251}
252
253// ----------------------------------------------------------------------------
254// wxHtmlListBox appearance
255// ----------------------------------------------------------------------------
256
257wxColour wxHtmlListBox::GetSelectedTextColour(const wxColour& colFg) const
258{
259    return m_htmlRendStyle->
260                wxDefaultHtmlRenderingStyle::GetSelectedTextColour(colFg);
261}
262
263wxColour
264wxHtmlListBox::GetSelectedTextBgColour(const wxColour& WXUNUSED(colBg)) const
265{
266    return GetSelectionBackground();
267}
268
269// ----------------------------------------------------------------------------
270// wxHtmlListBox items markup
271// ----------------------------------------------------------------------------
272
273wxString wxHtmlListBox::OnGetItemMarkup(size_t n) const
274{
275    // we don't even need to wrap the value returned by OnGetItem() inside
276    // "<html><body>" and "</body></html>" because wxHTML can parse it even
277    // without these tags
278    return OnGetItem(n);
279}
280
281// ----------------------------------------------------------------------------
282// wxHtmlListBox cache handling
283// ----------------------------------------------------------------------------
284
285void wxHtmlListBox::CacheItem(size_t n) const
286{
287    if ( !m_cache->Has(n) )
288    {
289        if ( !m_htmlParser )
290        {
291            wxHtmlListBox *self = wxConstCast(this, wxHtmlListBox);
292
293            self->m_htmlParser = new wxHtmlWinParser(self);
294            m_htmlParser->SetDC(new wxClientDC(self));
295            m_htmlParser->SetFS(&self->m_filesystem);
296#if !wxUSE_UNICODE
297            if (GetFont().Ok())
298                m_htmlParser->SetInputEncoding(GetFont().GetEncoding());
299#endif
300            // use system's default GUI font by default:
301            m_htmlParser->SetStandardFonts();
302        }
303
304        wxHtmlContainerCell *cell = (wxHtmlContainerCell *)m_htmlParser->
305                Parse(OnGetItemMarkup(n));
306        wxCHECK_RET( cell, _T("wxHtmlParser::Parse() returned NULL?") );
307
308        // set the cell's ID to item's index so that CellCoordsToPhysical()
309        // can quickly find the item:
310        cell->SetId(wxString::Format(_T("%lu"), (unsigned long)n));
311
312        cell->Layout(GetClientSize().x - 2*GetMargins().x);
313
314        m_cache->Store(n, cell);
315    }
316}
317
318void wxHtmlListBox::OnSize(wxSizeEvent& event)
319{
320    // we need to relayout all the cached cells
321    m_cache->Clear();
322
323    event.Skip();
324}
325
326void wxHtmlListBox::RefreshLine(size_t line)
327{
328    m_cache->InvalidateRange(line, line);
329
330    wxVListBox::RefreshLine(line);
331}
332
333void wxHtmlListBox::RefreshLines(size_t from, size_t to)
334{
335    m_cache->InvalidateRange(from, to);
336
337    wxVListBox::RefreshLines(from, to);
338}
339
340void wxHtmlListBox::RefreshAll()
341{
342    m_cache->Clear();
343
344    wxVListBox::RefreshAll();
345}
346
347void wxHtmlListBox::SetItemCount(size_t count)
348{
349    // the items are going to change, forget the old ones
350    m_cache->Clear();
351
352    wxVListBox::SetItemCount(count);
353}
354
355// ----------------------------------------------------------------------------
356// wxHtmlListBox implementation of wxVListBox pure virtuals
357// ----------------------------------------------------------------------------
358
359void wxHtmlListBox::OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const
360{
361    CacheItem(n);
362
363    wxHtmlCell *cell = m_cache->Get(n);
364    wxCHECK_RET( cell, _T("this cell should be cached!") );
365
366    wxHtmlRenderingInfo htmlRendInfo;
367
368    // draw the selected cell in selected state
369    if ( IsSelected(n) )
370    {
371        wxHtmlSelection htmlSel;
372        htmlSel.Set(wxPoint(0,0), cell, wxPoint(INT_MAX, INT_MAX), cell);
373        htmlRendInfo.SetSelection(&htmlSel);
374        if ( m_htmlRendStyle )
375            htmlRendInfo.SetStyle(m_htmlRendStyle);
376        htmlRendInfo.GetState().SetSelectionState(wxHTML_SEL_IN);
377    }
378
379    // note that we can't stop drawing exactly at the window boundary as then
380    // even the visible cells part could be not drawn, so always draw the
381    // entire cell
382    cell->Draw(dc,
383               rect.x + CELL_BORDER, rect.y + CELL_BORDER,
384               0, INT_MAX, htmlRendInfo);
385}
386
387wxCoord wxHtmlListBox::OnMeasureItem(size_t n) const
388{
389    CacheItem(n);
390
391    wxHtmlCell *cell = m_cache->Get(n);
392    wxCHECK_MSG( cell, 0, _T("this cell should be cached!") );
393
394    return cell->GetHeight() + cell->GetDescent() + 4;
395}
396
397// ----------------------------------------------------------------------------
398// wxHtmlListBox implementation of wxHtmlListBoxWinInterface
399// ----------------------------------------------------------------------------
400
401void wxHtmlListBox::SetHTMLWindowTitle(const wxString& WXUNUSED(title))
402{
403    // nothing to do
404}
405
406void wxHtmlListBox::OnHTMLLinkClicked(const wxHtmlLinkInfo& link)
407{
408    OnLinkClicked(GetItemForCell(link.GetHtmlCell()), link);
409}
410
411void wxHtmlListBox::OnLinkClicked(size_t WXUNUSED(n),
412                                  const wxHtmlLinkInfo& link)
413{
414    wxHtmlLinkEvent event(GetId(), link);
415    GetEventHandler()->ProcessEvent(event);
416}
417
418wxHtmlOpeningStatus
419wxHtmlListBox::OnHTMLOpeningURL(wxHtmlURLType WXUNUSED(type),
420                                const wxString& WXUNUSED(url),
421                                wxString *WXUNUSED(redirect)) const
422{
423    return wxHTML_OPEN;
424}
425
426wxPoint wxHtmlListBox::HTMLCoordsToWindow(wxHtmlCell *cell,
427                                          const wxPoint& pos) const
428{
429    return CellCoordsToPhysical(pos, cell);
430}
431
432wxWindow* wxHtmlListBox::GetHTMLWindow() { return this; }
433
434wxColour wxHtmlListBox::GetHTMLBackgroundColour() const
435{
436    return GetBackgroundColour();
437}
438
439void wxHtmlListBox::SetHTMLBackgroundColour(const wxColour& WXUNUSED(clr))
440{
441    // nothing to do
442}
443
444void wxHtmlListBox::SetHTMLBackgroundImage(const wxBitmap& WXUNUSED(bmpBg))
445{
446    // nothing to do
447}
448
449void wxHtmlListBox::SetHTMLStatusText(const wxString& WXUNUSED(text))
450{
451    // nothing to do
452}
453
454wxCursor wxHtmlListBox::GetHTMLCursor(HTMLCursor type) const
455{
456    // we don't want to show text selection cursor in listboxes
457    if (type == HTMLCursor_Text)
458        return wxHtmlWindow::GetDefaultHTMLCursor(HTMLCursor_Default);
459
460    // in all other cases, use the same cursor as wxHtmlWindow:
461    return wxHtmlWindow::GetDefaultHTMLCursor(type);
462}
463
464// ----------------------------------------------------------------------------
465// wxHtmlListBox handling of HTML links
466// ----------------------------------------------------------------------------
467
468wxPoint wxHtmlListBox::GetRootCellCoords(size_t n) const
469{
470    wxPoint pos(CELL_BORDER, CELL_BORDER);
471    pos += GetMargins();
472    pos.y += GetLinesHeight(GetFirstVisibleLine(), n);
473    return pos;
474}
475
476bool wxHtmlListBox::PhysicalCoordsToCell(wxPoint& pos, wxHtmlCell*& cell) const
477{
478    int n = HitTest(pos);
479    if ( n == wxNOT_FOUND )
480        return false;
481
482    // convert mouse coordinates to coords relative to item's wxHtmlCell:
483    pos -= GetRootCellCoords(n);
484
485    CacheItem(n);
486    cell = m_cache->Get(n);
487
488    return true;
489}
490
491size_t wxHtmlListBox::GetItemForCell(const wxHtmlCell *cell) const
492{
493    wxCHECK_MSG( cell, 0, _T("no cell") );
494
495    cell = cell->GetRootCell();
496
497    wxCHECK_MSG( cell, 0, _T("no root cell") );
498
499    // the cell's ID contains item index, see CacheItem():
500    unsigned long n;
501    if ( !cell->GetId().ToULong(&n) )
502    {
503        wxFAIL_MSG( _T("unexpected root cell's ID") );
504        return 0;
505    }
506
507    return n;
508}
509
510wxPoint
511wxHtmlListBox::CellCoordsToPhysical(const wxPoint& pos, wxHtmlCell *cell) const
512{
513    return pos + GetRootCellCoords(GetItemForCell(cell));
514}
515
516void wxHtmlListBox::OnInternalIdle()
517{
518    wxVListBox::OnInternalIdle();
519
520    if ( wxHtmlWindowMouseHelper::DidMouseMove() )
521    {
522        wxPoint pos = ScreenToClient(wxGetMousePosition());
523        wxHtmlCell *cell;
524
525        if ( !PhysicalCoordsToCell(pos, cell) )
526            return;
527
528        wxHtmlWindowMouseHelper::HandleIdle(cell, pos);
529    }
530}
531
532void wxHtmlListBox::OnMouseMove(wxMouseEvent& event)
533{
534    wxHtmlWindowMouseHelper::HandleMouseMoved();
535    event.Skip();
536}
537
538void wxHtmlListBox::OnLeftDown(wxMouseEvent& event)
539{
540    wxPoint pos = event.GetPosition();
541    wxHtmlCell *cell;
542
543    if ( !PhysicalCoordsToCell(pos, cell) )
544    {
545        event.Skip();
546        return;
547    }
548
549    if ( !wxHtmlWindowMouseHelper::HandleMouseClick(cell, pos, event) )
550    {
551        // no link was clicked, so let the listbox code handle the click (e.g.
552        // by selecting another item in the list):
553        event.Skip();
554    }
555}
556
557
558// ----------------------------------------------------------------------------
559// wxSimpleHtmlListBox
560// ----------------------------------------------------------------------------
561
562bool wxSimpleHtmlListBox::Create(wxWindow *parent, wxWindowID id,
563                                 const wxPoint& pos,
564                                 const wxSize& size,
565                                 int n, const wxString choices[],
566                                 long style,
567                                 const wxValidator& validator,
568                                 const wxString& name)
569{
570    if (!wxHtmlListBox::Create(parent, id, pos, size, style, name))
571        return false;
572
573#if wxUSE_VALIDATORS
574    SetValidator(validator);
575#endif
576    for (int i=0; i<n; i++)
577        Append(choices[i]);
578
579    return true;
580}
581
582bool wxSimpleHtmlListBox::Create(wxWindow *parent, wxWindowID id,
583                                    const wxPoint& pos,
584                                    const wxSize& size,
585                                    const wxArrayString& choices,
586                                    long style,
587                                    const wxValidator& validator,
588                                    const wxString& name)
589{
590    if (!wxHtmlListBox::Create(parent, id, pos, size, style, name))
591        return false;
592
593#if wxUSE_VALIDATORS
594    SetValidator(validator);
595#endif
596    Append(choices);
597
598    return true;
599}
600
601wxSimpleHtmlListBox::~wxSimpleHtmlListBox()
602{
603    wxASSERT(m_items.GetCount() == m_HTMLclientData.GetCount());
604    if (HasClientObjectData())
605    {
606        // clear the array of client data objects
607        for (size_t i=0; i<m_items.GetCount(); i++)
608            delete DoGetItemClientObject(i);
609    }
610
611    m_items.Clear();
612    m_HTMLclientData.Clear();
613}
614
615void wxSimpleHtmlListBox::Clear()
616{
617    m_items.Clear();
618    m_HTMLclientData.Clear();
619    UpdateCount();
620}
621
622void wxSimpleHtmlListBox::Delete(unsigned int n)
623{
624    m_items.RemoveAt(n);
625    m_HTMLclientData.RemoveAt(n);
626    UpdateCount();
627}
628
629void wxSimpleHtmlListBox::Append(const wxArrayString& strings)
630{
631    // append all given items at once
632    WX_APPEND_ARRAY(m_items, strings);
633    m_HTMLclientData.Add(NULL, strings.GetCount());
634    UpdateCount();
635}
636
637int wxSimpleHtmlListBox::DoAppend(const wxString& item)
638{
639    m_items.Add(item);
640    m_HTMLclientData.Add(NULL);
641    UpdateCount();
642    return GetCount()-1;
643}
644
645int wxSimpleHtmlListBox::DoInsert(const wxString& item, unsigned int pos)
646{
647    m_items.Insert(item, pos);
648    m_HTMLclientData.Insert(NULL, pos);
649    UpdateCount();
650    return pos;
651}
652
653void wxSimpleHtmlListBox::SetString(unsigned int n, const wxString& s)
654{
655    wxCHECK_RET( IsValid(n),
656                 wxT("invalid index in wxSimpleHtmlListBox::SetString") );
657
658    m_items[n]=s;
659    RefreshLine(n);
660}
661
662wxString wxSimpleHtmlListBox::GetString(unsigned int n) const
663{
664    wxCHECK_MSG( IsValid(n), wxEmptyString,
665                 wxT("invalid index in wxSimpleHtmlListBox::GetString") );
666
667    return m_items[n];
668}
669
670void wxSimpleHtmlListBox::UpdateCount()
671{
672    wxASSERT(m_items.GetCount() == m_HTMLclientData.GetCount());
673    wxHtmlListBox::SetItemCount(m_items.GetCount());
674
675    // very small optimization: if you need to add lot of items to
676    // a wxSimpleHtmlListBox be sure to use the
677    // wxSimpleHtmlListBox::Append(const wxArrayString&) method instead!
678    if (!this->IsFrozen())
679        RefreshAll();
680}
681
682#endif // wxUSE_HTML
683