//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make Magic (tm) cards | //| Copyright: (C) 2001 - 2010 Twan van Laarhoven and Sean Hunt | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ // ----------------------------------------------------------------------------- : Includes #include #include #include #include #include DECLARE_TYPEOF_COLLECTION(TreeList::ItemP); // ----------------------------------------------------------------------------- : TreeList : item managment void TreeList::rebuild(bool full) { if (full) initItems(); calcItemCount(); UpdateScrollbar(); Refresh(false); } bool TreeList::hasChildren(size_t item) const { return item + 1 < items.size() && items[item]->level < items[item+1]->level; } void TreeList::expand(size_t item, bool expand) { if (hasChildren(item) && items[item]->expanded != expand) { items[item]->expanded = expand; rebuild(false); } } void TreeList::expandAll(bool expand) { for (size_t item = 0 ; item < items.size() ; ++item) { if (hasChildren(item)) { items[item]->expanded = expand; } } rebuild(false); } void TreeList::select(size_t item, bool event) { if (item >= items.size() || selection == item) return; // select size_t oldpos = selection < items.size() ? items[selection]->position : 0; selection = item; size_t pos = items[selection]->position; // event if (event) { wxCommandEvent ev(wxEVT_COMMAND_LISTBOX_SELECTED, GetId()); ProcessEvent(ev); } // redraw if (pos < first_line) { ScrollToLine(pos); } else if (pos >= first_line + visible_lines_t) { ScrollToLine(pos - visible_lines_t + 1); } else { if (oldpos != NOTHING) RefreshLine(oldpos); RefreshLine(pos); } } void TreeList::calcItemCount() { // item count total_lines = 0; int visible_level = 0; FOR_EACH(i,items) { if (i->level <= visible_level) { i->position = total_lines; ++total_lines; if (i->expanded) visible_level = i->level + 1; else visible_level = i->level; } else { i->position = NOTHING; } } // update lines UInt lines = 0; FOR_EACH_REVERSE(i,items) { if (i->visible()) { i->lines = lines; lines &= (1 << i->level) - 1; lines |= 1 << i->level; } } // selection hidden? move to first visible item before it if (selection < items.size()) { for ( ; selection + 1 > 0 ; --selection) { if (items[selection]->visible()) break; // visible } if (selection >= items.size()) selection = 0; } } size_t TreeList::findItemByPos(int y) const { if (y < header_height) return false; return findItem(first_line + (y - header_height) / item_height); } size_t TreeList::findItem(size_t line, size_t start) const { for (size_t i = start ; i < items.size() ; ++i) { if (items[i]->visible() && items[i]->position >= line) return i; } return items.size(); } size_t TreeList::findLastItem(size_t start) const { for (size_t i = min(items.size(), start) - 1 ; i + 1 > 0 ; --i) { if (items[i]->visible()) return i; } return items.size(); } size_t TreeList::findParent(size_t start) const { int level = items[start]->level; for (size_t i = start - 1 ; i + 1 > 0 ; --i) { if (items[i]->visible() && items[i]->level < level) return i; } return items.size(); } // ----------------------------------------------------------------------------- : TreeList : UI TreeList::TreeList(Window* parent, int id, long style) : wxPanel(parent, id, wxDefaultPosition, wxDefaultSize, style | wxWANTS_CHARS | wxVSCROLL) , selection(NOTHING) , total_lines(0) , first_line(0) { // determine item size wxClientDC dc(this); dc.SetFont(*wxNORMAL_FONT); int h; dc.GetTextExtent(_("X"), 0, &h); item_height = h + 2; } void TreeList::onPaint(wxPaintEvent& ev) { wxBufferedPaintDC dc(this); size_t cols = columnCount(); wxRendererNative& rn = wxRendererNative::GetDefault(); // clear background wxSize cs = GetClientSize(); dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); dc.DrawRectangle(0,0,cs.x,header_height); dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); dc.DrawRectangle(0,header_height,cs.x,cs.y-header_height); // draw header dc.SetFont(*wxNORMAL_FONT); dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_BTNTEXT)); int x = 0, y = 0; for (size_t j = 0 ; j < cols ; ++j) { int w = columnWidth(j); wxRect rect(x,0,w-1,header_height-1); rn.DrawHeaderButton(this, dc, rect); dc.DrawText(columnText(j),x+3,2); x += w; } y += header_height; // draw items size_t start = findItem(first_line); size_t end = findItem(first_line + visible_lines); for (size_t i = start ; i < end ; ++i) { const Item& item = *items[i]; if (!item.visible()) continue; // invisible x = level_width * (item.level + 1); // line below dc.SetPen(lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),0.2)); dc.DrawLine(x,y+item_height-1,cs.x,y+item_height-1); // draw lines dc.SetPen(lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT),0.4)); for (int j = 0 ; j < 32 && j <= item.level ; ++j) { if (item.lines & (1 << j)) { // draw line dc.DrawLine(8 + level_width*j,y,8 + level_width*j,y+item_height); } } dc.DrawLine(x-1, y + item_height/2, x - 9, y + item_height/2); if (!(item.lines & (1 << item.level))) { dc.DrawLine(8 + level_width*item.level,y,8 + level_width*item.level,y+item_height/2+1); } // draw expand button if (hasChildren(i)) { wxRect rect(x - 13, y + (item_height - 9)/2, 9, 9); rn.DrawTreeItemButton(this, dc, rect, item.expanded ? wxCONTROL_EXPANDED : 0); } if (selection == i) { dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); dc.DrawRectangle(x, y, cs.x-x, item_height-1); } // draw text(s) for (size_t j = 0 ; j < cols ; ++j) { int w = columnWidth(j); drawItem(dc, i, j, x+1, y, selection == i); if (j == 0) x = 0; x += w; } y += item_height; } } void TreeList::onChar(wxKeyEvent& ev) { switch (ev.GetKeyCode()) { case WXK_UP: { select(findLastItem(selection)); break; } case WXK_DOWN: { select(findItem(0, selection + 1)); break; } case WXK_LEFT: { if (selection < items.size()) { if (hasChildren(selection) && items[selection]->expanded) { expand(selection, false); } else { // select parent select(findParent(selection)); } } break; } case WXK_RIGHT: { if (selection < items.size() && hasChildren(selection)) { if (items[selection]->expanded) { // select first child select(selection+1); Refresh(false); } else { expand(selection, true); } } break; } case WXK_PAGEUP: { ScrollToLine(first_line > visible_lines_t ? first_line - visible_lines_t : 0); break; } case WXK_PAGEDOWN: { ScrollToLine(first_line + visible_lines_t); break; } case WXK_HOME: { select(findItem(0)); break; } case WXK_END: { select(findLastItem(items.size())); break; } case WXK_TAB: { // send a navigation event to our parent, to select another control // we need this because of wxWANTS_CHARS wxNavigationKeyEvent nev; nev.SetDirection(!ev.ShiftDown()); GetParent()->ProcessEvent(nev); break; } } } void TreeList::onLeftDown(wxMouseEvent& ev) { size_t i = findItemByPos(ev.GetY()); if (i >= items.size()) return; int left = items[i]->level * level_width; if (hasChildren(i) && ev.GetX() >= left && ev.GetX() < left + level_width) { expand(i, !items[i]->expanded); } else { select(i); } ev.Skip(); } void TreeList::onLeftDClick(wxMouseEvent& ev) { size_t i = findItemByPos(ev.GetY()); if (i >= items.size()) return; if (hasChildren(i)) { expand(i, !items[i]->expanded); } ev.Skip(); } // ----------------------------------------------------------------------------- : TreeList : Copy of VScrolledWindow void TreeList::ScrollToLine(size_t line) { // determine the real first line to scroll to: we shouldn't scroll beyond the end line = (size_t)max((int)line, 0); line = (size_t)min((int)line, max(0, (int)(total_lines - visible_lines_t))); // nothing to do? if (line == first_line) return; first_line = line; UpdateScrollbar(); Refresh(false); } void TreeList::UpdateScrollbar() { // how many lines fit on the screen? int h = GetClientSize().y - header_height; visible_lines = (h + item_height - 1) / item_height; visible_lines_t = h / item_height; // set the scrollbar parameters to reflect this SetScrollbar(wxVERTICAL, (int)first_line, (int)visible_lines_t, (int)total_lines); } void TreeList::RefreshLine(size_t line) { if (line < first_line || line >= first_line + visible_lines) return; // calculate the rect occupied by this line on screen wxRect rect; rect.x = 0; rect.y = header_height + (int)(line - first_line) * item_height; rect.width = GetClientSize().x; rect.height = item_height; // do refresh it RefreshRect(rect, false); } void TreeList::onScroll(wxScrollWinEvent& ev) { wxEventType type = ev.GetEventType(); if (type == wxEVT_SCROLLWIN_TOP) { ScrollToLine(0); } else if (type == wxEVT_SCROLLWIN_BOTTOM) { ScrollToLine(total_lines); } else if (type == wxEVT_SCROLLWIN_LINEUP) { ScrollToLine(first_line > 0 ? first_line - 1 : 0); } else if (type == wxEVT_SCROLLWIN_LINEDOWN) { ScrollToLine(first_line + 1); } else if (type == wxEVT_SCROLLWIN_PAGEUP) { ScrollToLine(first_line > visible_lines_t ? first_line - visible_lines_t : 0); } else if (type == wxEVT_SCROLLWIN_PAGEDOWN) { ScrollToLine(first_line + visible_lines_t); } else { ScrollToLine(ev.GetPosition()); } } void TreeList::onSize(wxSizeEvent& ev) { UpdateScrollbar(); Refresh(false); ev.Skip(); } void TreeList::onMouseWheel(wxMouseEvent& ev) { int delta = -ev.GetWheelRotation() * ev.GetLinesPerAction() / ev.GetWheelDelta(); ScrollToLine(first_line + delta); } // ----------------------------------------------------------------------------- : TreeList : Event table BEGIN_EVENT_TABLE(TreeList, wxPanel) EVT_PAINT (TreeList::onPaint) EVT_SIZE (TreeList::onSize) EVT_SCROLLWIN (TreeList::onScroll) EVT_CHAR (TreeList::onChar) EVT_LEFT_DOWN (TreeList::onLeftDown) EVT_LEFT_DCLICK(TreeList::onLeftDClick) EVT_MOUSEWHEEL (TreeList::onMouseWheel) END_EVENT_TABLE()