diff --git a/include/wx/univ/inphand.h b/include/wx/univ/inphand.h index bc8c946688..b22c2bd19f 100644 --- a/include/wx/univ/inphand.h +++ b/include/wx/univ/inphand.h @@ -21,6 +21,7 @@ #include "wx/univ/renderer.h" // for wxHitTest +class WXDLLEXPORT wxListBox; class WXDLLEXPORT wxRenderer; class WXDLLEXPORT wxScrollBar; @@ -214,6 +215,14 @@ public: const wxMouseEvent& event); virtual bool HandleMouseMove(wxControl *control, const wxMouseEvent& event); + +protected: + // get the listbox item under mouse and return -1 if there is none + int HitTest(const wxListBox *listbox, const wxMouseEvent& event); + + wxRenderer *m_renderer; + + wxWindow *m_winCapture; }; #endif // _WX_UNIV_INPHAND_H_ diff --git a/include/wx/univ/listbox.h b/include/wx/univ/listbox.h index 78b12de14c..3b96c1f470 100644 --- a/include/wx/univ/listbox.h +++ b/include/wx/univ/listbox.h @@ -123,6 +123,7 @@ public: // actions void Activate(); void Select(bool sel = TRUE); + void EnsureVisible(); // get, calculating it if necessary, the number of items per page and the // height of each line @@ -187,6 +188,15 @@ private: // the number of items per page size_t m_itemsPerPage; + // if the number of items has changed we may need to show/hide the + // scrollbar + bool m_updateScrollbar, + m_showScrollbar; + + // if the current item has changed, we might need to scroll if it went out + // of the window + bool m_currentChanged; + DECLARE_EVENT_TABLE() DECLARE_DYNAMIC_CLASS(wxListBox) }; diff --git a/include/wx/univ/renderer.h b/include/wx/univ/renderer.h index 15232ea6f5..c26e071185 100644 --- a/include/wx/univ/renderer.h +++ b/include/wx/univ/renderer.h @@ -150,9 +150,6 @@ public: // the control looks "nice" if it uses the adjusted rectangle virtual void AdjustSize(wxSize *size, const wxWindow *window) = 0; - // hit testing functions - // --------------------- - // gets the bounding box for a scrollbar element for the given (by default // - current) thumb position virtual wxRect GetScrollbarRect(const wxScrollBar *scrollbar, @@ -170,6 +167,9 @@ public: virtual int PixelToScrollbar(const wxScrollBar *scrollbar, wxCoord coord) = 0; + // get the height of a listbox item from the base font height + virtual wxCoord GetListboxItemHeight(wxCoord fontHeight) = 0; + // virtual dtor for any base class virtual ~wxRenderer(); @@ -288,6 +288,8 @@ public: virtual int PixelToScrollbar(const wxScrollBar *scrollbar, wxCoord coord) { return m_renderer->PixelToScrollbar(scrollbar, coord); } + virtual wxCoord GetListboxItemHeight(wxCoord fontHeight) + { return m_renderer->GetListboxItemHeight(fontHeight); } protected: wxRenderer *m_renderer; diff --git a/samples/univ/univ.cpp b/samples/univ/univ.cpp index 2f4ea75aa8..55b4a8acef 100644 --- a/samples/univ/univ.cpp +++ b/samples/univ/univ.cpp @@ -107,6 +107,7 @@ public: protected: // event handlers void OnButton(wxCommandEvent& event); + void OnListBox(wxCommandEvent& event); void OnLeftUp(wxMouseEvent& event); private: @@ -139,6 +140,7 @@ IMPLEMENT_APP(MyUnivApp) BEGIN_EVENT_TABLE(MyUnivFrame, wxFrame) EVT_BUTTON(-1, MyUnivFrame::OnButton) + EVT_LISTBOX(-1, MyUnivFrame::OnListBox) EVT_LEFT_UP(MyUnivFrame::OnLeftUp) END_EVENT_TABLE() @@ -311,10 +313,15 @@ MyUnivFrame::MyUnivFrame(const wxString& title) _T("is one of my"), _T("really"), _T("wonderful"), - _T("examples."), + _T("examples"), }; - new wxListBox(this, -1, wxPoint(550, 10), wxDefaultSize, - WXSIZEOF(choices), choices); + wxListBox *lbox = new wxListBox(this, -1, wxPoint(550, 10), wxDefaultSize, + WXSIZEOF(choices), choices, + wxLB_MULTIPLE); + for ( int i = 0; i < 20; i++ ) + { + lbox->Append(wxString::Format(_T("entry %d"), i)); + } } void MyUnivFrame::OnButton(wxCommandEvent& event) @@ -330,6 +337,11 @@ void MyUnivFrame::OnButton(wxCommandEvent& event) } } +void MyUnivFrame::OnListBox(wxCommandEvent& event) +{ + wxLogDebug(_T("Listbox item %d selected."), event.GetInt()); +} + void MyUnivFrame::OnLeftUp(wxMouseEvent& event) { if ( event.ControlDown() ) diff --git a/src/univ/listbox.cpp b/src/univ/listbox.cpp index 4079b36812..0d314fd524 100644 --- a/src/univ/listbox.cpp +++ b/src/univ/listbox.cpp @@ -65,9 +65,12 @@ void wxListBox::Init() // no items hence no current item m_current = -1; + m_currentChanged = FALSE; // no need to update anything initially m_updateCount = 0; + m_updateScrollbar = + m_showScrollbar = FALSE; } bool wxListBox::Create(wxWindow *parent, @@ -83,6 +86,8 @@ bool wxListBox::Create(wxWindow *parent, if ( !wxControl::Create(parent, id, pos, size, style, wxDefaultValidator, name) ) return FALSE; + SetWindow(this); + SetBackgroundColour(*wxWHITE); if ( style & wxLB_SORT ) @@ -108,6 +113,8 @@ int wxListBox::DoAppend(const wxString& item) size_t index = m_strings.Add(item); m_clientData.Insert(NULL, index); + m_updateScrollbar = TRUE; + RefreshItem(m_strings.GetCount() - 1); return index; @@ -126,6 +133,8 @@ void wxListBox::DoInsertItems(const wxArrayString& items, int pos) m_clientData.Insert(NULL, pos + n); } + m_updateScrollbar = TRUE; + RefreshItems(pos, count); } @@ -139,11 +148,11 @@ void wxListBox::DoSetItems(const wxArrayString& items, void **clientData) for ( size_t n = 0; n < count; n++ ) { size_t index = m_strings.Add(items[n]); - - if ( clientData ) - m_clientData.Insert(clientData[n], index); + m_clientData.Insert(clientData ? clientData[n] : NULL, index); } + m_updateScrollbar = TRUE; + RefreshAll(); } @@ -171,6 +180,8 @@ void wxListBox::Clear() { DoClear(); + m_updateScrollbar = TRUE; + RefreshAll(); } @@ -187,6 +198,8 @@ void wxListBox::Delete(int n) m_clientData.RemoveAt(n); + m_updateScrollbar = TRUE; + RefreshItems(n, GetCount() - n); } @@ -266,11 +279,14 @@ int wxListBox::GetSelections(wxArrayInt& selections) const void wxListBox::Refresh(bool eraseBackground, const wxRect *rect) { - // do nothing here if we didn't call it ourselves - if ( m_updateCount ) - { - wxControl::Refresh(eraseBackground, rect); - } + if ( rect ) + wxLogTrace(_T("listbox"), _T("Refreshing (%d, %d)-(%d, %d)"), + rect->x, rect->y, + rect->x + rect->width, rect->y + rect->height); + else + wxLogTrace(_T("listbox"), _T("Refreshing all")); + + wxControl::Refresh(eraseBackground, rect); } void wxListBox::RefreshItems(int from, int count) @@ -339,6 +355,29 @@ void wxListBox::RefreshAll() void wxListBox::OnIdle(wxIdleEvent& event) { + if ( m_updateScrollbar ) + { + // is our height enough to show all items? + wxCoord lineHeight = GetLineHeight(); + bool showScrollbar = GetCount()*lineHeight > GetClientSize().y; + if ( showScrollbar != m_showScrollbar ) + { + // TODO: support for horz scrollbar + SetScrollbars(0, lineHeight, 0, GetCount()); + + m_showScrollbar = showScrollbar; + } + + m_updateScrollbar = FALSE; + } + + if ( m_currentChanged ) + { + EnsureVisible(); + + m_currentChanged = FALSE; + } + if ( m_updateCount ) { // only refresh the items which must be refreshed @@ -375,6 +414,13 @@ void wxListBox::OnIdle(wxIdleEvent& event) void wxListBox::DoDraw(wxControlRenderer *renderer) { // draw the border first + if ( m_showScrollbar ) + { + // we need to draw a border around the client area + renderer->GetRect().width -= GetScrollbar(wxVERTICAL)->GetSize().x; + } + + // the base class version does it for us wxControl::DoDraw(renderer); // adjust the DC to account for scrolling @@ -385,9 +431,9 @@ void wxListBox::DoDraw(wxControlRenderer *renderer) #if 0 int y; GetViewStart(NULL, &y); -#endif wxCoord lineHeight = GetLineHeight(); wxRegion rgnUpdate = GetUpdateRegion(); + //dc.SetClippingRegion(rgnUpdate); wxRect rectUpdate = rgnUpdate.GetBox(); size_t itemFirst = rectUpdate.GetTop() / lineHeight, itemLast = (rectUpdate.GetBottom() + lineHeight - 1) / lineHeight, @@ -398,11 +444,15 @@ void wxListBox::DoDraw(wxControlRenderer *renderer) if ( itemLast > itemMax ) itemLast = itemMax; +#else + size_t itemFirst = 0, + itemLast = m_strings.GetCount(); +#endif // do draw them wxLogTrace(_T("listbox"), _T("Repainting items %d..%d"), itemFirst, itemLast); - dc.SetClippingRegion(rgnUpdate); + renderer->DrawItems(this, itemFirst, itemLast); } @@ -424,7 +474,8 @@ bool wxListBox::SetFont(const wxFont& font) void wxListBox::CalcItemsPerPage() { - m_lineHeight = wxClientDC(this).GetCharHeight() + 2; + m_lineHeight = GetRenderer()-> + GetListboxItemHeight(wxClientDC(this).GetCharHeight()); m_itemsPerPage = GetClientSize().y / m_lineHeight; } @@ -528,13 +579,56 @@ bool wxListBox::SendEvent(int item, wxEventType type) void wxListBox::SetCurrentItem(int n) { - if ( m_current != -1 ) - RefreshItem(n); + if ( n != m_current ) + { + if ( m_current != -1 ) + RefreshItem(n); - m_current = n; + m_current = n; - if ( m_current != -1 ) - RefreshItem(n); + if ( m_current != -1 ) + { + if ( !HasMultipleSelection() ) + { + // for a single selection listbox, the current item is always + // the one selected + Select(TRUE); + } + + m_currentChanged = TRUE; + + RefreshItem(n); + } + } + //else: nothing to do +} + +void wxListBox::EnsureVisible() +{ + if ( !m_showScrollbar ) + { + // nothing to do - everything is shown anyhow + return; + } + + int first; + GetViewStart(0, &first); + if ( first > m_current ) + { + // we need to scroll upwards, so make the current item appear on top + // of the shown range + Scroll(0, m_current); + } + else + { + int last = first + GetClientSize().y / GetLineHeight() - 1; + if ( last < m_current ) + { + // scroll down: the current item appears at the bottom of the + // range + Scroll(0, m_current - (last - first)); + } + } } void wxListBox::ChangeCurrent(int diff) @@ -625,6 +719,17 @@ bool wxListBox::PerformAction(const wxControlAction& action, wxStdListboxInputHandler::wxStdListboxInputHandler(wxInputHandler *handler) : wxStdInputHandler(handler) { + m_winCapture = NULL; +} + +int wxStdListboxInputHandler::HitTest(const wxListBox *lbox, + const wxMouseEvent& event) +{ + int y; + lbox->CalcUnscrolledPosition(0, event.GetPosition().y, NULL, &y); + int item = y / lbox->GetLineHeight(); + + return item < lbox->GetCount() ? item : -1; } bool wxStdListboxInputHandler::HandleKey(wxControl *control, @@ -664,14 +769,53 @@ bool wxStdListboxInputHandler::HandleKey(wxControl *control, bool wxStdListboxInputHandler::HandleMouse(wxControl *control, const wxMouseEvent& event) { - if ( event.LeftDown() ) + // single and extended listboxes behave similarly with respect to the + // mouse events: for both of them clicking the item selects or toggles it, + // but multiple selection listboxes are different: the item is focused + // when clicked and only toggled when the button is released + if ( ((control->GetWindowStyle() & wxLB_MULTIPLE) && event.ButtonUp()) + || event.ButtonDown() || event.LeftDClick() ) { wxListBox *lbox = wxStaticCast(control, wxListBox); - int item = event.GetPosition().y / lbox->GetLineHeight(); - if ( item < lbox->GetCount() ) + int item = HitTest(lbox, event); + if ( item != -1 ) { - lbox->PerformAction(wxACTION_LISTBOX_SETFOCUS, item); - lbox->PerformAction(wxACTION_LISTBOX_TOGGLE); + wxControlAction action; + if ( event.ButtonUp() ) + { + m_winCapture->ReleaseMouse(); + m_winCapture = NULL; + + action = wxACTION_LISTBOX_TOGGLE; + } + else if ( event.ButtonDown() ) + { + if ( lbox->HasMultipleSelection() ) + { + if ( lbox->GetWindowStyle() & wxLB_MULTIPLE ) + { + // capture the mouse to track the selected item + m_winCapture = lbox; + m_winCapture->CaptureMouse(); + + action = wxACTION_LISTBOX_SETFOCUS; + } + else + { + action = wxACTION_LISTBOX_TOGGLE; + } + } + else // single selection + { + action = wxACTION_LISTBOX_SELECT; + } + } + else // event.LeftDClick() + { + action = wxACTION_LISTBOX_ACTIVATE; + } + + lbox->PerformAction(action, item); return TRUE; } @@ -684,8 +828,29 @@ bool wxStdListboxInputHandler::HandleMouse(wxControl *control, bool wxStdListboxInputHandler::HandleMouseMove(wxControl *control, const wxMouseEvent& event) { - // we don't react to this at all - return FALSE; + if ( !m_winCapture || (event.GetEventObject() != m_winCapture) ) + { + // we don't react to this + return FALSE; + } + + // TODO: not yet... should track the mouse outside and start an auto + // scroll timer - but this should be probably done in + // wxScrolledWindow itself (?) + if ( !event.Moving() ) + return FALSE; + + wxListBox *lbox = wxStaticCast(control, wxListBox); + int item = HitTest(lbox, event); + if ( item == -1 ) + { + // mouse is below the last item + return FALSE; + } + + lbox->PerformAction(wxACTION_LISTBOX_SETFOCUS, item); + + return TRUE; } #endif // wxUSE_LISTBOX diff --git a/src/univ/renderer.cpp b/src/univ/renderer.cpp index f7bad7f585..50220c1df3 100644 --- a/src/univ/renderer.cpp +++ b/src/univ/renderer.cpp @@ -578,14 +578,22 @@ void wxControlRenderer::DrawLine(wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2) void wxControlRenderer::DrawItems(const wxListBox *lbox, size_t itemFirst, size_t itemLast) { + // prepare for the drawing: calc the initial position wxCoord lineHeight = lbox->GetLineHeight(); + int lines, pixelsPerLine; + lbox->GetViewStart(NULL, &lines); + lbox->GetScrollPixelsPerUnit(NULL, &pixelsPerLine); wxRect rect = m_rect; - rect.y += itemFirst*lineHeight; + rect.y += itemFirst*lineHeight - lines*pixelsPerLine; rect.height = lineHeight; + + // an item should have the focused rect only when the app has focus, so + // make sure that we never set wxCONTROL_FOCUSED flag if it doesn't + int itemCurrent = wxTheApp->IsActive() ? lbox->GetCurrentItem() : -1; for ( size_t n = itemFirst; n < itemLast; n++ ) { int flags = 0; - if ( (int)n == lbox->GetCurrentItem() ) + if ( (int)n == itemCurrent ) flags |= wxCONTROL_FOCUSED; if ( lbox->IsSelected(n) ) flags |= wxCONTROL_SELECTED; diff --git a/src/univ/themes/gtk.cpp b/src/univ/themes/gtk.cpp index d7d75f4adf..e974084605 100644 --- a/src/univ/themes/gtk.cpp +++ b/src/univ/themes/gtk.cpp @@ -111,6 +111,8 @@ public: virtual wxCoord ScrollbarToPixel(const wxScrollBar *scrollbar, int thumbPos = -1); virtual int PixelToScrollbar(const wxScrollBar *scrollbar, wxCoord coord); + virtual wxCoord GetListboxItemHeight(wxCoord fontHeight) + { return fontHeight + 2; } protected: // DrawBackground() helpers @@ -393,7 +395,7 @@ wxGTKRenderer::wxGTKRenderer(const wxColourScheme *scheme) { // init data m_scheme = scheme; - m_sizeScrollbarArrow = wxSize(16, 14); + m_sizeScrollbarArrow = wxSize(15, 14); // init pens m_penBlack = wxPen(scheme->Get(wxColourScheme::SHADOW_DARK), 0, wxSOLID); @@ -522,8 +524,8 @@ void wxGTKRenderer::DrawBorder(wxDC& dc, switch ( border ) { case wxBORDER_SUNKEN: - DrawShadedRect(dc, &rect, m_penDarkGrey, m_penHighlight); - DrawShadedRect(dc, &rect, m_penBlack, m_penLightGrey); + DrawAntiShadedRect(dc, &rect, m_penDarkGrey, m_penHighlight); + DrawAntiShadedRect(dc, &rect, m_penBlack, m_penLightGrey); break; case wxBORDER_STATIC: @@ -721,6 +723,13 @@ void wxGTKRenderer::DrawItem(wxDC& dc, dc.SetTextForeground(m_scheme->Get(wxColourScheme::HIGHLIGHT_TEXT)); } + if ( flags & wxCONTROL_FOCUSED ) + { + dc.SetBrush(*wxTRANSPARENT_BRUSH); + wxRect rectFocus = rect; + DrawRect(dc, &rectFocus, m_penBlack); + } + wxRect rectText = rect; rectText.x += 2; rectText.y++; @@ -784,6 +793,8 @@ void wxGTKRenderer::DrawArrowBorder(wxDC& dc, rect2.Inflate(-1); rectInner.Inflate(-2); + DoDrawBackground(dc, m_scheme->Get(wxColourScheme::SCROLLBAR), *rect); + // find the side not to draw and also adjust the rectangles to compensate // for it wxDirection sideToOmit; @@ -792,7 +803,7 @@ void wxGTKRenderer::DrawArrowBorder(wxDC& dc, case wxUP: sideToOmit = wxDOWN; rect2.height += 1; - rectInner.height += 2; + rectInner.height += 1; break; case wxDOWN: @@ -800,13 +811,13 @@ void wxGTKRenderer::DrawArrowBorder(wxDC& dc, rect2.y -= 1; rect2.height += 1; rectInner.y -= 2; - rectInner.height += 2; + rectInner.height += 1; break; case wxLEFT: sideToOmit = wxRIGHT; rect2.width += 1; - rectInner.width += 2; + rectInner.width += 1; break; case wxRIGHT: @@ -814,7 +825,7 @@ void wxGTKRenderer::DrawArrowBorder(wxDC& dc, rect2.x -= 1; rect2.width += 1; rectInner.x -= 2; - rectInner.width += 2; + rectInner.width += 1; break; default: @@ -844,7 +855,6 @@ void wxGTKRenderer::DrawArrowBorder(wxDC& dc, } *rect = rectInner; - DoDrawBackground(dc, m_scheme->Get(wxColourScheme::SCROLLBAR), rectInner); } // gtk_default_draw_arrow() takes ~350 lines and we can't do much better here diff --git a/src/univ/themes/win32.cpp b/src/univ/themes/win32.cpp index 0e9813252d..6a280e387d 100644 --- a/src/univ/themes/win32.cpp +++ b/src/univ/themes/win32.cpp @@ -128,6 +128,8 @@ public: virtual wxCoord ScrollbarToPixel(const wxScrollBar *scrollbar, int thumbPos = -1); virtual int PixelToScrollbar(const wxScrollBar *scrollbar, wxCoord coord); + virtual wxCoord GetListboxItemHeight(wxCoord fontHeight) + { return fontHeight + 2; } protected: // common part of DrawLabel() and DrawItem() diff --git a/src/univ/winuniv.cpp b/src/univ/winuniv.cpp index ab6183cec8..35d8301112 100644 --- a/src/univ/winuniv.cpp +++ b/src/univ/winuniv.cpp @@ -289,22 +289,37 @@ void wxWindow::SetScrollbar(int orient, bool refresh) { wxScrollBar *scrollbar = GetScrollbar(orient); - if ( !scrollbar ) + if ( range ) { - // create it - scrollbar = new wxScrollBar(this, -1, - wxDefaultPosition, wxDefaultSize, - orient & wxVERTICAL ? wxSB_VERTICAL - : wxSB_HORIZONTAL); - if ( orient & wxVERTICAL ) - m_scrollbarVert = scrollbar; - else - m_scrollbarHorz = scrollbar; + if ( !scrollbar ) + { + // create it + scrollbar = new wxScrollBar(this, -1, + wxDefaultPosition, wxDefaultSize, + orient & wxVERTICAL ? wxSB_VERTICAL + : wxSB_HORIZONTAL); + if ( orient & wxVERTICAL ) + m_scrollbarVert = scrollbar; + else + m_scrollbarHorz = scrollbar; - PositionScrollbars(); + PositionScrollbars(); + } + + scrollbar->SetScrollbar(pos, thumb, range, thumb, refresh); } + else // no range means no scrollbar + { + if ( scrollbar ) + { + delete scrollbar; - scrollbar->SetScrollbar(pos, thumb, range, thumb, refresh); + if ( orient & wxVERTICAL ) + m_scrollbarVert = NULL; + else + m_scrollbarHorz = NULL; + } + } } void wxWindow::SetScrollPos(int orient, int pos, bool refresh)