diff options
Diffstat (limited to 'wx+')
-rw-r--r-- | wx+/file_drop.h | 3 | ||||
-rw-r--r-- | wx+/graph.cpp | 10 | ||||
-rw-r--r-- | wx+/graph.h | 11 | ||||
-rw-r--r-- | wx+/grid.cpp | 553 | ||||
-rw-r--r-- | wx+/grid.h | 100 | ||||
-rw-r--r-- | wx+/image_tools.h | 50 | ||||
-rw-r--r-- | wx+/serialize.h | 538 | ||||
-rw-r--r-- | wx+/zlib_wrap.cpp | 2 | ||||
-rw-r--r-- | wx+/zlib_wrap.h | 2 |
9 files changed, 476 insertions, 793 deletions
diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 0e9ba538..cdb19f4b 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -91,8 +91,7 @@ private: { //create a custom event on drop window: execute event after file dropping is completed! (after mouse is released) FileDropEvent evt(filenames, dropWindow_, wxPoint(x, y)); - auto handler = dropWindow_.GetEventHandler(); - if (handler) + if (wxEvtHandler* handler = dropWindow_.GetEventHandler()) handler->AddPendingEvent(evt); } return true; diff --git a/wx+/graph.cpp b/wx+/graph.cpp index fd68b548..6ba794b0 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -96,7 +96,7 @@ void drawYLabel(wxDC& dc, double& yMin, double& yMax, const wxRect& clientArea, if (clientArea.GetHeight() <= 0 || clientArea.GetWidth() <= 0) return; - int optimalBlockHeight = 3 * dc.GetMultiLineTextExtent(L"1").GetHeight();; + int optimalBlockHeight = 3 * dc.GetMultiLineTextExtent(L"1").GetHeight(); double valRangePerBlock = (yMax - yMin) * optimalBlockHeight / clientArea.GetHeight(); valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); @@ -244,7 +244,7 @@ Graph2D::Graph2D(wxWindow* parent, wxPanel(parent, winid, pos, size, style, name) { Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxEventHandler(Graph2D::onRefreshRequired), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (Graph2D::onSizeEvent ), nullptr, this); //http://wiki.wxwidgets.org/Flicker-Free_Drawing Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Graph2D::onEraseBackGround), nullptr, this); @@ -291,9 +291,9 @@ void Graph2D::OnMouseLeftUp(wxMouseEvent& event) if (activeSel->getStartPos() != activeSel->refCurrentPos()) //if it's just a single mouse click: discard selection { //fire off GraphSelectEvent - GraphSelectEvent evt(activeSel->refSelection()); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->AddPendingEvent(evt); + GraphSelectEvent selEvent(activeSel->refSelection()); + if (wxEvtHandler* handler = GetEventHandler()) + handler->AddPendingEvent(selEvent); oldSel.push_back(activeSel->refSelection()); } diff --git a/wx+/graph.h b/wx+/graph.h index db52753b..70da5dc3 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -279,20 +279,15 @@ private: void OnMouseLeftUp (wxMouseEvent& event); void OnMouseCaptureLost(wxMouseCaptureLostEvent& event); - void onPaintEvent(wxPaintEvent& evt) + void onPaintEvent(wxPaintEvent& event) { wxAutoBufferedPaintDC dc(this); //this one happily fucks up for RTL layout by not drawing the first column (x = 0)! //wxPaintDC dc(this); render(dc); } - void onRefreshRequired(wxEvent& evt) - { - Refresh(); - evt.Skip(); - } - - void onEraseBackGround(wxEraseEvent& evt) {} + void onSizeEvent(wxSizeEvent& event) { Refresh(); event.Skip(); } + void onEraseBackGround(wxEraseEvent& event) {} void render(wxDC& dc) const; diff --git a/wx+/grid.cpp b/wx+/grid.cpp index e1208109..33419c8e 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -18,6 +18,7 @@ #include <zen/scope_guard.h> #include <zen/utf.h> #include "format_unit.h" +#include "image_tools.h" #ifdef FFS_LINUX #include <gtk/gtk.h> @@ -26,8 +27,8 @@ using namespace zen; -wxColor zen::getColorSelectionGradientFrom() { return wxColor(137, 172, 255); } //blue: H:158 S:255 V:196 -wxColor zen::getColorSelectionGradientTo () { return wxColor(225, 234, 255); } // H:158 S:255 V:240 +wxColor zen::getColorSelectionGradientFrom() { return wxColor(137, 172, 255); } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 +wxColor zen::getColorSelectionGradientTo () { return wxColor(225, 234, 255); } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) { @@ -38,16 +39,17 @@ void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) namespace { -//------------ Grid Constants ------------------------------------------------------------------------------------ +//------------ Grid Constants -------------------------------- const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value as Explorer! const int DEFAULT_ROW_HEIGHT = 20; -const int DEFAULT_COL_LABEL_HEIGHT = 25; +const int DEFAULT_COL_LABEL_HEIGHT = 24; const int COLUMN_BORDER_LEFT = 4; //for left-aligned text const int COLUMN_LABEL_BORDER = COLUMN_BORDER_LEFT; const int COLUMN_MOVE_DELAY = 5; //unit: [pixel] (from Explorer) const int COLUMN_MIN_WIDTH = 40; //only honored when resizing manually! const int ROW_LABEL_BORDER = 3; const int COLUMN_RESIZE_TOLERANCE = 6; //unit [pixel] +const int COLUMN_FILL_GAP_TOLERANCE = 10; //enlarge column to fill full width when resizing const wxColor COLOR_SELECTION_GRADIENT_NO_FOCUS_FROM = wxColour(192, 192, 192); //light grey wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW); const wxColor COLOR_SELECTION_GRADIENT_NO_FOCUS_TO = wxColour(228, 228, 228); @@ -62,7 +64,7 @@ const wxColor COLOR_LABEL_GRADIENT_TO_FOCUS = COLOR_LABEL_GRADIENT_TO; wxColor getColorMainWinBackground() { return wxListBox::GetClassDefaultAttributes().colBg; } //cannot be initialized statically on wxGTK! const wxColor colorGridLine = wxColour(192, 192, 192); //light grey -//---------------------------------------------------------------------------------------------------------------- +//------------------------------------------------------------ //a fix for a poor wxWidgets implementation (wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active) @@ -328,7 +330,7 @@ public: parent_(parent) { Connect(wxEVT_PAINT, wxPaintEventHandler(SubWindow::onPaintEvent), nullptr, this); - Connect(wxEVT_SIZE, wxEventHandler(SubWindow::onSizeEvent), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (SubWindow::onSizeEvent), nullptr, this); //http://wiki.wxwidgets.org/Flicker-Free_Drawing Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(SubWindow::onEraseBackGround), nullptr, this); @@ -411,7 +413,7 @@ private: virtual void onKeyUp (wxKeyEvent& event) { event.Skip(); } virtual void onKeyDown(wxKeyEvent& event) { event.Skip(); } - void onPaintEvent(wxPaintEvent& evt) + void onPaintEvent(wxPaintEvent& event) { //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! BufferedPaintDC dc(*this, buffer); @@ -423,13 +425,13 @@ private: render(dc, iter.GetRect()); } - void onSizeEvent(wxEvent& evt) + void onSizeEvent(wxSizeEvent& event) { Refresh(); - evt.Skip(); + event.Skip(); } - void onEraseBackGround(wxEraseEvent& evt) {} + void onEraseBackGround(wxEraseEvent& event) {} Grid& parent_; std::unique_ptr<wxBitmap> buffer; @@ -595,12 +597,15 @@ class ColumnResizing { public: ColumnResizing(wxWindow& wnd, size_t col, size_t compPos, ptrdiff_t startWidth, int clientPosX) : - wnd_(wnd), - col_(col), - compPos_(compPos), - startWidth_(startWidth), - clientPosX_(clientPosX) { wnd_.CaptureMouse(); } - ~ColumnResizing() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } + wnd_(wnd), col_(col), compPos_(compPos), startWidth_(startWidth), clientPosX_(clientPosX) + { + wnd_.CaptureMouse(); + } + ~ColumnResizing() + { + if (wnd_.HasCapture()) + wnd_.ReleaseMouse(); + } size_t getColumn () const { return col_; } size_t getComponentPos() const { return compPos_; } @@ -674,7 +679,7 @@ private: wxPoint labelAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0)).x, 0); //client coordinates - std::vector<std::vector<VisibleColumn>> compAbsWidths = refParent().getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = refParent().getColWidths(); //resolve stretched widths for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) { @@ -725,17 +730,9 @@ private: { if (action->wantResize) { - if (event.LeftDClick()) //auto-size visible range on double-click - { - const auto bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error - if (bestWidth >= 0) - refParent().setColWidth(action->col, action->compPos, std::max<ptrdiff_t>(COLUMN_MIN_WIDTH, bestWidth)); - } - else - { - if (Opt<size_t> colWidth = refParent().getAbsoluteWidth(action->col, action->compPos)) + if (!event.LeftDClick()) //double-clicks never seem to arrive here; why is this checked at all??? + if (Opt<ptrdiff_t> colWidth = refParent().getColWidth(action->col, action->compPos)) activeResizing.reset(new ColumnResizing(*this, action->col, action->compPos, *colWidth, event.GetPosition().x)); - } } else //a move or single click activeMove.reset(new ColumnMove(*this, action->col, action->compPos, event.GetPosition().x)); @@ -786,20 +783,17 @@ private: virtual void onMouseLeftDouble(wxMouseEvent& event) { - const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()); - if (action && action->wantResize) - { - //auto-size visible range on double-click - const auto bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error - if (bestWidth >= 0) + if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) + if (action->wantResize) { - const auto newWidth = std::max<ptrdiff_t>(COLUMN_MIN_WIDTH, bestWidth); - refParent().setColWidth(action->col, action->compPos, newWidth); - - if (const Opt<ColumnType> colType = refParent().colToType(action->col, action->compPos)) - sendEventNow(GridColumnResizeEvent(newWidth, *colType, action->compPos)); //notify column resize + //auto-size visible range on double-click + const ptrdiff_t bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error + if (bestWidth >= 0) + { + refParent().setColWidthAndNotify(bestWidth, action->col, action->compPos); + refParent().Refresh(); //refresh main grid as well! + } } - } event.Skip(); } @@ -810,19 +804,17 @@ private: const auto col = activeResizing->getColumn(); const auto compPos = activeResizing->getComponentPos(); - if (Opt<size_t> colWidth = refParent().getAbsoluteWidth(col, compPos)) - { - const size_t newWidth = std::max<ptrdiff_t>(COLUMN_MIN_WIDTH, activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX()); - if (newWidth != *colWidth) - { - refParent().setColWidth(col, compPos, newWidth); + const ptrdiff_t newWidth = activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX(); - if (const Opt<ColumnType> colType = refParent().colToType(col, compPos)) - sendEventNow(GridColumnResizeEvent(static_cast<int>(newWidth), *colType, compPos)); //notify column resize + //set width tentatively + refParent().setColWidthAndNotify(newWidth, col, compPos); - refParent().Refresh(); - } - } + //check if there's a small gap after last column, if yes, fill it + int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); + if (std::abs(gapWidth) < COLUMN_FILL_GAP_TOLERANCE) + refParent().setColWidthAndNotify(newWidth + gapWidth, col, compPos); + + refParent().Refresh(); //refresh columns on main grid as well! } else if (activeMove) { @@ -982,11 +974,11 @@ private: wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates - std::vector<std::vector<VisibleColumn>> compAbsWidths = refParent().getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = refParent().getColWidths(); //resolve stretched widths for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) { const ptrdiff_t compWidth = std::accumulate(iterComp->begin(), iterComp->end(), static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const VisibleColumn& vc) { return sum + vc.width_; }); + [](ptrdiff_t sum, const ColumnWidth& cw) { return sum + cw.width_; }); const size_t compPos = iterComp - compAbsWidths.begin(); if (auto prov = refParent().getDataProvider(compPos)) @@ -1058,7 +1050,7 @@ private: void onMouseDown(wxMouseEvent& event) //handle left and right mouse button clicks (almost) the same { - if (FindFocus() != this) //doesn't seem to happen automatically for right mouse button + if (wxWindow::FindFocus() != this) //doesn't seem to happen automatically for right mouse button SetFocus(); const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); @@ -1115,9 +1107,9 @@ private: const auto rowTo = activeSelection->getCurrentRow(); const auto compPos = activeSelection->getComponentPos(); const bool positive = activeSelection->isPositiveSelect(); - refParent().selectRange(rowFrom, rowTo, compPos, positive); cursor.first = activeSelection->getCurrentRow(); //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys + refParent().selectRange(rowFrom, rowTo, compPos, positive); activeSelection.reset(); } @@ -1425,8 +1417,7 @@ private: //=> we cannot use CalcUnscrolledPosition() here which gives the wrong/outdated value!!! //=> we need to update asynchronously: wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->AddPendingEvent(scrollEvent); + AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll() } void updateAfterScroll(wxCommandEvent&) @@ -1453,8 +1444,8 @@ Grid::Grid(wxWindow* parent, const wxSize& size, long style, const wxString& name) : wxScrolledWindow(parent, id, pos, size, style | wxWANTS_CHARS, name), - showScrollbarX(true), - showScrollbarY(true), + showScrollbarX(SB_SHOW_AUTOMATIC), + showScrollbarY(SB_SHOW_AUTOMATIC), colLabelHeight(DEFAULT_COL_LABEL_HEIGHT), drawRowLabel(true), comp(1), @@ -1462,7 +1453,7 @@ Grid::Grid(wxWindow* parent, { Connect(wxEVT_PAINT, wxPaintEventHandler(Grid::onPaintEvent ), nullptr, this); Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Grid::onEraseBackGround), nullptr, this); - Connect(wxEVT_SIZE, wxEventHandler (Grid::onSizeEvent ), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (Grid::onSizeEvent ), nullptr, this); cornerWin_ = new CornerWin (*this); // rowLabelWin_ = new RowLabelWin(*this); //owership handled by "this" @@ -1480,67 +1471,129 @@ Grid::Grid(wxWindow* parent, void Grid::updateWindowSizes(bool updateScrollbar) { - /* We have to deal with a nasty circular dependency: - + /* We have to deal with TWO nasty circular dependencies: + 1. + rowLabelWidth + /|\ + mainWin::client width + /|\ + SetScrollbars -> show/hide horizontal scrollbar depending on client width + /|\ + mainWin::client height -> possibly trimmed by horizontal scrollbars + /|\ + rowLabelWidth + + 2. mainWin_->GetClientSize() - /|\ + /|\ SetScrollbars -> show/hide scrollbars depending on whether client size is big enough - /|\ - GetClientRect(); -> possibly trimmed by scrollbars - /|\ - mainWin_->GetClientSize() -> also trimmed, since it's a sub-window ! + /|\ + GetClientSize(); -> possibly trimmed by scrollbars + /|\ + mainWin_->GetClientSize() -> also trimmed, since it's a sub-window! */ - //update scrollbars: showing/hiding scrollbars changes client size! - if (updateScrollbar) + //break this vicious circle: + + //1. calculate row label width independent from scrollbars + const int mainWinHeightGross = std::max(GetSize().GetHeight() - colLabelHeight, 0); //independent from client sizes and scrollbars! + const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // + + int rowLabelWidth = 0; + if (drawRowLabel && logicalHeight > 0) { - //help SetScrollbars() do a better job - //mainWin_->SetSize(std::max(0, GetSize().GetWidth () - rowLabelWidth), -> not working! - // std::max(0, GetSize().GetHeight() - colLabelHeight)); + ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; + ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; + numeric::confine<ptrdiff_t>(yFrom, 0, logicalHeight - 1); + numeric::confine<ptrdiff_t>(yTo, 0, logicalHeight - 1); + + const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom); + const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo); + if (rowFrom >= 0 && rowTo >= 0) + rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo); + } - int scrollPosX = 0; - int scrollPosY = 0; - GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position + auto getMainWinSize = [&](const wxSize& clientSize) { return wxSize(std::max(0, clientSize.GetWidth() - rowLabelWidth), std::max(0, clientSize.GetHeight() - colLabelHeight)); }; - const int pixPerScrollUnitY = rowLabelWin_->getRowHeight(); - const int pixPerScrollUnitX = pixPerScrollUnitY; + auto setScrollbars2 = [&](int logWidth, int logHeight) //replace SetScrollbars, which loses precision to pixelsPerUnitX for some brain-dead reason + { + int ppsuX = 0; //pixel per scroll unit + int ppsuY = 0; + GetScrollPixelsPerUnit(&ppsuX, &ppsuY); - const int scrollUnitsX = std::ceil(static_cast<double>( getMinAbsoluteWidthTotal()) / pixPerScrollUnitX); - const int scrollUnitsY = std::ceil(static_cast<double>(rowLabelWin_->getLogicalHeight()) / pixPerScrollUnitY); + const int ppsuNew = rowLabelWin_->getRowHeight(); + if (ppsuX != ppsuNew || ppsuY != ppsuNew) //support polling! + SetScrollRate(ppsuNew, ppsuNew); //internally calls AdjustScrollbars()! - SetScrollbars(pixPerScrollUnitX, pixPerScrollUnitY, //another abysmal wxWidgets design decision: why is precision needlessly reduced to "pixelsPerUnit"???? - scrollUnitsX, scrollUnitsY, - scrollPosX, scrollPosY); - } + mainWin_->SetVirtualSize(logWidth, logHeight); + AdjustScrollbars(); //lousy wxWidgets design decision: internally calls mainWin_->GetClientSize() without considering impact of scrollbars! + //Attention: setting scrollbars triggers *synchronous* resize event if scrollbars are shown or hidden! => updateWindowSizes() recursion! (Windows) + }; - const wxRect clientRect = GetClientRect(); + //2. update managed windows' sizes: just assume scrollbars are already set correctly, even if they may not be (yet)! + //this ensures mainWin_->SetVirtualSize() and AdjustScrollbars() are working with the correct main window size, unless sb change later, which triggers a recalculation anyway! + const wxSize mainWinSize = getMainWinSize(GetClientSize()); + + cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight); + rowLabelWin_->SetSize(0, colLabelHeight, rowLabelWidth, mainWinSize.GetHeight()); + colLabelWin_->SetSize(rowLabelWidth, 0, mainWinSize.GetWidth(), colLabelHeight); + mainWin_ ->SetSize(rowLabelWidth, colLabelHeight, mainWinSize.GetWidth(), mainWinSize.GetHeight()); - const int mainWinHeight = std::max(0, clientRect.height - colLabelHeight); + //avoid flicker in wxWindowMSW::HandleSize() when calling ::EndDeferWindowPos() where the sub-windows are moved only although they need to be redrawn! + colLabelWin_->Refresh(); + mainWin_ ->Refresh(); - int rowLabelWidth = 0; //calculate optimal row label width - if (drawRowLabel) + //3. update scrollbars: "guide wxScrolledHelper to not screw up too much" + if (updateScrollbar) { - const auto heightTotal = rowLabelWin_->getLogicalHeight(); - if (heightTotal > 0) + const int mainWinWidthGross = getMainWinSize(GetSize()).GetWidth(); + + if (logicalHeight <= mainWinHeightGross && + getColWidthsSum(mainWinWidthGross) <= mainWinWidthGross && + //this special case needs to be considered *only* when both scrollbars are flexible: + showScrollbarX == SB_SHOW_AUTOMATIC && + showScrollbarY == SB_SHOW_AUTOMATIC) + setScrollbars2(0, 0); //no scrollbars required at all! -> wxScrolledWindow requires active help to detect this special case! + else { - ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; - ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeight - 1)).y ; - numeric::confine<ptrdiff_t>(yFrom, 0, heightTotal - 1); - numeric::confine<ptrdiff_t>(yTo, 0, heightTotal - 1); - - const ptrdiff_t rowFrom = rowLabelWin_->getRowAtPos(yFrom); - const ptrdiff_t rowTo = rowLabelWin_->getRowAtPos(yTo); - if (rowFrom >= 0 && rowTo >= 0) - rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo); + const int logicalWidthTmp = getColWidthsSum(mainWinSize.GetWidth()); //assuming vertical scrollbar stays as it is... + setScrollbars2(logicalWidthTmp, logicalHeight); //if scrollbars are shown or hidden a new resize event recurses into updateWindowSizes() + /* + is there a risk of endless recursion? No, 2-level recursion at most, consider the following 6 cases: + + <----------gw----------> + <----------nw------> + ------------------------ /|\ /|\ + | | | | | + | main window | | nh | + | | | | gh + ------------------------ \|/ | + | | | | + ------------------------ \|/ + gw := gross width + nw := net width := gross width - sb size + gh := gross height + nh := net height := gross height - sb size + + There are 6 cases that can occur: + --------------------------------- + lw := logical width + lh := logical height + + 1. lw <= gw && lh <= gh => no scrollbars needed + + 2. lw > gw && lh > gh => need both scrollbars + + 3. lh > gh + 4.1 lw <= nw => need vertical scrollbar only + 4.2 nw < lw <= gw => need both scrollbars + + 4. lw > gw + 3.1 lh <= nh => need horizontal scrollbar only + 3.2 nh < lh <= gh => need both scrollbars + */ } } - - const int mainWinWidth = std::max(0, clientRect.width - rowLabelWidth); - - cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight); - rowLabelWin_->SetSize(0, colLabelHeight, rowLabelWidth, mainWinHeight); - colLabelWin_->SetSize(rowLabelWidth, 0, mainWinWidth, colLabelHeight); - mainWin_ ->SetSize(rowLabelWidth, colLabelHeight, mainWinWidth, mainWinHeight); } @@ -1622,6 +1675,7 @@ void Grid::setRowHeight(size_t height) { rowLabelWin_->setRowHeight(height); updateWindowSizes(); + Refresh(); } @@ -1637,7 +1691,7 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr, size_ [&](const ColumnAttribute& ca) { if (ca.visible_) - visibleCols.push_back(Grid::VisibleColumn(ca.type_, ca.width_)); + visibleCols.push_back(Grid::VisibleColumn(ca.type_, ca.offset_, ca.stretch_)); }); //set ownership of visible columns @@ -1673,7 +1727,8 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const { ca.visible_ = true; //paranoia ca.type_ = iterVcols->type_; - ca.width_ = iterVcols->width_; + ca.stretch_ = iterVcols->stretch_; + ca.offset_ = iterVcols->offset_; ++iterVcols; } } @@ -1687,30 +1742,74 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const } -void Grid::showScrollBars(bool horizontal, bool vertical) +void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatus vertical) { -#ifdef FFS_WIN +#if wxCHECK_VERSION(2, 9, 1) + int weShouldMigrateToWxWidgetsShowScrollBarsInstead; //lousy compile-time warning, I know ;) +#endif + + if (showScrollbarX == horizontal && + showScrollbarY == vertical) return; //support polling! + showScrollbarX = horizontal; showScrollbarY = vertical; - updateWindowSizes(); -#elif defined FFS_LINUX //get rid of scrollbars, but preserve scrolling behavior! +#ifdef FFS_LINUX //get rid of scrollbars, but preserve scrolling behavior! + //the following wxGTK approach is pretty much identical to wxWidgets 2.9 ShowScrollbars() code! + + auto mapStatus = [](ScrollBarStatus sbStatus) -> GtkPolicyType + { + switch (sbStatus) + { + case SB_SHOW_AUTOMATIC: + return GTK_POLICY_AUTOMATIC; + case SB_SHOW_ALWAYS: + return GTK_POLICY_ALWAYS; + case SB_SHOW_NEVER: + return GTK_POLICY_NEVER; + } + assert(false); + return GTK_POLICY_AUTOMATIC; + }; + GtkWidget* gridWidget = wxWindow::m_widget; GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget); gtk_scrolled_window_set_policy(scrolledWindow, - horizontal ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER, - vertical ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER); + mapStatus(horizontal), + mapStatus(vertical)); #endif + updateWindowSizes(); } #ifdef FFS_WIN //get rid of scrollbars, but preserve scrolling behavior! void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) { - if ((orientation == wxHORIZONTAL && !showScrollbarX) || (orientation == wxVERTICAL && !showScrollbarY)) - wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh); + ScrollBarStatus sbStatus = SB_SHOW_AUTOMATIC; + if (orientation == wxHORIZONTAL) + sbStatus = showScrollbarX; + else if (orientation == wxVERTICAL) + sbStatus = showScrollbarY; else - wxWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); + assert(false); + + switch (sbStatus) + { + case SB_SHOW_AUTOMATIC: + wxScrolledWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); + break; + + case SB_SHOW_ALWAYS: + if (range <= 1) //scrollbars hidden if range == 0 or 1 + wxScrolledWindow::SetScrollbar(orientation, 0, 199999, 200000, refresh); + else + wxScrolledWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); + break; + + case SB_SHOW_NEVER: + wxScrolledWindow::SetScrollbar(orientation, 0, 0, 0, refresh); + break; + } } @@ -1735,7 +1834,7 @@ WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) scrollDelta(rotations * linesPerRotation, 0); //in scroll units } - return wxScrolledWindow::MSWDefWindowProc(nMsg, wParam, lParam);; + return wxScrolledWindow::MSWDefWindowProc(nMsg, wParam, lParam); } #endif @@ -1748,23 +1847,23 @@ wxWindow& Grid::getMainWin () { return *mainWin_; } wxRect Grid::getColumnLabelArea(ColumnType colType, size_t compPos) const { - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve negative/stretched widths if (compPos < compAbsWidths.size()) { auto iterComp = compAbsWidths.begin() + compPos; - auto iterCol = std::find_if(iterComp->begin(), iterComp->end(), [&](const VisibleColumn& vc) { return vc.type_ == colType; }); + auto iterCol = std::find_if(iterComp->begin(), iterComp->end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }); if (iterCol != iterComp->end()) { ptrdiff_t posX = std::accumulate(compAbsWidths.begin(), iterComp, static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const std::vector<VisibleColumn>& cols) + [](ptrdiff_t sum, const std::vector<ColumnWidth>& cols) { return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0), - [](ptrdiff_t val2, const VisibleColumn& vc) { return val2 + vc.width_; }); + [](ptrdiff_t val2, const ColumnWidth& cw) { return val2 + cw.width_; }); }); posX += std::accumulate(iterComp->begin(), iterCol, static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const VisibleColumn& vc) { return sum + vc.width_; }); + [](ptrdiff_t sum, const ColumnWidth& cw) { return sum + cw.width_; }); return wxRect(wxPoint(posX, 0), wxSize(iterCol->width_, colLabelHeight)); } @@ -1780,7 +1879,7 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const { ptrdiff_t accuWidth = 0; - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve stretched widths for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) { const size_t compPos = iterComp - compAbsWidths.begin(); @@ -1833,17 +1932,17 @@ void Grid::moveColumn(size_t colFrom, size_t colTo, size_t compPos) ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos, size_t compPos) const { - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve negative/stretched widths if (compPos < compAbsWidths.size()) { auto iterComp = compAbsWidths.begin() + compPos; const int absPosX = CalcUnscrolledPosition(pos).x; ptrdiff_t accuWidth = std::accumulate(compAbsWidths.begin(), iterComp, static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum, const std::vector<VisibleColumn>& cols) + [](ptrdiff_t sum, const std::vector<ColumnWidth>& cols) { return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0), - [](ptrdiff_t sum2, const VisibleColumn& vc) { return sum2 + vc.width_; }); + [](ptrdiff_t sum2, const ColumnWidth& cw) { return sum2 + cw.width_; }); }); for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) @@ -1862,12 +1961,8 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos, size_t compPos) Opt<ColumnType> Grid::colToType(size_t col, size_t compPos) const { - if (compPos < comp.size()) - { - auto& visibleCols = comp[compPos].visibleCols; - if (col < visibleCols.size()) - return visibleCols[col].type_; - } + if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + return comp[compPos].visibleCols[col].type_; return NoValue(); } @@ -1879,7 +1974,7 @@ Opt<std::pair<ColumnType, size_t>> Grid::getColumnAtPos(int posX) const { if (posX >= 0) { - std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + std::vector<std::vector<ColumnWidth>> compAbsWidths = getColWidths(); //resolve negative/stretched widths ptrdiff_t accWidth = 0; for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) @@ -1905,15 +2000,26 @@ wxRect Grid::getCellArea(size_t row, ColumnType colType, size_t compPos) const } +void Grid::clearSelection(size_t compPos) +{ + if (compPos < comp.size()) + { + comp[compPos].selection.clear(); + mainWin_->Refresh(); + } +} + + void Grid::setGridCursor(size_t row, size_t compPos) { if (compPos < comp.size()) { + mainWin_->setCursor(row, compPos); + mainWin_->makeRowVisible(row); + std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); //clear selection, do NOT fire event selectRange(row, row, compPos); //set new selection + fire event - mainWin_->setCursor(row, compPos); - mainWin_->makeRowVisible(row); mainWin_->Refresh(); rowLabelWin_->Refresh(); //row labels! (Kubuntu) } @@ -1938,12 +2044,17 @@ void Grid::selectRange(ptrdiff_t rowFrom, ptrdiff_t rowTo, size_t compPos, bool void Grid::clearSelectionAll() { - std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); + for (auto iter = comp.begin(); iter != comp.end(); ++iter) + { + Grid::Component& c = *iter; + c.selection.clear(); - //notify event - GridRangeSelectEvent unselectionEvent(-1, -1, static_cast<size_t>(-1), false); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->ProcessEvent(unselectionEvent); + //notify event + const size_t compPos = iter - comp.begin(); + GridRangeSelectEvent unselectionEvent(-1, -1, compPos, false); + if (wxEvtHandler* evtHandler = GetEventHandler()) + evtHandler->ProcessEvent(unselectionEvent); + } } @@ -1956,13 +2067,17 @@ void Grid::scrollTo(size_t row) GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); if (pixelsPerUnitY > 0) { - int scrollPosX = 0; - GetViewStart(&scrollPosX, nullptr); - int scrollPosY = labelRect.GetTopLeft().y / pixelsPerUnitY; + const int scrollPosYNew = labelRect.GetTopLeft().y / pixelsPerUnitY; + int scrollPosXOld = 0; + int scrollPosYOld = 0; + GetViewStart(&scrollPosXOld, &scrollPosYOld); - Scroll(scrollPosX, scrollPosY); - updateWindowSizes(); //may show horizontal scroll bar - Refresh(); + if (scrollPosYOld != scrollPosYNew) //support polling + { + Scroll(scrollPosXOld, scrollPosYNew); + updateWindowSizes(); //may show horizontal scroll bar + Refresh(); + } } } } @@ -2000,6 +2115,43 @@ ptrdiff_t Grid::getBestColumnSize(size_t col, size_t compPos) const } +void Grid::setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, bool notifyAsync) +{ + if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + { + VisibleColumn& vcRs = comp[compPos].visibleCols[col]; + const int mainWinWidth = mainWin_->GetClientSize().GetWidth(); + const ptrdiff_t stretchTotal = getStretchTotal(); + + const ptrdiff_t offset = width - getColStretchedWidth(vcRs.stretch_, stretchTotal, mainWinWidth); //width := stretchedWidth + (normalized) offset + vcRs.offset_ = offset; + + //I. width may be < COLUMN_MIN_WIDTH: for non-stretched columns this doesn't matter, since it's normalized in getColWidths() anyway, + // for stretched columns on the other hand negative width would be evaluated *before* normalization! => need to normalize here! + //II. worse: resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth! + // Testcase: 1. make main window so small in width that horizontal scrollbars are shown despite existing streched column. + // 2. resize a fixed size column so that scrollbars vanish. 3. verify that the stretched column is resizing immediately while main dialog is enlarged + std::for_each(comp.begin(), comp.end(), [&](Component& c) + { + std::for_each(c.visibleCols.begin(), c.visibleCols.end(), [&](VisibleColumn& vc) + { + const ptrdiff_t stretchedWidth = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth); + vc.offset_ = std::max(vc.offset_, COLUMN_MIN_WIDTH - stretchedWidth); //it would suffice to normalize stretched columns only + }); + }); + + GridColumnResizeEvent sizeEvent(offset, vcRs.type_, compPos); + if (wxEvtHandler* evtHandler = GetEventHandler()) + { + if (notifyAsync) + evtHandler->AddPendingEvent(sizeEvent); + else + evtHandler->ProcessEvent(sizeEvent); + } + } +} + + void Grid::autoSizeColumns(size_t compPos) { if (compPos < comp.size() && comp[compPos].allowColumnResize) @@ -2007,18 +2159,10 @@ void Grid::autoSizeColumns(size_t compPos) auto& visibleCols = comp[compPos].visibleCols; for (auto iter = visibleCols.begin(); iter != visibleCols.end(); ++iter) { - const int col = iter - visibleCols.begin(); - const int bestWidth = getBestColumnSize(col, compPos); //return -1 on error + const size_t col = iter - visibleCols.begin(); + const ptrdiff_t bestWidth = getBestColumnSize(col, compPos); //return -1 on error if (bestWidth >= 0) - { - const auto newWidth = std::max(COLUMN_MIN_WIDTH, bestWidth); - iter->width_ = newWidth; - - //notify column resize (asynchronously!) - GridColumnResizeEvent sizeEvent(newWidth, iter->type_, compPos); - if (wxEvtHandler* evtHandler = GetEventHandler()) - evtHandler->AddPendingEvent(sizeEvent); - } + setColWidthAndNotify(bestWidth, col, compPos, true); } updateWindowSizes(); Refresh(); @@ -2026,80 +2170,59 @@ void Grid::autoSizeColumns(size_t compPos) } -ptrdiff_t Grid::getMinAbsoluteWidthTotal() const +ptrdiff_t Grid::getStretchTotal() const //sum of all stretch factors { - ptrdiff_t minWidthTotal = 0; - //bool haveStretchedCols = false; - std::for_each(comp.begin(), comp.end(), - [&](const Component& c) + return std::accumulate(comp.begin(), comp.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t sum, const Component& c) { - std::for_each(c.visibleCols.begin(), c.visibleCols.end(), - [&](const VisibleColumn& vc) - { - if (vc.width_ >= 0) - minWidthTotal += vc.width_; - else - { - //haveStretchedCols = true; - minWidthTotal += COLUMN_MIN_WIDTH; //use "min width" if column is stretched - } - }); + return sum + std::accumulate(c.visibleCols.begin(), c.visibleCols.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t val2, const Grid::VisibleColumn& vc) { return val2 + vc.stretch_; }); }); - return minWidthTotal; } -std::vector<std::vector<Grid::VisibleColumn>> Grid::getAbsoluteWidths() const //evaluate negative widths as stretched absolute values! structure matches "comp" +ptrdiff_t Grid::getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth) //final width = stretchedWidth + (normalized) offset { - std::vector<std::vector<VisibleColumn>> output; + return stretchTotal > 0 ? mainWinWidth * stretch / stretchTotal : 0; //rounds down! => not all of clientWidth is correctly distributed according to stretch factors +} - std::vector<std::pair<ptrdiff_t, VisibleColumn*>> stretchedCols; //(factor, column to stretch) - ptrdiff_t factorTotal = 0; - ptrdiff_t minWidthTotal = 0; - output.reserve(comp.size()); - std::for_each(comp.begin(), comp.end(), - [&](const Component& c) +std::vector<std::vector<Grid::ColumnWidth>> Grid::getColWidths() const +{ + return getColWidths(mainWin_->GetClientSize().GetWidth()); +} + + +std::vector<std::vector<Grid::ColumnWidth>> Grid::getColWidths(int mainWinWidth) const //evaluate stretched columns; structure matches "comp" +{ + std::vector<std::vector<ColumnWidth>> output; + + const ptrdiff_t stretchTotal = getStretchTotal(); + + std::for_each(comp.begin(), comp.end(), [&](const Component& c) { - output.push_back(std::vector<VisibleColumn>()); + output.push_back(std::vector<ColumnWidth>()); auto& compWidths = output.back(); - compWidths.reserve(c.visibleCols.size()); - std::for_each(c.visibleCols.begin(), c.visibleCols.end(), - [&](const VisibleColumn& vc) + std::for_each(c.visibleCols.begin(), c.visibleCols.end(), [&](const VisibleColumn& vc) { - if (vc.width_ >= 0) - { - compWidths.push_back(Grid::VisibleColumn(vc.type_, vc.width_)); - minWidthTotal += vc.width_; - } - else //stretched column - { - compWidths.push_back(Grid::VisibleColumn(vc.type_, COLUMN_MIN_WIDTH)); //use "min width" if column is stretched - minWidthTotal += COLUMN_MIN_WIDTH; + const ptrdiff_t stretchedWidth = Grid::getColStretchedWidth(vc.stretch_, stretchTotal, mainWinWidth); + const ptrdiff_t widthNormalized = std::max(stretchedWidth + vc.offset_, static_cast<ptrdiff_t>(COLUMN_MIN_WIDTH)); - stretchedCols.push_back(std::make_pair(vc.width_, &compWidths.back())); - factorTotal += vc.width_; - } + compWidths.push_back(Grid::ColumnWidth(vc.type_, widthNormalized)); }); }); - - if (!stretchedCols.empty()) - { - const ptrdiff_t widthToFill = mainWin_->GetClientSize().GetWidth() - minWidthTotal; - if (widthToFill > 0) - { - int widthRemaining = widthToFill; - for (auto iter = stretchedCols.begin(); iter != stretchedCols.end(); ++iter) - { - const ptrdiff_t addWidth = (widthToFill * iter->first) / factorTotal; //round down - iter->second->width_ += addWidth; - widthRemaining -= addWidth; - } - - if (widthRemaining > 0) //should be empty, except for rounding errors - stretchedCols.back().second->width_ += widthRemaining; - } - } return output; } + + +ptrdiff_t Grid::getColWidthsSum(int mainWinWidth) const +{ + auto widths = getColWidths(mainWinWidth); + return std::accumulate(widths.begin(), widths.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t sum, const std::vector<Grid::ColumnWidth>& cols) + { + return sum + std::accumulate(cols.begin(), cols.end(), static_cast<ptrdiff_t>(0), + [](ptrdiff_t val2, const Grid::ColumnWidth& cw) { return val2 + cw.width_; }); + }); +}; @@ -50,11 +50,11 @@ struct GridClickEvent : public wxMouseEvent struct GridColumnResizeEvent : public wxCommandEvent { - GridColumnResizeEvent(int width, ColumnType colType, size_t compPos) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), width_(width), compPos_(compPos) {} + GridColumnResizeEvent(ptrdiff_t offset, ColumnType colType, size_t compPos) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset), compPos_(compPos) {} virtual wxEvent* Clone() const { return new GridColumnResizeEvent(*this); } const ColumnType colType_; - const int width_; + const ptrdiff_t offset_; const size_t compPos_; }; @@ -138,10 +138,13 @@ public: struct ColumnAttribute { - ColumnAttribute(ColumnType type, int width, bool visible = true) : type_(type), width_(width), visible_(visible) {} + ColumnAttribute(ColumnType type, ptrdiff_t offset, ptrdiff_t stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max<ptrdiff_t>(stretch, 0)), offset_(offset) {} ColumnType type_; - int width_; //if negative, treat as proportional stretch! bool visible_; + //first client width is partitioned according to all available stretch factors, then "offset_" is added + //universal model: a non-stretched column has stretch factor 0 with the "offset" becoming identical to final width! + ptrdiff_t stretch_; //>= 0 + ptrdiff_t offset_; }; void setColumnConfig(const std::vector<ColumnAttribute>& attr, size_t compPos = 0); //set column count + widths @@ -155,10 +158,17 @@ public: void setColumnLabelHeight(int height); void showRowLabel(bool visible); - void showScrollBars(bool horizontal, bool vertical); + enum ScrollBarStatus + { + SB_SHOW_AUTOMATIC, + SB_SHOW_ALWAYS, + SB_SHOW_NEVER, + }; + //alternative until wxScrollHelper::ShowScrollbars() becomes available in wxWidgets 2.9 + void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); std::vector<size_t> getSelectedRows(size_t compPos = 0) const; - void clearSelection(size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].selection.clear(); } + void clearSelection(size_t compPos = 0); void scrollDelta(int deltaX, int deltaY); //in scroll units @@ -187,7 +197,7 @@ public: private: void onPaintEvent(wxPaintEvent& event); void onEraseBackGround(wxEraseEvent& event) {} //[!] - void onSizeEvent(wxEvent& evt) { updateWindowSizes(); } + void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } void updateWindowSizes(bool updateScrollbar = true); @@ -242,9 +252,10 @@ private: struct VisibleColumn { - VisibleColumn(ColumnType type, ptrdiff_t width) : type_(type), width_(width) {} + VisibleColumn(ColumnType type, ptrdiff_t offset, ptrdiff_t stretch) : type_(type), stretch_(stretch), offset_(offset) {} ColumnType type_; - ptrdiff_t width_; //may be NEGATIVE => treat as proportional stretch! use getAbsoluteWidths() to evaluate!!! + ptrdiff_t stretch_; //>= 0 + ptrdiff_t offset_; }; struct Component @@ -260,22 +271,67 @@ private: std::vector<ColumnAttribute> oldColAttributes; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! }; - ptrdiff_t getMinAbsoluteWidthTotal() const; //assigns minimum width to stretched columns - std::vector<std::vector<VisibleColumn>> getAbsoluteWidths() const; //evaluate negative widths as stretched absolute values! structure matches "comp" + struct ColumnWidth + { + ColumnWidth(ColumnType type, ptrdiff_t width) : type_(type), width_(width) {} + ColumnType type_; + ptrdiff_t width_; + }; + std::vector<std::vector<ColumnWidth>> getColWidths() const; // + std::vector<std::vector<ColumnWidth>> getColWidths(int mainWinWidth) const; //evaluate stretched columns; structure matches "comp" + ptrdiff_t getColWidthsSum(int mainWinWidth) const; - Opt<size_t> getAbsoluteWidth(size_t col, size_t compPos) const //resolve stretched columns + Opt<ptrdiff_t> getColWidth(size_t col, size_t compPos) const { - const auto& absWidth = getAbsoluteWidths(); - if (compPos < absWidth.size() && col < absWidth[compPos].size()) - return absWidth[compPos][col].width_; + const auto& widths = getColWidths(); + if (compPos < widths.size() && col < widths[compPos].size()) + return widths[compPos][col].width_; return NoValue(); } - void setColWidth(size_t col, size_t compPos, ptrdiff_t width) //width may be >= 0: absolute, or < 0: stretched - { - if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) - comp[compPos].visibleCols[col].width_ = width; - } + ptrdiff_t getStretchTotal() const; //sum of all stretch factors + static ptrdiff_t getColStretchedWidth(ptrdiff_t stretch, ptrdiff_t stretchTotal, int mainWinWidth); //final width = stretchedWidth + (normalized) offset + + void setColWidthAndNotify(ptrdiff_t width, size_t col, size_t compPos, bool notifyAsync = false); + + //ptrdiff_t getNormalizedColOffset(ptrdiff_t offset, ptrdiff_t stretchedWidth) const; //normalize so that "stretchedWidth + offset" gives reasonable width! + + //Opt<ptrdiff_t> getColOffsetNorm(size_t col, size_t compPos) const //returns *normalized* offset! + // { + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // { + // const VisibleColumn& vc = comp[compPos].visibleCols[col]; + // return getNormalizedColOffset(vc.offset_, getColStretchedWidth(vc.stretch_)); + // } + // return NoValue(); + // } + + //Opt<VisibleColumn> getColAttrib(size_t col, size_t compPos) const + //{ + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // return comp[compPos].visibleCols[col]; + // return NoValue(); + //} + + //Opt<ptrdiff_t> getColStretchedWidth(size_t col, size_t compPos) const + // { + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // { + // const VisibleColumn& vc = comp[compPos].visibleCols[col]; + // return getColStretchedWidth(vc.stretch_); + // } + // return NoValue(); + // } + + + //void setColOffset(size_t col, size_t compPos, ptrdiff_t offset) + // { + // if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + // { + // VisibleColumn& vc = comp[compPos].visibleCols[col]; + // vc.offset_ = offset; + // } + // } wxRect getColumnLabelArea(ColumnType colType, size_t compPos) const; //returns empty rect if column not found @@ -313,8 +369,8 @@ private: ColLabelWin* colLabelWin_; MainWin* mainWin_; - bool showScrollbarX; - bool showScrollbarY; + ScrollBarStatus showScrollbarX; + ScrollBarStatus showScrollbarY; int colLabelHeight; bool drawRowLabel; diff --git a/wx+/image_tools.h b/wx+/image_tools.h index 8a2a43ed..772189a6 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -27,6 +27,7 @@ bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs); //pixel-wise equality (r wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color +wxColour hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] @@ -164,7 +165,54 @@ wxColor gradient(const wxColor& from, const wxColor& to, double fraction) from.Blue () + (to.Blue () - from.Blue ()) * fraction, from.Alpha() + (to.Alpha() - from.Alpha()) * fraction); } -} +inline +wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1] +{ + //http://de.wikipedia.org/wiki/HSV-Farbraum + + //make input values fit into bounds + if (h > 360) + h -= static_cast<int>(h / 360) * 360; + else if (h < 0) + h -= static_cast<int>(h / 360) * 360 - 360; + numeric::confine<double>(s, 0, 1); + numeric::confine<double>(v, 0, 1); + //------------------------------------ + const int h_i = h / 60; + const float f = h / 60 - h_i; + + auto polish = [](double val) -> unsigned char + { + int result = numeric::round(val * 255); + numeric::confine(result, 0, 255); + return static_cast<unsigned char>(result); + }; + + const unsigned char p = polish(v * (1 - s)); + const unsigned char q = polish(v * (1 - s * f)); + const unsigned char t = polish(v * (1 - s * (1 - f))); + const unsigned char vi = polish(v); + + switch (h_i) + { + case 0: + return wxColour(vi, t, p); + case 1: + return wxColour(q, vi, p); + case 2: + return wxColour(p, vi, t); + case 3: + return wxColour(p, q, vi); + case 4: + return wxColour(t, p, vi); + case 5: + return wxColour(vi, p, q); + } + assert(false); + return *wxBLACK; +} +} + #endif //IMAGE_TOOLS_HEADER_45782456427634254 diff --git a/wx+/serialize.h b/wx+/serialize.h deleted file mode 100644 index 030b55c7..00000000 --- a/wx+/serialize.h +++ /dev/null @@ -1,538 +0,0 @@ -// ************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#ifndef SERIALIZE_H_INCLUDED -#define SERIALIZE_H_INCLUDED - -#include <cstdint> -#include <zen/string_base.h> -#include <zen/file_io.h> - -#ifdef FFS_WIN -warn_static("get rid of wx, then move to zen") -#endif - -#include <wx/stream.h> - - -namespace zen -{ -//high-performance unformatted serialization (avoiding wxMemoryOutputStream/wxMemoryInputStream inefficiencies) - -/* --------------------------- -|Binary Container Concept| --------------------------- -binary container for data storage: must support "basic" std::vector interface (e.g. std::vector<char>, std::string, Zbase<char>) -*/ - -//binary container reference implementations -typedef Zbase<char> Utf8String; //ref-counted + COW text stream + guaranteed performance: exponential growth -class BinaryStream; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?) - -class BinaryStream //essentially a std::vector<char> with ref-counted semantics -{ -public: - BinaryStream() : buffer(std::make_shared<std::vector<char>>()) {} - - typedef std::vector<char>::value_type value_type; - typedef std::vector<char>::iterator iterator; - typedef std::vector<char>::const_iterator const_iterator; - - iterator begin() { return buffer->begin(); } - iterator end () { return buffer->end (); } - - const_iterator begin() const { return buffer->begin(); } - const_iterator end () const { return buffer->end (); } - - void resize(size_t len) { buffer->resize(len); } - size_t size() const { return buffer->size(); } - bool empty() const { return buffer->empty(); } - - inline friend bool operator==(const BinaryStream& lhs, const BinaryStream& rhs) { return *lhs.buffer == *rhs.buffer; } - -private: - std::shared_ptr<std::vector<char>> buffer; //always bound! - //perf: shared_ptr indirection irrelevant: less than 1% slower! -}; - -//---------------------------------------------------------------------- -//functions based on binary container abstraction -template <class BinContainer> void saveBinStream(const Zstring& filename, const BinContainer& cont); //throw FileError -template <class BinContainer> BinContainer loadBinStream(const Zstring& filename); //throw FileError, ErrorNotExisting - - -/* ------------------------------ -|Binary Input Stream Concept| ------------------------------ -struct BinInputStream -{ - const void* requestRead(size_t len); //expect external read of len bytes -}; - ------------------------------- -|Binary Output Stream Concept| ------------------------------- -struct BinOutputStream -{ - void* requestWrite(size_t len); //expect external write of len bytes -}; -*/ - -//binary input/output stream reference implementation -class UnexpectedEndOfStreamError {}; - -struct BinStreamIn //throw UnexpectedEndOfStreamError -{ - BinStreamIn(const BinaryStream& cont) : buffer(cont), pos(0) {} //this better be cheap! - - const void* requestRead(size_t len) //throw UnexpectedEndOfStreamError - { - if (pos + len > buffer.size()) - throw UnexpectedEndOfStreamError(); - size_t oldPos = pos; - pos += len; - return &*buffer.begin() + oldPos; - } - -private: - const BinaryStream buffer; - size_t pos; -}; - -struct BinStreamOut -{ - void* requestWrite(size_t len) - { - size_t oldSize = buffer.size(); - buffer.resize(buffer.size() + len); - return &*buffer.begin() + oldSize; - } - - BinaryStream get() { return buffer; } - -private: - BinaryStream buffer; -}; - -//---------------------------------------------------------------------- -//functions based on binary stream abstraction -template <class N, class BinOutputStream> void writeNumber (BinOutputStream& stream, const N& num); // -template <class C, class BinOutputStream> void writeContainer(BinOutputStream& stream, const C& str); //throw () -template < class BinOutputStream> void writeArray (BinOutputStream& stream, const void* data, size_t len); // - -//---------------------------------------------------------------------- - -template <class N, class BinInputStream> N readNumber (BinInputStream& stream); // -template <class C, class BinInputStream> C readContainer(BinInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data) -template < class BinInputStream> void readArray (BinInputStream& stream, void* data, size_t len); // - - - - - - - - - - - - - - - - - - - - - - - -//-----------------------implementation------------------------------- -template <class BinContainer> inline -void saveBinStream(const Zstring& filename, const BinContainer& cont) //throw FileError -{ - assert_static(sizeof(typename BinContainer::value_type) == 1); //expect: bytes (until further) - - FileOutput fileOut(filename, zen::FileOutput::ACC_OVERWRITE); //throw FileError - if (!cont.empty()) - fileOut.write(&*cont.begin(), cont.size()); //throw FileError -} - - -template <class BinContainer> inline -BinContainer loadBinStream(const Zstring& filename) //throw FileError, ErrorNotExisting -{ - assert_static(sizeof(typename BinContainer::value_type) == 1); //expect: bytes (until further) - - FileInput fileIn(filename); //throw FileError, ErrorNotExisting - - BinContainer contOut; - const size_t blockSize = 64 * 1024; - do - { - contOut.resize(contOut.size() + blockSize); - - const size_t bytesRead = fileIn.read(&*contOut.begin() + contOut.size() - blockSize, blockSize); //throw FileError - if (bytesRead < blockSize) - contOut.resize(contOut.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics - } - while (!fileIn.eof()); - - return contOut; -} - - -template <class BinOutputStream> inline -void writeArray(BinOutputStream& stream, const void* data, size_t len) -{ - std::copy(static_cast<const char*>(data), - static_cast<const char*>(data) + len, - static_cast< char*>(stream.requestWrite(len))); -} - - -template <class N, class BinOutputStream> inline -void writeNumber(BinOutputStream& stream, const N& num) -{ - assert_static((IsArithmetic<N>::value || IsSameType<N, bool>::value)); - writeArray(stream, &num, sizeof(N)); -} - - -template <class C, class BinOutputStream> inline -void writeContainer(BinOutputStream& stream, const C& cont) //don't even consider UTF8 conversions here! "string" is expected to handle arbitrary binary data! -{ - const auto len = cont.size(); - writeNumber(stream, static_cast<std::uint32_t>(len)); - if (len > 0) - writeArray(stream, &*cont.begin(), sizeof(typename C::value_type) * len); //don't use c_str(), but access uniformly via STL interface -} - - -template <class BinInputStream> inline -void readArray(BinInputStream& stream, void* data, size_t len) -{ - const char* const src = static_cast<const char*>(stream.requestRead(len)); //expect external write of len bytes - std::copy(src, src + len, static_cast<char*>(data)); -} - - -template <class N, class BinInputStream> inline -N readNumber(BinInputStream& stream) -{ - assert_static((IsArithmetic<N>::value || IsSameType<N, bool>::value)); - N num = 0; - readArray(stream, &num, sizeof(N)); - return num; -} - - -template <class C, class BinInputStream> inline -C readContainer(BinInputStream& stream) -{ - C cont; - auto strLength = readNumber<std::uint32_t>(stream); - if (strLength > 0) - try - { - cont.resize(strLength); //throw std::bad_alloc - readArray(stream, &*cont.begin(), sizeof(typename C::value_type) * strLength); - } - catch (std::bad_alloc&) //most likely this is due to data corruption! - { - throw UnexpectedEndOfStreamError(); - } - return cont; -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#ifdef FFS_WIN -warn_static("get rid of wx, then move to zen") -#endif - - - -//unchecked, unformatted serialization -template <class T> T readPOD (wxInputStream& stream); -template <class T> void readPOD (wxInputStream& stream, T& pod); -template <class T> void writePOD(wxOutputStream& stream, const T& pod); - -template <class S> S readString (wxInputStream& stream); -template <class S> void readString (wxInputStream& stream, S& str); -template <class S> void writeString(wxOutputStream& stream, const S& str); - - -//############# wxWidgets stream adapter ############# -class FileInputStream : public wxInputStream -{ -public: - FileInputStream(const Zstring& filename) : fileObj(filename) {} //throw FileError - -private: - virtual size_t OnSysRead(void* buffer, size_t bufsize) { return fileObj.read(buffer, bufsize); } //throw FileError - - zen::FileInput fileObj; -}; - - -class FileOutputStream : public wxOutputStream -{ -public: - FileOutputStream(const Zstring& filename) : fileObj(filename, zen::FileOutput::ACC_OVERWRITE) {} //throw FileError - -private: - virtual size_t OnSysWrite(const void* buffer, size_t bufsize) - { - fileObj.write(buffer, bufsize); //throw FileError - return bufsize; - } - - zen::FileOutput fileObj; -}; - - -class CheckedIo -{ -public: - virtual void throwException() const = 0; - -protected: - CheckedIo(wxStreamBase& stream) : stream_(stream) {} - - void check() const - { - if (stream_.GetLastError() != wxSTREAM_NO_ERROR) - throwException(); - } - -private: - wxStreamBase& stream_; -}; - - -//wxInputStream proxy throwing exception on error -class CheckedReader : public CheckedIo -{ -public: - CheckedReader(wxInputStream& stream) : CheckedIo(stream), stream_(stream) {} - - template <class T> - T readPOD() const; //throw! - - template <class T> - void readPOD(T& pod) const; //throw! - - template <class S> - S readString() const; //throw! - - template <class S> - void readString(S& str) const; //throw! - - void readArray(void* data, size_t len) const; //throw! - -private: - wxInputStream& stream_; -}; - - -//wxOutputStream proxy throwing FileError on error -class CheckedWriter : public CheckedIo -{ -public: - CheckedWriter(wxOutputStream& stream) : CheckedIo(stream), stream_(stream) {} - - template <class T> - void writePOD(const T& pod) const; //throw! - - template <class S> - void writeString(const S& str) const; //throw! - - void writeArray(const void* data, size_t len) const; //throw! - -private: - wxOutputStream& stream_; -}; - - -template <class T> inline -T readPOD(wxInputStream& stream) -{ - T pod = 0; - readPOD(stream, pod); - return pod; -} - - -template <class T> inline -void readPOD(wxInputStream& stream, T& pod) -{ - stream.Read(reinterpret_cast<char*>(&pod), sizeof(T)); -} - - -template <class T> inline -void writePOD(wxOutputStream& stream, const T& pod) -{ - stream.Write(reinterpret_cast<const char*>(&pod), sizeof(T)); -} - - -template <class S> inline -S readString(wxInputStream& stream) -{ - S str; - readString(stream, str); - return str; -} - - -template <class S> inline -void readString(wxInputStream& stream, S& str) -{ - //don't even consider UTF8 conversions here! "string" is expected to handle arbitrary binary data! - - const auto strLength = readPOD<std::uint32_t>(stream); - str.resize(strLength); //throw std::bad_alloc - if (strLength > 0) - stream.Read(&*str.begin(), sizeof(typename S::value_type) * strLength); -} - - -template <class S> inline -void writeString(wxOutputStream& stream, const S& str) -{ - const auto strLength = str.length(); - writePOD(stream, static_cast<std::uint32_t>(strLength)); - if (strLength > 0) - stream.Write(&*str.begin(), sizeof(typename S::value_type) * strLength); //don't use c_str(), but access uniformly via STL interface -} - - -inline -void CheckedReader::readArray(void* data, size_t len) const //throw! -{ - stream_.Read(data, len); - check(); -} - - -template <class T> inline -T CheckedReader::readPOD() const //checked read operation -{ - T pod = 0; - readPOD(pod); - return pod; -} - - -template <class T> inline -void CheckedReader::readPOD(T& pod) const //checked read operation -{ - readArray(&pod, sizeof(T)); -} - - -template <class S> inline -S CheckedReader::readString() const //checked read operation -{ - S str; - readString(str); - return str; -} - - -template <class S> inline -void CheckedReader::readString(S& str) const //checked read operation -{ - try - { - zen::readString<S>(stream_, str); //throw std::bad_alloc - } - catch (std::exception&) { throwException(); } - check(); - if (stream_.LastRead() != str.size() * sizeof(typename S::value_type)) //some additional check - throwException(); -} - - -inline -void CheckedWriter::writeArray(const void* data, size_t len) const //throw! -{ - stream_.Write(data, len); - check(); -} - - -template <class T> inline -void CheckedWriter::writePOD(const T& pod) const //checked write opera -{ - writeArray(&pod, sizeof(T)); -} - - -template <class S> inline -void CheckedWriter::writeString(const S& str) const //checked write operation -{ - zen::writeString(stream_, str); - check(); - //warn_static("buggy check if length 0!") - if (stream_.LastWrite() != str.length() * sizeof(typename S::value_type)) //some additional check - throwException(); -} -} - -#endif //SERIALIZE_H_INCLUDED diff --git a/wx+/zlib_wrap.cpp b/wx+/zlib_wrap.cpp index 22285bab..a27a4fa4 100644 --- a/wx+/zlib_wrap.cpp +++ b/wx+/zlib_wrap.cpp @@ -6,7 +6,7 @@ #include "zlib_wrap.h" #ifdef FFS_WIN -#include <../src/zlib/zlib.h> //not really a "nice" place to look for a stable solution +#include <wx/../../src/zlib/zlib.h> //not really a "nice" place to look for a stable solution #elif defined FFS_LINUX #include <zlib.h> //let's pray this is the same version wxWidgets is linking against! #endif diff --git a/wx+/zlib_wrap.h b/wx+/zlib_wrap.h index 7b196a75..c229a589 100644 --- a/wx+/zlib_wrap.h +++ b/wx+/zlib_wrap.h @@ -7,7 +7,7 @@ #ifndef SIMPLE_H_INCLUDED_18134135134135345489 #define SIMPLE_H_INCLUDED_18134135134135345489 -#include <wx+/serialize.h> +#include <zen/serialize.h> namespace zen { |