diff options
Diffstat (limited to 'wx+')
-rwxr-xr-x | wx+/file_drop.cpp | 1 | ||||
-rwxr-xr-x | wx+/file_drop.h | 2 | ||||
-rwxr-xr-x | wx+/focus.h | 66 | ||||
-rwxr-xr-x | wx+/grid.cpp | 255 | ||||
-rwxr-xr-x | wx+/grid.h | 151 | ||||
-rwxr-xr-x | wx+/http.cpp | 21 |
6 files changed, 321 insertions, 175 deletions
diff --git a/wx+/file_drop.cpp b/wx+/file_drop.cpp index 2c0b471e..65d5d861 100755 --- a/wx+/file_drop.cpp +++ b/wx+/file_drop.cpp @@ -7,6 +7,7 @@ #include "file_drop.h" #include <wx/dnd.h> #include <zen/utf.h> +#include <zen/file_access.h> using namespace zen; diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 9826bf27..ee5393b7 100755 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -60,6 +60,8 @@ using FileDropEventFunction = void (wxEvtHandler::*)(FileDropEvent&); void setupFileDrop(wxWindow& wnd); + + } #endif //FILE_DROP_H_09457802957842560325626 diff --git a/wx+/focus.h b/wx+/focus.h new file mode 100755 index 00000000..cd99d010 --- /dev/null +++ b/wx+/focus.h @@ -0,0 +1,66 @@ +// ***************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * +// ***************************************************************************** + +#ifndef FOCUS_1084731021985757843 +#define FOCUS_1084731021985757843 + +#include <wx/toplevel.h> + + +namespace zen +{ +//pretty much the same like "bool wxWindowBase::IsDescendant(wxWindowBase* child) const" but without the obvious misnomer +inline +bool isComponentOf(const wxWindow* child, const wxWindow* top) +{ + for (const wxWindow* wnd = child; wnd != nullptr; wnd = wnd->GetParent()) + if (wnd == top) + return true; + return false; +} + + +inline +wxTopLevelWindow* getTopLevelWindow(wxWindow* child) +{ + for (wxWindow* wnd = child; wnd != nullptr; wnd = wnd->GetParent()) + if (auto tlw = dynamic_cast<wxTopLevelWindow*>(wnd)) //why does wxWidgets use wxWindows::IsTopLevel() ?? + return tlw; + return nullptr; +} + + +/* +Preserving input focus has to be more clever than: + wxWindow* oldFocus = wxWindow::FindFocus(); + ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus()); + +=> wxWindow::SetFocus() internally calls Win32 ::SetFocus, which calls ::SetActiveWindow, which - lord knows why - changes the foreground window to the focus window + even if the user is currently busy using a different app! More curiosity: this foreground focus stealing happens only during the *first* SetFocus() after app start! + It also can be avoided by changing focus back and forth with some other app after start => wxWidgets bug or Win32 feature??? +*/ +struct FocusPreserver +{ + ~FocusPreserver() + { + //wxTopLevelWindow::IsActive() does NOT call Win32 ::GetActiveWindow()! + //Instead it checks if ::GetFocus() is set somewhere inside the top level + //Note: Both Win32 active and focus windows are *thread-local* values, while foreground window is global! https://blogs.msdn.microsoft.com/oldnewthing/20131016-00/?p=2913 + if (oldFocus_) + if (wxTopLevelWindow* topWin = getTopLevelWindow(oldFocus_)) + if (topWin->IsActive()) //Linux/macOS: already behaves just like ::GetForegroundWindow() on Windows! + oldFocus_->SetFocus(); + } + + wxWindow* getFocus() const { return oldFocus_; } + void setFocus(wxWindow* win) { oldFocus_ = win; } + +private: + wxWindow* oldFocus_ = wxWindow::FindFocus(); +}; +} + +#endif //FOCUS_1084731021985757843 diff --git a/wx+/grid.cpp b/wx+/grid.cpp index f048d059..b301bf6b 100755 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -631,12 +631,12 @@ private: for (auto it = absWidths.begin(); it != absWidths.end(); ++it) { const size_t col = it - absWidths.begin(); - const int width = it->width_; //don't use unsigned for calculations! + const int width = it->width; //don't use unsigned for calculations! if (labelAreaTL.x > rect.GetRight()) return; //done, rect is fully covered if (labelAreaTL.x + width > rect.x) - drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(width, colLabelHeight)), col, it->type_); + drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(width, colLabelHeight)), col, it->type); labelAreaTL.x += width; } if (labelAreaTL.x > rect.GetRight()) @@ -647,7 +647,7 @@ private: { int totalWidth = 0; for (const ColumnWidth& cw : absWidths) - totalWidth += cw.width_; + totalWidth += cw.width; const int clientWidth = GetClientSize().GetWidth(); //need reliable, stable width in contrast to rect.width if (totalWidth < clientWidth) @@ -894,7 +894,7 @@ private: { int totalRowWidth = 0; for (const ColumnWidth& cw : absWidths) - totalRowWidth += cw.width_; + totalRowWidth += cw.width; //fill gap after columns and cover full width if (fillGapAfterColumns) @@ -922,14 +922,14 @@ private: if (cellAreaTL.x > rect.GetRight()) return; //done - if (cellAreaTL.x + cw.width_ > rect.x) + if (cellAreaTL.x + cw.width > rect.x) for (auto row = rowRange.first; row < rowRange.second; ++row) { - const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight); + const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width, rowHeight); RecursiveDcClipper dummy3(dc, cellRect); - prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row), getRowHoverToDraw(row)); + prov->renderCell(dc, cellRect, row, cw.type, refParent().IsThisEnabled(), drawAsSelected(row), getRowHoverToDraw(row)); } - cellAreaTL.x += cw.width_; + cellAreaTL.x += cw.width; } } } @@ -993,28 +993,29 @@ private: //row < 0 possible!!! Pressing "Menu key" simulates Mouse Right Down + Up at position 0xffff/0xffff! GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, rowHover); + const MouseSelect mouseSelectBegin{ mouseEvent, false /*complete*/ }; if (row >= 0) if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! { if (event.ControlDown()) - activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row) /*positive*/, mouseEvent); else if (event.ShiftDown()) { - activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true, mouseEvent); - refParent().clearSelection(ALLOW_GRID_EVENT); + activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true /*positive*/, mouseEvent); + refParent().clearSelectionImpl(&mouseSelectBegin, ALLOW_GRID_EVENT); } else { - activeSelection_ = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); - refParent().clearSelection(ALLOW_GRID_EVENT); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, true /*positive*/, mouseEvent); + refParent().clearSelectionImpl(&mouseSelectBegin, ALLOW_GRID_EVENT); } } - //notify event *after* potential "clearSelection(true)" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary, - //then GridClickEvent and the associated GridRangeSelectEvent one after the other - sendEventNow(mouseEvent); - Refresh(); + + //notify event *after* potential "clearSelection()" above: a client should first receive a GridSelectEvent for clearing the grid, if necessary, + //then GridClickEvent and the associated GridSelectEvent one after the other + sendEventNow(mouseEvent); } event.Skip(); //allow changing focus } @@ -1041,11 +1042,15 @@ private: } //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys - refParent().selectRangeAndNotify(activeSelection_->getStartRow (), //from - activeSelection_->getCurrentRow(), //to - activeSelection_->isPositiveSelect(), - &activeSelection_->getFirstClick()); - activeSelection_.reset(); + const ptrdiff_t rowFrom = activeSelection_->getStartRow(); + const ptrdiff_t rowTo = activeSelection_->getCurrentRow(); + const bool positive = activeSelection_->isPositiveSelect(); + const MouseSelect mouseSelect{ activeSelection_->getFirstClick(), true /*complete*/ }; + + activeSelection_.reset(); //release mouse capture *before* sending the event (which might show a modal popup dialog requiring the mouse!!!) + + + refParent().selectRangeAndNotify(rowFrom, rowTo, positive, &mouseSelect); } if (auto prov = refParent().getDataProvider()) @@ -1123,8 +1128,8 @@ private: class MouseSelection : private wxEvtHandler { public: - MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect, const GridClickEvent& firstClick) : - wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick) + MouseSelection(MainWin& wnd, size_t rowStart, bool positive, const GridClickEvent& firstClick) : + wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positive), firstClick_(firstClick) { wnd_.CaptureMouse(); timer_.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); @@ -1618,28 +1623,42 @@ void Grid::showRowLabel(bool show) } +void Grid::selectRow(size_t row, GridEventPolicy rangeEventPolicy) +{ + selection_.selectRow(row); + mainWin_->Refresh(); + + if (rangeEventPolicy == ALLOW_GRID_EVENT) + { + GridSelectEvent selEvent(row, row + 1, true, nullptr); + if (wxEvtHandler* evtHandler = GetEventHandler()) + evtHandler->ProcessEvent(selEvent); + } +} + + void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) { selection_.selectAll(); mainWin_->Refresh(); - if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction + if (rangeEventPolicy == ALLOW_GRID_EVENT) { - GridRangeSelectEvent selEvent(0, getRowCount(), true, nullptr); + GridSelectEvent selEvent(0, getRowCount(), true /*positive*/, nullptr); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selEvent); } } -void Grid::clearSelection(GridEventPolicy rangeEventPolicy) +void Grid::clearSelectionImpl(const MouseSelect* mouseSelect, GridEventPolicy rangeEventPolicy) { selection_.clear(); mainWin_->Refresh(); - if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction + if (rangeEventPolicy == ALLOW_GRID_EVENT) { - GridRangeSelectEvent unselectionEvent(0, getRowCount(), false, nullptr); + GridSelectEvent unselectionEvent(0, getRowCount(), false /*positive*/, mouseSelect); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(unselectionEvent); } @@ -1648,18 +1667,16 @@ void Grid::clearSelection(GridEventPolicy rangeEventPolicy) void Grid::scrollDelta(int deltaX, int deltaY) { - int scrollPosX = 0; - int scrollPosY = 0; - GetViewStart(&scrollPosX, &scrollPosY); + wxPoint scrollPos = GetViewStart(); - scrollPosX += deltaX; - scrollPosY += deltaY; + scrollPos.x += deltaX; + scrollPos.y += deltaY; - scrollPosX = std::max(0, scrollPosX); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! - scrollPosY = std::max(0, scrollPosY); // + scrollPos.x = std::max(0, scrollPos.x); //wxScrollHelper::Scroll() will exit prematurely if input happens to be "-1"! + scrollPos.y = std::max(0, scrollPos.y); // - Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()! - updateWindowSizes(); //may show horizontal scroll bar + Scroll(scrollPos); //internally calls wxWindows::Update()! + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider } @@ -1704,17 +1721,19 @@ void Grid::setRowHeight(int height) } -void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) +void Grid::setColumnConfig(const std::vector<Grid::ColAttributes>& attr) { //hold ownership of non-visible columns oldColAttributes_ = attr; std::vector<VisibleColumn> visCols; - for (const ColumnAttribute& ca : attr) + for (const ColAttributes& ca : attr) { - assert(ca.type_ != ColumnType::NONE); - if (ca.visible_) - visCols.emplace_back(ca.type_, ca.offset_, ca.stretch_); + assert(ca.stretch >= 0); + assert(ca.type != ColumnType::NONE); + + if (ca.visible) + visCols.push_back({ ca.type, ca.offset, std::max(ca.stretch, 0) }); } //"ownership" of visible columns is now within Grid @@ -1725,23 +1744,23 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) } -std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const +std::vector<Grid::ColAttributes> Grid::getColumnConfig() const { //get non-visible columns (+ outdated visible ones) - std::vector<ColumnAttribute> output = oldColAttributes_; + std::vector<ColAttributes> output = oldColAttributes_; auto iterVcols = visibleCols_.begin(); auto iterVcolsend = visibleCols_.end(); //update visible columns but keep order of non-visible ones! - for (ColumnAttribute& ca : output) - if (ca.visible_) + for (ColAttributes& ca : output) + if (ca.visible) { if (iterVcols != iterVcolsend) { - ca.type_ = iterVcols->type_; - ca.stretch_ = iterVcols->stretch_; - ca.offset_ = iterVcols->offset_; + ca.type = iterVcols->type; + ca.stretch = iterVcols->stretch; + ca.offset = iterVcols->offset; ++iterVcols; } else @@ -1807,7 +1826,7 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const int accuWidth = 0; for (size_t col = 0; col < absWidths.size(); ++col) { - accuWidth += absWidths[col].width_; + accuWidth += absWidths[col].width; if (std::abs(absPosX - accuWidth) < resizeTolerance) { ColAction out; @@ -1850,7 +1869,7 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths for (auto itCol = absWidths.begin(); itCol != absWidths.end(); ++itCol) { - const int width = itCol->width_; //beware dreaded unsigned conversions! + const int width = itCol->width; //beware dreaded unsigned conversions! accWidth += width; if (absPosX < accWidth - width / 2) @@ -1863,7 +1882,7 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const ColumnType Grid::colToType(size_t col) const { if (col < visibleCols_.size()) - return visibleCols_[col].type_; + return visibleCols_[col].type; return ColumnType::NONE; } @@ -1878,9 +1897,9 @@ Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const int accWidth = 0; for (const ColumnWidth& cw : getColWidths()) { - accWidth += cw.width_; + accWidth += cw.width; if (posX < accWidth) - return { cw.type_, posX + cw.width_ - accWidth, cw.width_ }; + return { cw.type, posX + cw.width - accWidth, cw.width }; } } return { ColumnType::NONE, 0, 0 }; @@ -1892,16 +1911,16 @@ wxRect Grid::getColumnLabelArea(ColumnType colType) const std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths //colType is not unique in general, but *this* function expects it! - assert(std::count_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }) <= 1); + assert(std::count_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type == colType; }) <= 1); - auto itCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }); + auto itCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type == colType; }); if (itCol != absWidths.end()) { ptrdiff_t posX = 0; for (auto it = absWidths.begin(); it != itCol; ++it) - posX += it->width_; + posX += it->width; - return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight_)); + return wxRect(wxPoint(posX, 0), wxSize(itCol->width, colLabelHeight_)); } return wxRect(); } @@ -1928,9 +1947,6 @@ void Grid::setGridCursor(size_t row) selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event - - mainWin_->Refresh(); - rowLabelWin_->Refresh(); //row labels! (Kubuntu) } @@ -1943,9 +1959,6 @@ void Grid::selectWithCursor(ptrdiff_t row) selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event - - mainWin_->Refresh(); - rowLabelWin_->Refresh(); } @@ -1954,43 +1967,45 @@ void Grid::makeRowVisible(size_t row) const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found if (labelRect.height > 0) { - int scrollPosX = 0; - GetViewStart(&scrollPosX, nullptr); - int pixelsPerUnitY = 0; GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); - if (pixelsPerUnitY <= 0) return; - - const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y; - if (clientPosY < 0) - { - const int scrollPosY = labelRect.y / pixelsPerUnitY; - Scroll(scrollPosX, scrollPosY); //internally calls wxWindows::Update()! - updateWindowSizes(); //may show horizontal scroll bar - } - else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight()) + if (pixelsPerUnitY > 0) { - auto execScroll = [&](int clientHeight) - { - const int scrollPosY = std::ceil((labelRect.y - clientHeight + - labelRect.height) / static_cast<double>(pixelsPerUnitY)); - Scroll(scrollPosX, scrollPosY); - updateWindowSizes(); //may show horizontal scroll bar - }; - - const int clientHeightBefore = rowLabelWin_->GetClientSize().GetHeight(); - execScroll(clientHeightBefore); + const wxPoint scrollPosOld = GetViewStart(); - //client height may decrease after scroll due to a new horizontal scrollbar, resulting in a partially visible last row - const int clientHeightAfter = rowLabelWin_->GetClientSize().GetHeight(); - if (clientHeightAfter < clientHeightBefore) - execScroll(clientHeightAfter); + const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y; + if (clientPosY < 0) + { + const int scrollPosNewY = labelRect.y / pixelsPerUnitY; + Scroll(scrollPosOld.x, scrollPosNewY); //internally calls wxWindows::Update()! + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider + Refresh(); + } + else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight()) + { + auto execScroll = [&](int clientHeight) + { + const int scrollPosNewY = std::ceil((labelRect.y - clientHeight + + labelRect.height) / static_cast<double>(pixelsPerUnitY)); + Scroll(scrollPosOld.x, scrollPosNewY); + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider + Refresh(); + }; + + const int clientHeightBefore = rowLabelWin_->GetClientSize().GetHeight(); + execScroll(clientHeightBefore); + + //client height may decrease after scroll due to a new horizontal scrollbar, resulting in a partially visible last row + const int clientHeightAfter = rowLabelWin_->GetClientSize().GetHeight(); + if (clientHeightAfter < clientHeightBefore) + execScroll(clientHeightAfter); + } } } } -void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated) +void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const MouseSelect* mouseSelect) { //sort + convert to half-open range auto rowFirst = std::min(rowFrom, rowTo); @@ -2001,13 +2016,12 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv numeric::clamp<ptrdiff_t>(rowLast, 0, rowCount); selection_.selectRange(rowFirst, rowLast, positive); + mainWin_->Refresh(); //notify event - GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated); + GridSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseSelect); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selectionEvent); - - mainWin_->Refresh(); } @@ -2020,15 +2034,13 @@ void Grid::scrollTo(size_t row) GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); if (pixelsPerUnitY > 0) { - const int scrollPosYNew = labelRect.y / pixelsPerUnitY; - int scrollPosXOld = 0; - int scrollPosYOld = 0; - GetViewStart(&scrollPosXOld, &scrollPosYOld); + const int scrollPosNewY = labelRect.y / pixelsPerUnitY; + const wxPoint scrollPosOld = GetViewStart(); - if (scrollPosYOld != scrollPosYNew) //support polling + if (scrollPosOld.y != scrollPosNewY) //support polling { - Scroll(scrollPosXOld, scrollPosYNew); //internally calls wxWindows::Update()! - updateWindowSizes(); //may show horizontal scroll bar + Scroll(scrollPosOld.x, scrollPosNewY); //internally calls wxWindows::Update()! + updateWindowSizes(); //may show horizontal scroll bar if row column gets wider Refresh(); } } @@ -2036,6 +2048,15 @@ void Grid::scrollTo(size_t row) } +size_t Grid::getTopRow() const +{ + const wxPoint absPos = CalcUnscrolledPosition(wxPoint(0, 0)); + const ptrdiff_t row = rowLabelWin_->getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + assert((getRowCount() == 0 && row == 0) || (0 <= row && row < static_cast<ptrdiff_t>(getRowCount()))); + return row; +} + + bool Grid::Enable(bool enable) { Refresh(); @@ -2053,7 +2074,7 @@ int Grid::getBestColumnSize(size_t col) const { if (dataView_ && col < visibleCols_.size()) { - const ColumnType type = visibleCols_[col].type_; + const ColumnType type = visibleCols_[col].type; wxClientDC dc(mainWin_); dc.SetFont(mainWin_->GetFont()); //harmonize with MainWin::render() @@ -2088,7 +2109,7 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve //unusual delay when enlarging the column again later width = std::max(width, COLUMN_MIN_WIDTH); - vcRs.offset_ = width - stretchedWidths[col]; //width := stretchedWidth + offset + vcRs.offset = width - stretchedWidths[col]; //width := stretchedWidth + offset //III. resizing any column should normalize *all* other stretched columns' offsets considering current mainWinWidth! // test case: @@ -2097,12 +2118,12 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve //3. shrink a fixed-size column so that the scrollbars vanish and columns cover full width again //4. now verify that the stretched column is resizing immediately if main window is enlarged again for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) - if (visibleCols_[col2].stretch_ > 0) //normalize stretched columns only - visibleCols_[col2].offset_ = std::max(visibleCols_[col2].offset_, COLUMN_MIN_WIDTH - stretchedWidths[col2]); + if (visibleCols_[col2].stretch > 0) //normalize stretched columns only + visibleCols_[col2].offset = std::max(visibleCols_[col2].offset, COLUMN_MIN_WIDTH - stretchedWidths[col2]); if (columnResizeEventPolicy == ALLOW_GRID_EVENT) { - GridColumnResizeEvent sizeEvent(vcRs.offset_, vcRs.type_); + GridColumnResizeEvent sizeEvent(vcRs.offset, vcRs.type); if (wxEvtHandler* evtHandler = GetEventHandler()) { if (notifyAsync) @@ -2125,7 +2146,7 @@ void Grid::autoSizeColumns(GridEventPolicy columnResizeEventPolicy) { const int bestWidth = getBestColumnSize(col); //return -1 on error if (bestWidth >= 0) - setColumnWidth(bestWidth, col, columnResizeEventPolicy, true); + setColumnWidth(bestWidth, col, columnResizeEventPolicy, true /*notifyAsync*/); } updateWindowSizes(); Refresh(); @@ -2140,8 +2161,8 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt int stretchTotal = 0; for (const VisibleColumn& vc : visibleCols_) { - assert(vc.stretch_ >= 0); - stretchTotal += vc.stretch_; + assert(vc.stretch >= 0); + stretchTotal += vc.stretch; } int remainingWidth = clientWidth; @@ -2154,7 +2175,7 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt { for (const VisibleColumn& vc : visibleCols_) { - const int width = clientWidth * vc.stretch_ / stretchTotal; //rounds down! + const int width = clientWidth * vc.stretch / stretchTotal; //rounds down! output.push_back(width); remainingWidth -= width; } @@ -2162,7 +2183,7 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution if (remainingWidth > 0) for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) - if (visibleCols_[col2].stretch_ > 0) + if (visibleCols_[col2].stretch > 0) { ++output[col2]; if (--remainingWidth == 0) @@ -2189,14 +2210,14 @@ std::vector<Grid::ColumnWidth> Grid::getColWidths(int mainWinWidth) const //eval for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) { const auto& vc = visibleCols_[col2]; - int width = stretchedWidths[col2] + vc.offset_; + int width = stretchedWidths[col2] + vc.offset; - if (vc.stretch_ > 0) + if (vc.stretch > 0) width = std::max(width, COLUMN_MIN_WIDTH); //normalization really needed here: e.g. smaller main window would result in negative width else width = std::max(width, 0); //support smaller width than COLUMN_MIN_WIDTH if set via configuration - output.emplace_back(vc.type_, width); + output.push_back({ vc.type, width }); } return output; } @@ -2206,6 +2227,6 @@ int Grid::getColWidthsSum(int mainWinWidth) const { int sum = 0; for (const ColumnWidth& cw : getColWidths(mainWinWidth)) - sum += cw.width_; + sum += cw.width; return sum; } @@ -28,7 +28,7 @@ extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEve extern const wxEventType EVENT_GRID_MOUSE_RIGHT_DOWN; // extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; // -extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent +extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridSelectEvent //NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window... extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridLabelClickEvent @@ -41,54 +41,60 @@ struct GridClickEvent : public wxMouseEvent { GridClickEvent(wxEventType et, const wxMouseEvent& me, ptrdiff_t row, HoverArea hoverArea) : wxMouseEvent(me), row_(row), hoverArea_(hoverArea) { SetEventType(et); } - wxEvent* Clone() const override { return new GridClickEvent(*this); } + GridClickEvent* Clone() const override { return new GridClickEvent(*this); } const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range const HoverArea hoverArea_; //may be HoverArea::NONE }; -struct GridRangeSelectEvent : public wxCommandEvent +struct MouseSelect { - GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const GridClickEvent* mouseInitiated) : + GridClickEvent click; + bool complete = false; //false if this is a preliminary "clear range" event for mouse-down, before the actual selection has happened during mouse-up +}; + +struct GridSelectEvent : public wxCommandEvent +{ + GridSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const MouseSelect* mouseSelect) : wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFirst_(rowFirst), rowLast_(rowLast), positive_(positive), - mouseInitiated_(mouseInitiated ? *mouseInitiated : Opt<GridClickEvent>()) { assert(rowFirst <= rowLast); } - wxEvent* Clone() const override { return new GridRangeSelectEvent(*this); } + mouseSelect_(mouseSelect ? *mouseSelect : Opt<MouseSelect>()) { assert(rowFirst <= rowLast); } + GridSelectEvent* Clone() const override { return new GridSelectEvent(*this); } const size_t rowFirst_; //selected range: [rowFirst_, rowLast_) const size_t rowLast_; const bool positive_; //"false" when clearing selection! - Opt<GridClickEvent> mouseInitiated_; //filled unless selection was performed via keyboard shortcuts or is result of Grid::clearSelection() + const Opt<MouseSelect> mouseSelect_; //filled unless selection was performed via keyboard shortcuts }; struct GridLabelClickEvent : public wxMouseEvent { GridLabelClickEvent(wxEventType et, const wxMouseEvent& me, ColumnType colType) : wxMouseEvent(me), colType_(colType) { SetEventType(et); } - wxEvent* Clone() const override { return new GridLabelClickEvent(*this); } + GridLabelClickEvent* Clone() const override { return new GridLabelClickEvent(*this); } const ColumnType colType_; //may be ColumnType::NONE }; - struct GridColumnResizeEvent : public wxCommandEvent { GridColumnResizeEvent(int offset, ColumnType colType) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset) {} - wxEvent* Clone() const override { return new GridColumnResizeEvent(*this); } + GridColumnResizeEvent* Clone() const override { return new GridColumnResizeEvent(*this); } const ColumnType colType_; const int offset_; }; using GridClickEventFunction = void (wxEvtHandler::*)(GridClickEvent&); -using GridRangeSelectEventFunction = void (wxEvtHandler::*)(GridRangeSelectEvent&); +using GridSelectEventFunction = void (wxEvtHandler::*)(GridSelectEvent&); using GridLabelClickEventFunction = void (wxEvtHandler::*)(GridLabelClickEvent&); using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEvent&); -#define GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) -#define GridRangeSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func) -#define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func) +#define GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) +#define GridSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridSelectEventFunction, &func) +#define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func) #define GridColumnResizeEventHandler(func)(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) //------------------------------------------------------------------------------------------------------------ + class Grid; @@ -146,19 +152,18 @@ public: void setRowHeight(int height); - struct ColumnAttribute + struct ColAttributes { - ColumnAttribute(ColumnType type, int offset, int stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max(stretch, 0)), offset_(offset) { assert(stretch >=0 ); } - ColumnType type_; - bool visible_; + ColumnType type = ColumnType::NONE; //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! - int stretch_; //>= 0 - int offset_; + int offset = 0; + int stretch = 0; //>= 0 + bool visible = false; }; - void setColumnConfig(const std::vector<ColumnAttribute>& attr); //set column count + widths - std::vector<ColumnAttribute> getColumnConfig() const; + void setColumnConfig(const std::vector<ColAttributes>& attr); //set column count + widths + std::vector<ColAttributes> getColumnConfig() const; void setDataProvider(const std::shared_ptr<GridData>& dataView) { dataView_ = dataView; } /**/ GridData* getDataProvider() { return dataView_.get(); } @@ -178,8 +183,9 @@ public: void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); std::vector<size_t> getSelectedRows() const { return selection_.get(); } - void selectAllRows (GridEventPolicy rangeEventPolicy); - void clearSelection(GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion! + void selectRow(size_t row, GridEventPolicy rangeEventPolicy); + void selectAllRows (GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion! + void clearSelection(GridEventPolicy rangeEventPolicy) { clearSelectionImpl(nullptr /*mouseSelect*/, rangeEventPolicy); } // void scrollDelta(int deltaX, int deltaY); //in scroll units @@ -193,9 +199,9 @@ public: struct ColumnPosInfo { - ColumnType colType; //ColumnType::NONE no column at x position! - int cellRelativePosX; - int colWidth; + ColumnType colType = ColumnType::NONE; //ColumnType::NONE no column at x position! + int cellRelativePosX = 0; + int colWidth = 0; }; ColumnPosInfo getColumnAtPos(int posX) const; //absolute position! @@ -208,6 +214,9 @@ public: size_t getGridCursor() const; //returns row void scrollTo(size_t row); + size_t getTopRow() const; + + void makeRowVisible(size_t row); void Refresh(bool eraseBackground = true, const wxRect* rect = nullptr) override; bool Enable(bool enable = true) override; @@ -226,7 +235,6 @@ private: void updateWindowSizes(bool updateScrollbar = true); void selectWithCursor(ptrdiff_t row); - void makeRowVisible(size_t row); void redirectRowLabelEvent(wxMouseEvent& event); @@ -247,53 +255,52 @@ private: class Selection { public: - void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); } + void init(size_t rowCount) { selected_.resize(rowCount); clear(); } - size_t maxSize() const { return rowSelectionValue.size(); } + size_t maxSize() const { return selected_.size(); } std::vector<size_t> get() const { std::vector<size_t> result; - for (size_t row = 0; row < rowSelectionValue.size(); ++row) - if (rowSelectionValue[row] != 0) + for (size_t row = 0; row < selected_.size(); ++row) + if (selected_[row] != 0) result.push_back(row); return result; } - void selectAll() { selectRange(0, rowSelectionValue.size(), true); } - void clear () { selectRange(0, rowSelectionValue.size(), false); } + void selectRow(size_t row) { selectRange(row, row + 1, true); } + void selectAll () { selectRange(0, selected_.size(), true); } + void clear () { selectRange(0, selected_.size(), false); } - bool isSelected(size_t row) const { return row < rowSelectionValue.size() ? rowSelectionValue[row] != 0 : false; } + bool isSelected(size_t row) const { return row < selected_.size() ? selected_[row] != 0 : false; } void selectRange(size_t rowFirst, size_t rowLast, bool positive = true) //select [rowFirst, rowLast), trims if required! { if (rowFirst <= rowLast) { - numeric::clamp<size_t>(rowFirst, 0, rowSelectionValue.size()); - numeric::clamp<size_t>(rowLast, 0, rowSelectionValue.size()); + numeric::clamp<size_t>(rowFirst, 0, selected_.size()); + numeric::clamp<size_t>(rowLast, 0, selected_.size()); - std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive); + std::fill(selected_.begin() + rowFirst, selected_.begin() + rowLast, positive); } else assert(false); } private: - std::vector<char> rowSelectionValue; //effectively a vector<bool> of size "number of rows" + std::vector<char> selected_; //effectively a vector<bool> of size "number of rows" }; struct VisibleColumn { - VisibleColumn(ColumnType type, int offset, int stretch) : type_(type), stretch_(stretch), offset_(offset) {} - ColumnType type_; - int stretch_; //>= 0 - int offset_; + ColumnType type = ColumnType::NONE; + int offset = 0; + int stretch = 0; //>= 0 }; struct ColumnWidth { - ColumnWidth(ColumnType type, int width) : type_(type), width_(width) {} - ColumnType type_; - int width_; + ColumnType type = ColumnType::NONE; + int width = 0; }; std::vector<ColumnWidth> getColWidths() const; // std::vector<ColumnWidth> getColWidths(int mainWinWidth) const; //evaluate stretched columns @@ -304,7 +311,7 @@ private: { const auto& widths = getColWidths(); if (col < widths.size()) - return widths[col].width_; + return widths[col].width; return NoValue(); } @@ -312,7 +319,9 @@ private: wxRect getColumnLabelArea(ColumnType colType) const; //returns empty rect if column not found - void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event! + void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const MouseSelect* mouseSelect); //select inclusive range [rowFrom, rowTo] + notify event! + + void clearSelectionImpl(const MouseSelect* mouseSelect, GridEventPolicy rangeEventPolicy); bool isSelected(size_t row) const { return selection_.isSelected(row); } @@ -352,11 +361,53 @@ private: bool allowColumnMove_ = true; bool allowColumnResize_ = true; - std::vector<VisibleColumn> visibleCols_; //individual widths, type and total column count - std::vector<ColumnAttribute> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! + std::vector<VisibleColumn> visibleCols_; //individual widths, type and total column count + std::vector<ColAttributes> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! size_t rowCountOld_ = 0; //at the time of last Grid::Refresh() }; + +//------------------------------------------------------------------------------------------------------------ + +template <class ColAttrReal> +std::vector<ColAttrReal> makeConsistent(const std::vector<ColAttrReal>& attribs, const std::vector<ColAttrReal>& defaults) +{ + using ColTypeReal = decltype(ColAttrReal().type); + std::vector<ColAttrReal> output; + + std::set<ColTypeReal> usedTypes; //remove duplicates + auto appendUnique = [&](const std::vector<ColAttrReal>& attr) + { + std::copy_if(attr.begin(), attr.end(), std::back_inserter(output), + [&](const ColAttrReal& a) { return usedTypes.insert(a.type).second; }); + }; + appendUnique(attribs); + appendUnique(defaults); //make sure each type is existing! + + return output; +} + + +template <class ColAttrReal> +std::vector<Grid::ColAttributes> convertColAttributes(const std::vector<ColAttrReal>& attribs, const std::vector<ColAttrReal>& defaults) +{ + std::vector<Grid::ColAttributes> output; + for (const ColAttrReal& ca : makeConsistent(attribs, defaults)) + output.push_back({ static_cast<ColumnType>(ca.type), ca.offset, ca.stretch, ca.visible }); + return output; +} + + +template <class ColAttrReal> +std::vector<ColAttrReal> convertColAttributes(const std::vector<Grid::ColAttributes>& attribs) +{ + using ColTypeReal = decltype(ColAttrReal().type); + + std::vector<ColAttrReal> output; + for (const Grid::ColAttributes& ca : attribs) + output.push_back({ static_cast<ColTypeReal>(ca.type), ca.offset, ca.stretch, ca.visible }); + return output; +} } #endif //GRID_H_834702134831734869987 diff --git a/wx+/http.cpp b/wx+/http.cpp index dd3cb3bc..fa88bb1d 100755 --- a/wx+/http.cpp +++ b/wx+/http.cpp @@ -77,22 +77,24 @@ public: size_t read(void* buffer, size_t bytesToRead) //throw SysError, X; return "bytesToRead" bytes unless end of stream! { const size_t blockSize = getBlockSize(); - assert(memBuf_.size() <= blockSize); + assert(memBuf_.size() >= blockSize); + assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size()); + char* it = static_cast<char*>(buffer); char* const itEnd = it + bytesToRead; for (;;) { - const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), memBuf_.size()); - std::copy (memBuf_.begin(), memBuf_.begin() + junkSize, it); - memBuf_.erase(memBuf_.begin(), memBuf_.begin() + junkSize); - it += junkSize; + const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_); + std::memcpy(it, &memBuf_[0] + bufPos_, junkSize); + bufPos_ += junkSize; + it += junkSize; if (it == itEnd) break; //-------------------------------------------------------------------- - memBuf_.resize(blockSize); const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0 - memBuf_.resize(bytesRead); + bufPos_ = 0; + bufPosEnd_ = bytesRead; if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X @@ -137,8 +139,11 @@ private: wxHTTP webAccess_; std::unique_ptr<wxInputStream> httpStream_; //must be deleted BEFORE webAccess is closed - std::vector<char> memBuf_; const IOCallback notifyUnbufferedIO_; //throw X + + std::vector<char> memBuf_ = std::vector<char>(getBlockSize()); + size_t bufPos_ = 0; //buffered I/O; see file_io.cpp + size_t bufPosEnd_ = 0; // }; |