diff options
author | Daniel Wilhelm <shieldwed@outlook.com> | 2017-01-08 18:21:23 +0100 |
---|---|---|
committer | Daniel Wilhelm <shieldwed@outlook.com> | 2017-01-08 18:21:23 +0100 |
commit | fe660cdff59aa3a939479ed60172e5c0803552b2 (patch) | |
tree | 045cf295b79de10f75ed6362c5836db25c9fc63a /wx+ | |
parent | 8.6 (diff) | |
download | FreeFileSync-fe660cdff59aa3a939479ed60172e5c0803552b2.tar.gz FreeFileSync-fe660cdff59aa3a939479ed60172e5c0803552b2.tar.bz2 FreeFileSync-fe660cdff59aa3a939479ed60172e5c0803552b2.zip |
8.7
Diffstat (limited to 'wx+')
-rw-r--r-- | wx+/async_task.h | 39 | ||||
-rw-r--r-- | wx+/file_drop.h | 7 | ||||
-rw-r--r-- | wx+/grid.cpp | 356 | ||||
-rw-r--r-- | wx+/grid.h | 28 | ||||
-rw-r--r-- | wx+/http.cpp | 410 | ||||
-rw-r--r-- | wx+/http.h | 27 | ||||
-rw-r--r-- | wx+/image_tools.cpp | 2 | ||||
-rw-r--r-- | wx+/popup_dlg.cpp | 38 | ||||
-rw-r--r-- | wx+/popup_dlg.h | 17 | ||||
-rw-r--r-- | wx+/popup_dlg_generated.cpp | 5 | ||||
-rw-r--r-- | wx+/std_button_layout.h | 7 | ||||
-rw-r--r-- | wx+/tooltip.cpp | 62 | ||||
-rw-r--r-- | wx+/tooltip.h | 4 |
13 files changed, 584 insertions, 418 deletions
diff --git a/wx+/async_task.h b/wx+/async_task.h index 915f9602..7ac03949 100644 --- a/wx+/async_task.h +++ b/wx+/async_task.h @@ -24,6 +24,9 @@ Run a task in an async thread, but process result in GUI event loop 2. schedule async task and synchronous continuation: guiQueue.processAsync(evalAsync, evalOnGui); + +Alternative: use wxWidgets' inter-thread communication (wxEvtHandler::QueueEvent) https://wiki.wxwidgets.org/Inter-Thread_and_Inter-Process_communication + => don't bother, probably too many MT race conditions lurking around */ namespace impl @@ -74,7 +77,7 @@ public: void add(Fun&& evalAsync, Fun2&& evalOnGui) { using ResultType = decltype(evalAsync()); - tasks.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(evalOnGui))); + tasks_.push_back(std::make_unique<ConcreteTask<ResultType, Fun2>>(zen::runAsync(std::forward<Fun>(evalAsync)), std::forward<Fun2>(evalOnGui))); } //equivalent to "evalOnGui(evalAsync())" // -> evalAsync: the usual thread-safety requirements apply! @@ -82,14 +85,14 @@ public: void evalResults() //call from gui thread repreatedly { - if (!inRecursion) //prevent implicit recursion, e.g. if we're called from an idle event and spawn another one within the callback below + if (!inRecursion_) //prevent implicit recursion, e.g. if we're called from an idle event and spawn another one within the callback below { - inRecursion = true; - ZEN_ON_SCOPE_EXIT(inRecursion = false); + inRecursion_ = true; + ZEN_ON_SCOPE_EXIT(inRecursion_ = false); std::vector<std::unique_ptr<Task>> readyTasks; //Reentrancy; access to AsyncTasks::add is not protected! => evaluate outside erase_if - erase_if(tasks, [&](std::unique_ptr<Task>& task) + erase_if(tasks_, [&](std::unique_ptr<Task>& task) { if (task->resultReady()) { @@ -104,14 +107,14 @@ public: } } - bool empty() const { return tasks.empty(); } + bool empty() const { return tasks_.empty(); } private: AsyncTasks (const AsyncTasks&) = delete; AsyncTasks& operator=(const AsyncTasks&) = delete; - bool inRecursion = false; - std::vector<std::unique_ptr<Task>> tasks; + bool inRecursion_ = false; + std::vector<std::unique_ptr<Task>> tasks_; }; } @@ -119,27 +122,27 @@ private: class AsyncGuiQueue : private wxEvtHandler { public: - AsyncGuiQueue() { timer.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); } + AsyncGuiQueue() { timer_.Connect(wxEVT_TIMER, wxEventHandler(AsyncGuiQueue::onTimerEvent), nullptr, this); } template <class Fun, class Fun2> void processAsync(Fun&& evalAsync, Fun2&& evalOnGui) { - asyncTasks.add(std::forward<Fun >(evalAsync), - std::forward<Fun2>(evalOnGui)); - if (!timer.IsRunning()) - timer.Start(50 /*unit: [ms]*/); + asyncTasks_.add(std::forward<Fun >(evalAsync), + std::forward<Fun2>(evalOnGui)); + if (!timer_.IsRunning()) + timer_.Start(50 /*unit: [ms]*/); } private: void onTimerEvent(wxEvent& event) //schedule and run long-running tasks asynchronously { - asyncTasks.evalResults(); //process results on GUI queue - if (asyncTasks.empty()) - timer.Stop(); + asyncTasks_.evalResults(); //process results on GUI queue + if (asyncTasks_.empty()) + timer_.Stop(); } - impl::AsyncTasks asyncTasks; - wxTimer timer; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu! + impl::AsyncTasks asyncTasks_; + wxTimer timer_; //don't use wxWidgets' idle handling => repeated idle requests/consumption hogs 100% cpu! }; } diff --git a/wx+/file_drop.h b/wx+/file_drop.h index c3ffe09c..57880ce2 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -134,6 +134,13 @@ public: private: bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray) override { + /*Linux, MTP: we get an empty file array + => switching to wxTextDropTarget won't help (much): we'd get the format + mtp://[usb:001,002]/Telefonspeicher/Folder/file.txt + instead of + /run/user/1000/gvfs/mtp:host=%5Busb%3A001%2C002%5D/Telefonspeicher/Folder/file.txt + */ + //wxPoint clientDropPos(x, y) std::vector<Zstring> filePaths; for (const wxString& file : fileArray) diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 13c06704..5d393f08 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -359,8 +359,18 @@ private: void onPaintEvent(wxPaintEvent& event) { +#ifndef NDEBUG +#ifdef ZEN_WIN + if (runningPaintEvent_ == true) //looks like showing the assert window here would quit the debug session + __debugbreak(); +#else + assert(runningPaintEvent_ == false); //catch unexpected recursion, e.g.: getIconByIndex() seems to run a message loop in rare cases! +#endif + runningPaintEvent_ = true; + ZEN_ON_SCOPE_EXIT(runningPaintEvent_ = false); +#endif //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! - BufferedPaintDC dc(*this, doubleBuffer); + BufferedPaintDC dc(*this, doubleBuffer_); assert(GetSize() == GetClientSize()); @@ -378,7 +388,10 @@ private: void onEraseBackGround(wxEraseEvent& event) {} Grid& parent_; - Opt<wxBitmap> doubleBuffer; + Opt<wxBitmap> doubleBuffer_; +#ifndef NDEBUG + bool runningPaintEvent_ = false; +#endif }; //---------------------------------------------------------------------------------------------------------------- @@ -427,7 +440,7 @@ class Grid::RowLabelWin : public SubWindow public: RowLabelWin(Grid& parent) : SubWindow(parent), - rowHeight(parent.GetCharHeight() + 2 + 1) {} //default height; don't call any functions on "parent" other than those from wxWindow during construction! + rowHeight_(parent.GetCharHeight() + 2 + 1) {} //default height; don't call any functions on "parent" other than those from wxWindow during construction! //2 for some more space, 1 for bottom border (gives 15 + 2 + 1 on Windows, 17 + 2 + 1 on Ubuntu) int getBestWidth(ptrdiff_t rowFrom, ptrdiff_t rowTo) @@ -444,27 +457,27 @@ public: return bestWidth; } - size_t getLogicalHeight() const { return refParent().getRowCount() * rowHeight; } + size_t getLogicalHeight() const { return refParent().getRowCount() * rowHeight_; } ptrdiff_t getRowAtPos(ptrdiff_t posY) const //returns < 0 on invalid input, else row number within: [0, rowCount]; rowCount if out of range { - if (posY >= 0 && rowHeight > 0) + if (posY >= 0 && rowHeight_ > 0) { - const size_t row = posY / rowHeight; + const size_t row = posY / rowHeight_; return std::min(row, refParent().getRowCount()); } return -1; } - int getRowHeight() const { return rowHeight; } //guarantees to return size >= 1 ! - void setRowHeight(int height) { assert(height > 0); rowHeight = std::max(1, height); } + int getRowHeight() const { return rowHeight_; } //guarantees to return size >= 1 ! + void setRowHeight(int height) { assert(height > 0); rowHeight_ = std::max(1, height); } wxRect getRowLabelArea(size_t row) const //returns empty rect if row not found { assert(GetClientAreaOrigin() == wxPoint()); if (row < refParent().getRowCount()) - return wxRect(wxPoint(0, rowHeight * row), - wxSize(GetClientSize().GetWidth(), rowHeight)); + return wxRect(wxPoint(0, rowHeight_ * row), + wxSize(GetClientSize().GetWidth(), rowHeight_)); return wxRect(); } @@ -473,8 +486,8 @@ public: const int yFrom = refParent().CalcUnscrolledPosition(clientRect.GetTopLeft ()).y; const int yTo = refParent().CalcUnscrolledPosition(clientRect.GetBottomRight()).y; - return std::make_pair(std::max(yFrom / rowHeight, 0), - std::min<ptrdiff_t>((yTo / rowHeight) + 1, refParent().getRowCount())); + return std::make_pair(std::max(yFrom / rowHeight_, 0), + std::min<ptrdiff_t>((yTo / rowHeight_) + 1, refParent().getRowCount())); } private: @@ -552,7 +565,7 @@ private: void onMouseMovement(wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); } void onMouseLeftUp (wxMouseEvent& event) override { refParent().redirectRowLabelEvent(event); } - int rowHeight; + int rowHeight_; }; @@ -670,21 +683,21 @@ private: { if (auto dataView = refParent().getDataProvider()) { - const bool isHighlighted = activeResizing ? col == activeResizing ->getColumn () : //highlight column on mouse-over - activeClickOrMove ? col == activeClickOrMove->getColumnFrom() : - highlightCol ? col == *highlightCol : + const bool isHighlighted = activeResizing_ ? col == activeResizing_ ->getColumn () : //highlight_ column on mouse-over + activeClickOrMove_ ? col == activeClickOrMove_->getColumnFrom() : + highlightCol_ ? col == *highlightCol_ : false; RecursiveDcClipper clip(dc, rect); dataView->renderColumnLabel(refParent(), dc, rect, colType, isHighlighted); //draw move target location - if (refParent().allowColumnMove) - if (activeClickOrMove && activeClickOrMove->isRealMove()) + if (refParent().allowColumnMove_) + if (activeClickOrMove_ && activeClickOrMove_->isRealMove()) { - if (col + 1 == activeClickOrMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position + if (col + 1 == activeClickOrMove_->refColumnTo()) //handle pos 1, 2, .. up to "at end" position dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); - else if (col == activeClickOrMove->refColumnTo() && col == 0) //pos 0 + else if (col == activeClickOrMove_->refColumnTo() && col == 0) //pos 0 dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); } } @@ -695,8 +708,8 @@ private: if (FindFocus() != &refParent().getMainWin()) refParent().getMainWin().SetFocus(); - activeResizing.reset(); - activeClickOrMove.reset(); + activeResizing_.reset(); + activeClickOrMove_.reset(); if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { @@ -704,26 +717,26 @@ private: { if (!event.LeftDClick()) //double-clicks never seem to arrive here; why is this checked at all??? if (Opt<int> colWidth = refParent().getColWidth(action->col)) - activeResizing = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x); + activeResizing_ = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x); } else //a move or single click - activeClickOrMove = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); + activeClickOrMove_ = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); } event.Skip(); } void onMouseLeftUp(wxMouseEvent& event) override { - activeResizing.reset(); //nothing else to do, actual work done by onMouseMovement() + activeResizing_.reset(); //nothing else to do, actual work done by onMouseMovement() - if (activeClickOrMove) + if (activeClickOrMove_) { - if (activeClickOrMove->isRealMove()) + if (activeClickOrMove_->isRealMove()) { - if (refParent().allowColumnMove) + if (refParent().allowColumnMove_) { - const size_t colFrom = activeClickOrMove->getColumnFrom(); - size_t colTo = activeClickOrMove->refColumnTo(); + const size_t colFrom = activeClickOrMove_->getColumnFrom(); + size_t colTo = activeClickOrMove_->refColumnTo(); if (colTo > colFrom) //simulate "colFrom" deletion --colTo; @@ -733,10 +746,10 @@ private: } else //notify single label click { - if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove->getColumnFrom())) + if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove_->getColumnFrom())) sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, *colType)); } - activeClickOrMove.reset(); + activeClickOrMove_.reset(); } refParent().updateWindowSizes(); //looks strange if done during onMouseMovement() @@ -746,8 +759,8 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { - activeResizing.reset(); - activeClickOrMove.reset(); + activeResizing_.reset(); + activeClickOrMove_.reset(); Refresh(); //event.Skip(); -> we DID handle it! } @@ -770,10 +783,10 @@ private: void onMouseMovement(wxMouseEvent& event) override { - if (activeResizing) + if (activeResizing_) { - const auto col = activeResizing->getColumn(); - const int newWidth = activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX(); + const auto col = activeResizing_->getColumn(); + const int newWidth = activeResizing_->getStartWidth() + event.GetPosition().x - activeResizing_->getStartPosX(); //set width tentatively refParent().setColumnWidth(newWidth, col, ALLOW_GRID_EVENT); @@ -785,23 +798,23 @@ private: refParent().Refresh(); //refresh columns on main grid as well! } - else if (activeClickOrMove) + else if (activeClickOrMove_) { const int clientPosX = event.GetPosition().x; - if (std::abs(clientPosX - activeClickOrMove->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) + if (std::abs(clientPosX - activeClickOrMove_->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) { - activeClickOrMove->setRealMove(); + activeClickOrMove_->setRealMove(); const ptrdiff_t col = refParent().clientPosToMoveTargetColumn(event.GetPosition()); if (col >= 0) - activeClickOrMove->refColumnTo() = col; + activeClickOrMove_->refColumnTo() = col; } } else { if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { - highlightCol = action->col; + highlightCol_ = action->col; if (action->wantResize) SetCursor(wxCURSOR_SIZEWE); //set window-local only! :) @@ -810,7 +823,7 @@ private: } else { - highlightCol = NoValue(); + highlightCol_ = NoValue(); SetCursor(*wxSTANDARD_CURSOR); } } @@ -832,7 +845,7 @@ private: void onLeaveWindow(wxMouseEvent& event) override { - highlightCol = NoValue(); //wxEVT_LEAVE_WINDOW does not respect mouse capture! -> however highlight is drawn unconditionally during move/resize! + highlightCol_ = NoValue(); //wxEVT_LEAVE_WINDOW does not respect mouse capture! -> however highlight_ is drawn unconditionally during move/resize! Refresh(); event.Skip(); } @@ -853,9 +866,9 @@ private: event.Skip(); } - std::unique_ptr<ColumnResizing> activeResizing; - std::unique_ptr<ColumnMove> activeClickOrMove; - Opt<size_t> highlightCol; //column during mouse-over + std::unique_ptr<ColumnResizing> activeResizing_; + std::unique_ptr<ColumnMove> activeClickOrMove_; + Opt<size_t> highlightCol_; //column during mouse-over }; //---------------------------------------------------------------------------------------------------------------- @@ -877,16 +890,16 @@ public: Connect(EVENT_GRID_HAS_SCROLLED, wxEventHandler(MainWin::onRequestWindowUpdate), nullptr, this); } - ~MainWin() { assert(!gridUpdatePending); } + ~MainWin() { assert(!gridUpdatePending_); } - size_t getCursor() const { return cursorRow; } - size_t getAnchor() const { return selectionAnchor; } + size_t getCursor() const { return cursorRow_; } + size_t getAnchor() const { return selectionAnchor_; } void setCursor(size_t newCursorRow, size_t newAnchorRow) { - cursorRow = newCursorRow; - selectionAnchor = newAnchorRow; - activeSelection.reset(); //e.g. user might search with F3 while holding down left mouse button + cursorRow_ = newCursorRow; + selectionAnchor_ = newAnchorRow; + activeSelection_.reset(); //e.g. user might search with F3 while holding down left mouse button } private: @@ -948,25 +961,25 @@ private: HoverArea getRowHoverToDraw(ptrdiff_t row) const { - if (activeSelection) + if (activeSelection_) { - if (activeSelection->getFirstClick().row_ == row) - return activeSelection->getFirstClick().hoverArea_; + if (activeSelection_->getFirstClick().row_ == row) + return activeSelection_->getFirstClick().hoverArea_; } - else if (highlight.row == row) - return highlight.rowHover; + else if (highlight_.row == row) + return highlight_.rowHover; return HoverArea::NONE; } bool drawAsSelected(size_t row) const { - if (activeSelection) //check if user is currently selecting with mouse + if (activeSelection_) //check if user is currently selecting with mouse { - const size_t rowFrom = std::min(activeSelection->getStartRow(), activeSelection->getCurrentRow()); - const size_t rowTo = std::max(activeSelection->getStartRow(), activeSelection->getCurrentRow()); + const size_t rowFrom = std::min(activeSelection_->getStartRow(), activeSelection_->getCurrentRow()); + const size_t rowTo = std::max(activeSelection_->getStartRow(), activeSelection_->getCurrentRow()); if (rowFrom <= row && row <= rowTo) - return activeSelection->isPositiveSelect(); //overwrite default + return activeSelection_->isPositiveSelect(); //overwrite default } return refParent().isSelected(row); } @@ -1009,15 +1022,15 @@ private: 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), mouseEvent); else if (event.ShiftDown()) { - activeSelection = std::make_unique<MouseSelection>(*this, selectionAnchor, true, mouseEvent); + activeSelection_ = std::make_unique<MouseSelection>(*this, selectionAnchor_, true, mouseEvent); refParent().clearSelection(ALLOW_GRID_EVENT); } else { - activeSelection = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); + activeSelection_ = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); refParent().clearSelection(ALLOW_GRID_EVENT); } } @@ -1032,31 +1045,31 @@ private: void onMouseUp(wxMouseEvent& event) { - if (activeSelection) + if (activeSelection_) { const size_t rowCount = refParent().getRowCount(); if (rowCount > 0) { - if (activeSelection->getCurrentRow() < rowCount) + if (activeSelection_->getCurrentRow() < rowCount) { - cursorRow = activeSelection->getCurrentRow(); - selectionAnchor = activeSelection->getStartRow(); //allowed to be "out of range" + cursorRow_ = activeSelection_->getCurrentRow(); + selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range" } - else if (activeSelection->getStartRow() < rowCount) //don't change cursor if "to" and "from" are out of range + else if (activeSelection_->getStartRow() < rowCount) //don't change cursor if "to" and "from" are out of range { - cursorRow = rowCount - 1; - selectionAnchor = activeSelection->getStartRow(); //allowed to be "out of range" + cursorRow_ = rowCount - 1; + selectionAnchor_ = activeSelection_->getStartRow(); //allowed to be "out of range" } else //total selection "out of range" - selectionAnchor = cursorRow; + selectionAnchor_ = cursorRow_; } //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(); + refParent().selectRangeAndNotify(activeSelection_->getStartRow (), //from + activeSelection_->getCurrentRow(), //to + activeSelection_->isPositiveSelect(), + &activeSelection_->getFirstClick()); + activeSelection_.reset(); } if (auto prov = refParent().getDataProvider()) @@ -1070,7 +1083,7 @@ private: sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, rowHover)); } - //update highlight and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) + //update highlight_ and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! onMouseMovement(event); @@ -1080,8 +1093,8 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { - activeSelection.reset(); - highlight.row = -1; + activeSelection_.reset(); + highlight_.row = -1; Refresh(); //event.Skip(); -> we DID handle it! } @@ -1104,14 +1117,14 @@ private: }(); setToolTip(toolTip); //show even during mouse selection! - if (activeSelection) - activeSelection->evalMousePos(); //call on both mouse movement + timer event! + if (activeSelection_) + activeSelection_->evalMousePos(); //call on both mouse movement + timer event! else { - refreshHighlight(highlight); - highlight.row = row; - highlight.rowHover = rowHover; - refreshHighlight(highlight); //multiple Refresh() calls are condensed into single one! + refreshHighlight(highlight_); + highlight_.row = row; + highlight_.rowHover = rowHover; + refreshHighlight(highlight_); //multiple Refresh() calls are condensed into single one! } } event.Skip(); @@ -1119,10 +1132,10 @@ private: void onLeaveWindow(wxMouseEvent& event) override //wxEVT_LEAVE_WINDOW does not respect mouse capture! { - if (!activeSelection) + if (!activeSelection_) { - refreshHighlight(highlight); - highlight.row = -1; + refreshHighlight(highlight_); + highlight_.row = -1; } event.Skip(); @@ -1138,8 +1151,8 @@ private: wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick) { wnd_.CaptureMouse(); - timer.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); - timer.Start(100); //timer interval in ms + timer_.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); + timer_.Start(100); //timer interval in ms evalMousePos(); } ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } @@ -1152,8 +1165,8 @@ private: void evalMousePos() { const auto now = std::chrono::steady_clock::now(); - const double deltaSecs = std::chrono::duration<double>(now - lastEvalTime).count(); //unit: [sec] - lastEvalTime = now; + const double deltaSecs = std::chrono::duration<double>(now - lastEvalTime_).count(); //unit: [sec] + lastEvalTime_ = now; const wxPoint clientPos = wnd_.ScreenToClient(wxGetMousePosition()); const wxSize clientSize = wnd_.GetClientSize(); @@ -1182,14 +1195,14 @@ private: toScroll = 0; }; - autoScroll(overlapPixX, toScrollX); - autoScroll(overlapPixY, toScrollY); + autoScroll(overlapPixX, toScrollX_); + autoScroll(overlapPixY, toScrollY_); - if (static_cast<int>(toScrollX) != 0 || static_cast<int>(toScrollY) != 0) + if (static_cast<int>(toScrollX_) != 0 || static_cast<int>(toScrollY_) != 0) { - wnd_.refParent().scrollDelta(static_cast<int>(toScrollX), static_cast<int>(toScrollY)); // - toScrollX -= static_cast<int>(toScrollX); //rounds down for positive numbers, up for negative, - toScrollY -= static_cast<int>(toScrollY); //exactly what we want + wnd_.refParent().scrollDelta(static_cast<int>(toScrollX_), static_cast<int>(toScrollY_)); // + toScrollX_ -= static_cast<int>(toScrollX_); //rounds down for positive numbers, up for negative, + toScrollY_ -= static_cast<int>(toScrollY_); //exactly what we want } //select current row *after* scrolling @@ -1214,10 +1227,10 @@ private: ptrdiff_t rowCurrent_; const bool positiveSelect_; const GridClickEvent firstClick_; - wxTimer timer; - double toScrollX = 0; //count outstanding scroll unit fractions while dragging mouse - double toScrollY = 0; // - std::chrono::steady_clock::time_point lastEvalTime = std::chrono::steady_clock::now(); + wxTimer timer_; + double toScrollX_ = 0; //count outstanding scroll unit fractions while dragging mouse + double toScrollY_ = 0; // + std::chrono::steady_clock::time_point lastEvalTime_ = std::chrono::steady_clock::now(); }; struct MouseHighlight @@ -1236,12 +1249,12 @@ private: //which *first* calls us, MainWin::ScrollWindow(), and *then* internally updates m_yScrollPosition //=> we cannot use CalcUnscrolledPosition() here which gives the wrong/outdated value!!! //=> we need to update asynchronously: - //=> don't use plain async event => severe performance issues on wxGTK! + //=> don't send async event repeatedly => severe performance issues on wxGTK! //=> can't use idle event neither: too few idle events on Windows, e.g. NO idle events while mouse drag-scrolling! //=> solution: send single async event at most! - if (!gridUpdatePending) //without guarding, the number of outstanding async events can become very high during scrolling!! test case: Ubuntu: 170; Windows: 20 + if (!gridUpdatePending_) //without guarding, the number of outstanding async events can become very high during scrolling!! test case: Ubuntu: 170; Windows: 20 { - gridUpdatePending = true; + gridUpdatePending_ = true; wxCommandEvent scrollEvent(EVENT_GRID_HAS_SCROLLED); AddPendingEvent(scrollEvent); //asynchronously call updateAfterScroll() } @@ -1249,8 +1262,8 @@ private: void onRequestWindowUpdate(wxEvent& event) { - assert(gridUpdatePending); - ZEN_ON_SCOPE_EXIT(gridUpdatePending = false); + assert(gridUpdatePending_); + ZEN_ON_SCOPE_EXIT(gridUpdatePending_ = false); refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! -> still a problem, now that we're called async?? rowLabelWin_.Update(); //update while dragging scroll thumb @@ -1267,19 +1280,19 @@ private: void refreshHighlight(const MouseHighlight& hl) { const ptrdiff_t rowCount = refParent().getRowCount(); - if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight? => NOP! + if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight_? => NOP! refreshRow(hl.row); } RowLabelWin& rowLabelWin_; ColLabelWin& colLabelWin_; - std::unique_ptr<MouseSelection> activeSelection; //bound while user is selecting with mouse - MouseHighlight highlight; //current mouse highlight (superseeded by activeSelection if available) + std::unique_ptr<MouseSelection> activeSelection_; //bound while user is selecting with mouse + MouseHighlight highlight_; //current mouse highlight_ (superseeded by activeSelection_ if available) - ptrdiff_t cursorRow = 0; - size_t selectionAnchor = 0; - bool gridUpdatePending = false; + ptrdiff_t cursorRow_ = 0; + size_t selectionAnchor_ = 0; + bool gridUpdatePending_ = false; }; //---------------------------------------------------------------------------------------------------------------- @@ -1353,7 +1366,7 @@ void Grid::updateWindowSizes(bool updateScrollbar) const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // int rowLabelWidth = 0; - if (drawRowLabel && logicalHeight > 0) + if (drawRowLabel_ && logicalHeight > 0) { ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; @@ -1406,8 +1419,8 @@ void Grid::updateWindowSizes(bool updateScrollbar) 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) + 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 { @@ -1461,7 +1474,7 @@ wxSize Grid::GetSizeAvailableForScrollTarget(const wxSize& size) const ptrdiff_t logicalHeight = rowLabelWin_->getLogicalHeight(); // int rowLabelWidth = 0; - if (drawRowLabel && logicalHeight > 0) + if (drawRowLabel_ && logicalHeight > 0) { ptrdiff_t yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; ptrdiff_t yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeightGross - 1)).y ; @@ -1624,14 +1637,14 @@ void Grid::setColumnLabelHeight(int height) void Grid::showRowLabel(bool show) { - drawRowLabel = show; + drawRowLabel_ = show; updateWindowSizes(); } void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) { - selection.selectAll(); + selection_.selectAll(); mainWin_->Refresh(); if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction @@ -1645,7 +1658,7 @@ void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) void Grid::clearSelection(GridEventPolicy rangeEventPolicy) { - selection.clear(); + selection_.clear(); mainWin_->Refresh(); if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction @@ -1694,14 +1707,14 @@ size_t Grid::getRowCount() const void Grid::Refresh(bool eraseBackground, const wxRect* rect) { const size_t rowCountNew = getRowCount(); - if (rowCountOld != rowCountNew) + if (rowCountOld_ != rowCountNew) { - rowCountOld = rowCountNew; + rowCountOld_ = rowCountNew; updateWindowSizes(); } - if (selection.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) - selection.init(rowCountNew); + if (selection_.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) + selection_.init(rowCountNew); wxScrolledWindow::Refresh(eraseBackground, rect); } @@ -1718,7 +1731,7 @@ void Grid::setRowHeight(int height) void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) { //hold ownership of non-visible columns - oldColAttributes = attr; + oldColAttributes_ = attr; std::vector<VisibleColumn> visCols; for (const ColumnAttribute& ca : attr) @@ -1729,7 +1742,7 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) } //"ownership" of visible columns is now within Grid - visibleCols = visCols; + visibleCols_ = visCols; updateWindowSizes(); Refresh(); @@ -1739,10 +1752,10 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const { //get non-visible columns (+ outdated visible ones) - std::vector<ColumnAttribute> output = oldColAttributes; + std::vector<ColumnAttribute> output = oldColAttributes_; - auto iterVcols = visibleCols.begin(); - auto iterVcolsend = visibleCols.end(); + auto iterVcols = visibleCols_.begin(); + auto iterVcolsend = visibleCols_.end(); //update visible columns but keep order of non-visible ones! for (ColumnAttribute& ca : output) @@ -1766,11 +1779,11 @@ std::vector<Grid::ColumnAttribute> Grid::getColumnConfig() const void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatus vertical) { - if (showScrollbarX == horizontal && - showScrollbarY == vertical) return; //support polling! + if (showScrollbarX_ == horizontal && + showScrollbarY_ == vertical) return; //support polling! - showScrollbarX = horizontal; - showScrollbarY = vertical; + showScrollbarX_ = horizontal; + showScrollbarY_ = vertical; #if defined ZEN_WIN || defined ZEN_MAC //handled by Grid::SetScrollbar @@ -1812,9 +1825,9 @@ void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, ScrollBarStatus sbStatus = SB_SHOW_AUTOMATIC; if (orientation == wxHORIZONTAL) - sbStatus = showScrollbarX; + sbStatus = showScrollbarX_; else if (orientation == wxVERTICAL) - sbStatus = showScrollbarY; + sbStatus = showScrollbarY_; else assert(false); @@ -1851,7 +1864,7 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const const int absPosX = CalcUnscrolledPosition(pos).x; if (absPosX >= 0) { - const int resizeTolerance = allowColumnResize ? COLUMN_RESIZE_TOLERANCE : 0; + const int resizeTolerance = allowColumnResize_ ? COLUMN_RESIZE_TOLERANCE : 0; std::vector<ColumnWidth> absWidths = getColWidths(); //resolve stretched widths int accuWidth = 0; @@ -1880,13 +1893,13 @@ Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const void Grid::moveColumn(size_t colFrom, size_t colTo) { - if (colFrom < visibleCols.size() && - colTo < visibleCols.size() && + if (colFrom < visibleCols_.size() && + colTo < visibleCols_.size() && colTo != colFrom) { - const VisibleColumn colAtt = visibleCols[colFrom]; - visibleCols.erase (visibleCols.begin() + colFrom); - visibleCols.insert(visibleCols.begin() + colTo, colAtt); + const VisibleColumn colAtt = visibleCols_[colFrom]; + visibleCols_.erase (visibleCols_.begin() + colFrom); + visibleCols_.insert(visibleCols_.begin() + colTo, colAtt); } } @@ -1912,8 +1925,8 @@ ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const ColumnType Grid::colToType(size_t col) const { - if (col < visibleCols.size()) - return visibleCols[col].type_; + if (col < visibleCols_.size()) + return visibleCols_[col].type_; return ColumnType::NONE; } @@ -1976,7 +1989,7 @@ void Grid::setGridCursor(size_t row) mainWin_->setCursor(row, row); makeRowVisible(row); - selection.clear(); //clear selection, do NOT fire event + selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); @@ -1991,7 +2004,7 @@ void Grid::selectWithCursor(ptrdiff_t row) mainWin_->setCursor(row, anchorRow); makeRowVisible(row); - selection.clear(); //clear selection, do NOT fire event + selection_.clear(); //clear selection, do NOT fire event selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); @@ -2050,7 +2063,7 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv numeric::clamp<ptrdiff_t>(rowFirst, 0, rowCount); numeric::clamp<ptrdiff_t>(rowLast, 0, rowCount); - selection.selectRange(rowFirst, rowLast, positive); + selection_.selectRange(rowFirst, rowLast, positive); //notify event GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated); @@ -2101,9 +2114,9 @@ size_t Grid::getGridCursor() const int Grid::getBestColumnSize(size_t col) const { - if (dataView_ && col < visibleCols.size()) + 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() @@ -2122,12 +2135,12 @@ int Grid::getBestColumnSize(size_t col) const void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEventPolicy, bool notifyAsync) { - if (col < visibleCols.size()) + if (col < visibleCols_.size()) { - VisibleColumn& vcRs = visibleCols[col]; + VisibleColumn& vcRs = visibleCols_[col]; const std::vector<int> stretchedWidths = getColStretchedWidths(mainWin_->GetClientSize().GetWidth()); - if (stretchedWidths.size() != visibleCols.size()) + if (stretchedWidths.size() != visibleCols_.size()) { assert(false); return; @@ -2146,9 +2159,9 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve //2. shrink main window width so that horizontal scrollbars are shown despite the streched column //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]); + 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 (columnResizeEventPolicy == ALLOW_GRID_EVENT) { @@ -2169,9 +2182,9 @@ void Grid::setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEve void Grid::autoSizeColumns(GridEventPolicy columnResizeEventPolicy) { - if (allowColumnResize) + if (allowColumnResize_) { - for (size_t col = 0; col < visibleCols.size(); ++col) + for (size_t col = 0; col < visibleCols_.size(); ++col) { const int bestWidth = getBestColumnSize(col); //return -1 on error if (bestWidth >= 0) @@ -2188,7 +2201,7 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt assert(clientWidth >= 0); clientWidth = std::max(clientWidth, 0); int stretchTotal = 0; - for (const VisibleColumn& vc : visibleCols) + for (const VisibleColumn& vc : visibleCols_) { assert(vc.stretch_ >= 0); stretchTotal += vc.stretch_; @@ -2199,28 +2212,27 @@ std::vector<int> Grid::getColStretchedWidths(int clientWidth) const //final widt std::vector<int> output; if (stretchTotal <= 0) - output.resize(visibleCols.size()); //fill with zeros + output.resize(visibleCols_.size()); //fill with zeros else - for (const VisibleColumn& vc : visibleCols) + { + for (const VisibleColumn& vc : visibleCols_) { const int width = clientWidth * vc.stretch_ / stretchTotal; //rounds down! output.push_back(width); remainingWidth -= width; } - //distribute *all* of clientWidth: should suffice to enlarge the first few stretched columns; no need to minimize total absolute error of distribution - if (stretchTotal > 0) + //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) + for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) + if (visibleCols_[col2].stretch_ > 0) { ++output[col2]; if (--remainingWidth == 0) - return output; + break; } - assert(false); - } + assert(remainingWidth == 0); + } return output; } @@ -2234,12 +2246,12 @@ std::vector<Grid::ColumnWidth> Grid::getColWidths() const std::vector<Grid::ColumnWidth> Grid::getColWidths(int mainWinWidth) const //evaluate stretched columns { const std::vector<int> stretchedWidths = getColStretchedWidths(mainWinWidth); - assert(stretchedWidths.size() == visibleCols.size()); + assert(stretchedWidths.size() == visibleCols_.size()); std::vector<ColumnWidth> output; - for (size_t col2 = 0; col2 < visibleCols.size(); ++col2) + for (size_t col2 = 0; col2 < visibleCols_.size(); ++col2) { - const auto& vc = visibleCols[col2]; + const auto& vc = visibleCols_[col2]; int width = stretchedWidths[col2] + vc.offset_; if (vc.stretch_ > 0) @@ -178,7 +178,7 @@ public: //alternative until wxScrollHelper::ShowScrollbars() becomes available in wxWidgets 2.9 void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); - std::vector<size_t> getSelectedRows() const { return selection.get(); } + 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! @@ -202,8 +202,8 @@ public: void refreshCell(size_t row, ColumnType colType); - void enableColumnMove (bool value) { allowColumnMove = value; } - void enableColumnResize(bool value) { allowColumnResize = value; } + void enableColumnMove (bool value) { allowColumnMove_ = value; } + void enableColumnResize(bool value) { allowColumnResize_ = value; } void setGridCursor(size_t row); //set + show + select cursor (+ emit range selection event) size_t getGridCursor() const; //returns row @@ -301,7 +301,7 @@ private: }; std::vector<ColumnWidth> getColWidths() const; // std::vector<ColumnWidth> getColWidths(int mainWinWidth) const; //evaluate stretched columns - int getColWidthsSum(int mainWinWidth) const; + int getColWidthsSum(int mainWinWidth) const; std::vector<int> getColStretchedWidths(int clientWidth) const; //final width = (normalized) (stretchedWidth + offset) Opt<int> getColWidth(size_t col) const @@ -318,7 +318,7 @@ private: void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event! - bool isSelected(size_t row) const { return selection.isSelected(row); } + bool isSelected(size_t row) const { return selection_.isSelected(row); } struct ColAction { @@ -345,21 +345,21 @@ private: ColLabelWin* colLabelWin_; MainWin* mainWin_; - ScrollBarStatus showScrollbarX = SB_SHOW_AUTOMATIC; - ScrollBarStatus showScrollbarY = SB_SHOW_AUTOMATIC; + ScrollBarStatus showScrollbarX_ = SB_SHOW_AUTOMATIC; + ScrollBarStatus showScrollbarY_ = SB_SHOW_AUTOMATIC; int colLabelHeight_ = 0; - bool drawRowLabel = true; + bool drawRowLabel_ = true; std::shared_ptr<GridData> dataView_; - Selection selection; - bool allowColumnMove = true; - bool allowColumnResize = true; + Selection selection_; + 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<ColumnAttribute> oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! - size_t rowCountOld = 0; //at the time of last Grid::Refresh() + size_t rowCountOld_ = 0; //at the time of last Grid::Refresh() }; } diff --git a/wx+/http.cpp b/wx+/http.cpp index ce3de482..3428546e 100644 --- a/wx+/http.cpp +++ b/wx+/http.cpp @@ -31,186 +31,259 @@ namespace #endif #endif +struct UrlRedirectError +{ + UrlRedirectError(const std::wstring& url) : newUrl(url) {} + std::wstring newUrl; +}; +} -std::string sendHttpRequestImpl(const std::wstring& url, //throw SysError - const std::wstring& userAgent, - const std::string* postParams, //issue POST if bound, GET otherwise - int level = 0) + +class HttpInputStream::Impl { - assert(!startsWith(makeUpperCopy(url), L"HTTPS:")); //not supported by wxHTTP! - const std::wstring urlFmt = startsWith(makeUpperCopy(url), L"HTTP://") ? afterFirst(url, L"://", IF_MISSING_RETURN_NONE) : url; - const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL); - const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE); +public: + Impl(const std::wstring& url, const std::wstring& userAgent, //throw SysError, UrlRedirectError + const std::string* postParams) //issue POST if bound, GET otherwise + { + ZEN_ON_SCOPE_FAIL( cleanup(); /*destructor call would lead to member double clean-up!!!*/ ); + + assert(!startsWith(makeUpperCopy(url), L"HTTPS:")); //not supported by wxHTTP! + const std::wstring urlFmt = startsWith(makeUpperCopy(url), L"HTTP://") || + startsWith(makeUpperCopy(url), L"HTTPS://") ? afterFirst(url, L"://", IF_MISSING_RETURN_NONE) : url; + const std::wstring server = beforeFirst(urlFmt, L'/', IF_MISSING_RETURN_ALL); + const std::wstring page = L'/' + afterFirst(urlFmt, L'/', IF_MISSING_RETURN_NONE); #ifdef ZEN_WIN - //WinInet: 1. uses IE proxy settings! :) 2. follows HTTP redirects by default 3. swallows HTTPS if needed - HINTERNET hInternet = ::InternetOpen(userAgent.c_str(), //_In_ LPCTSTR lpszAgent, - INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, - nullptr, //_In_ LPCTSTR lpszProxyName, - nullptr, //_In_ LPCTSTR lpszProxyBypass, - 0); //_In_ DWORD dwFlags - if (!hInternet) - THROW_LAST_SYS_ERROR(L"InternetOpen"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hInternet)); + //WinInet: 1. uses IE proxy settings! :) 2. follows HTTP redirects by default 3. swallows HTTPS if needed + hInternet_ = ::InternetOpen(userAgent.c_str(), //_In_ LPCTSTR lpszAgent, + INTERNET_OPEN_TYPE_PRECONFIG, //_In_ DWORD dwAccessType, + nullptr, //_In_ LPCTSTR lpszProxyName, + nullptr, //_In_ LPCTSTR lpszProxyBypass, + 0); //_In_ DWORD dwFlags + if (!hInternet_) + THROW_LAST_SYS_ERROR(L"InternetOpen"); + + hSession_ = ::InternetConnect(hInternet_, //_In_ HINTERNET hInternet, + server.c_str(), //_In_ LPCTSTR lpszServerName, + INTERNET_DEFAULT_HTTP_PORT, //_In_ INTERNET_PORT nServerPort, + nullptr, //_In_ LPCTSTR lpszUsername, + nullptr, //_In_ LPCTSTR lpszPassword, + INTERNET_SERVICE_HTTP, //_In_ DWORD dwService, + 0, //_In_ DWORD dwFlags, + 0); //_In_ DWORD_PTR dwContext + if (!hSession_) + THROW_LAST_SYS_ERROR(L"InternetConnect"); + + const wchar_t* acceptTypes[] = { L"*/*", nullptr }; + DWORD requestFlags = + //INTERNET_FLAG_KEEP_CONNECTION | + // the combination 1. INTERNET_FLAG_KEEP_CONNECTION (= adds "Connection: Keep-Alive" but NOT "Keep-Alive: timeout" to the header) + // 2. *no* "Keep-Alive: timeout" header entry 3. call from within VM and 4. *no* Fiddler running 5. HTTP POST + // leads to Godaddy blocking the IP: http://www.freefilesync.org/forum/viewtopic.php?t=3855 + // => it seems a broken keep alive header is the trigger: But why is it then working outside the VM or when Fiddler is running??? Why not a problem for HTTP GET? + // note: HTTP/1.1 has keep-alive semantics by default, so this flag is probably useless anyway + INTERNET_FLAG_NO_UI; + + if (postParams) + { + requestFlags |= INTERNET_FLAG_NO_AUTO_REDIRECT; //POST would be re-issued as GET during auto-redirect => handle ourselves! + } + else //HTTP GET + { + requestFlags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP; + requestFlags |= INTERNET_FLAG_RELOAD; //not relevant for POST (= never cached) + } - HINTERNET hSession = ::InternetConnect(hInternet, //_In_ HINTERNET hInternet, - server.c_str(), //_In_ LPCTSTR lpszServerName, - INTERNET_DEFAULT_HTTP_PORT, //_In_ INTERNET_PORT nServerPort, - nullptr, //_In_ LPCTSTR lpszUsername, - nullptr, //_In_ LPCTSTR lpszPassword, - INTERNET_SERVICE_HTTP, //_In_ DWORD dwService, - 0, //_In_ DWORD dwFlags, - 0); //_In_ DWORD_PTR dwContext - if (!hSession) - THROW_LAST_SYS_ERROR(L"InternetConnect"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hSession)); - - const wchar_t* acceptTypes[] = { L"*/*", nullptr }; - DWORD requestFlags = - //INTERNET_FLAG_KEEP_CONNECTION | - // the combination 1. INTERNET_FLAG_KEEP_CONNECTION (= adds "Connection: Keep-Alive" but NOT "Keep-Alive: timeout" to the header) - // 2. *no* "Keep-Alive: timeout" header entry 3. call from within VM and 4. *no* Fiddler running 5. HTTP POST - // leads to Godaddy blocking the IP: http://www.freefilesync.org/forum/viewtopic.php?t=3855 - // => it seems a broken keep alive header is the trigger: But why is it then working outside the VM or when Fiddler is running??? Why not a problem for HTTP GET? - // note: HTTP/1.1 has keep-alive semantics by default, so this flag is probably useless anyway - INTERNET_FLAG_NO_UI; - - if (postParams) - { - requestFlags |= INTERNET_FLAG_NO_AUTO_REDIRECT; //POST would be re-issued as GET during auto-redirect => handle ourselves! - } - else //HTTP GET - { - requestFlags |= INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP; - requestFlags |= INTERNET_FLAG_RELOAD; //not relevant for POST (= never cached) - } - - HINTERNET hRequest = ::HttpOpenRequest(hSession, //_In_ HINTERNET hConnect, - postParams ? L"POST" : L"GET", //_In_ LPCTSTR lpszVerb, - page.c_str(), //_In_ LPCTSTR lpszObjectName, - nullptr, //_In_ LPCTSTR lpszVersion, - nullptr, //_In_ LPCTSTR lpszReferer, - acceptTypes, //_In_ LPCTSTR *lplpszAcceptTypes, - requestFlags, //_In_ DWORD dwFlags, - 0); //_In_ DWORD_PTR dwContext - if (!hRequest) - THROW_LAST_SYS_ERROR(L"HttpOpenRequest"); - ZEN_ON_SCOPE_EXIT(::InternetCloseHandle(hRequest)); + hRequest_ = ::HttpOpenRequest(hSession_, //_In_ HINTERNET hConnect, + postParams ? L"POST" : L"GET", //_In_ LPCTSTR lpszVerb, + page.c_str(), //_In_ LPCTSTR lpszObjectName, + nullptr, //_In_ LPCTSTR lpszVersion, + nullptr, //_In_ LPCTSTR lpszReferer, + acceptTypes, //_In_ LPCTSTR *lplpszAcceptTypes, + requestFlags, //_In_ DWORD dwFlags, + 0); //_In_ DWORD_PTR dwContext + if (!hRequest_) + THROW_LAST_SYS_ERROR(L"HttpOpenRequest"); - const std::wstring headers = postParams ? L"Content-type: application/x-www-form-urlencoded" : L""; + const std::wstring headers = postParams ? L"Content-type: application/x-www-form-urlencoded" : L""; - assert(std::all_of(headers.begin(), headers.end(), [](wchar_t c){ return makeUnsigned(c) < 128; })); - //HttpSendRequest has finicky behavior for non-ASCII headers: https://msdn.microsoft.com/en-us/library/windows/desktop/aa384247 + assert(std::all_of(headers.begin(), headers.end(), [](wchar_t c) { return makeUnsigned(c) < 128; })); + //HttpSendRequest has finicky behavior for non-ASCII headers: https://msdn.microsoft.com/en-us/library/windows/desktop/aa384247 - std::string postParamsBuf = postParams ? *postParams : ""; + std::string postParamsBuf = postParams ? *postParams : ""; - if (!::HttpSendRequest(hRequest, //_In_ HINTERNET hRequest, - headers.c_str(), //_In_ LPCTSTR lpszHeaders, - static_cast<DWORD>(headers.size()), //_In_ DWORD dwHeadersLength, - postParamsBuf.empty() ? nullptr : &postParamsBuf[0], //_In_ LPVOID lpOptional, - static_cast<DWORD>(postParamsBuf.size()))) //_In_ DWORD dwOptionalLength - THROW_LAST_SYS_ERROR(L"HttpSendRequest"); + if (!::HttpSendRequest(hRequest_, //_In_ HINTERNET hRequest, + headers.c_str(), //_In_ LPCTSTR lpszHeaders, + static_cast<DWORD>(headers.size()), //_In_ DWORD dwHeadersLength, + postParamsBuf.empty() ? nullptr : &postParamsBuf[0], //_In_ LPVOID lpOptional, + static_cast<DWORD>(postParamsBuf.size()))) //_In_ DWORD dwOptionalLength + THROW_LAST_SYS_ERROR(L"HttpSendRequest"); - DWORD sc = 0; - { - DWORD bufLen = sizeof(sc); - if (!::HttpQueryInfo(hRequest, //_In_ HINTERNET hRequest, - HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, - &sc, //_Inout_ LPVOID lpvBuffer, - &bufLen, //_Inout_ LPDWORD lpdwBufferLength, - nullptr)) //_Inout_ LPDWORD lpdwIndex - THROW_LAST_SYS_ERROR(L"HttpQueryInfo: HTTP_QUERY_STATUS_CODE"); - } + DWORD sc = 0; + { + DWORD bufLen = sizeof(sc); + if (!::HttpQueryInfo(hRequest_, //_In_ HINTERNET hRequest, + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, //_In_ DWORD dwInfoLevel, + &sc, //_Inout_ LPVOID lpvBuffer, + &bufLen, //_Inout_ LPDWORD lpdwBufferLength, + nullptr)) //_Inout_ LPDWORD lpdwIndex + THROW_LAST_SYS_ERROR(L"HttpQueryInfo: HTTP_QUERY_STATUS_CODE"); + } - //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! - { - if (level < 5) //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." + //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection + if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! { DWORD bufLen = 10000; std::wstring location(bufLen, L'\0'); - if (!::HttpQueryInfo(hRequest, HTTP_QUERY_LOCATION, &*location.begin(), &bufLen, nullptr)) + if (!::HttpQueryInfo(hRequest_, HTTP_QUERY_LOCATION, &*location.begin(), &bufLen, nullptr)) THROW_LAST_SYS_ERROR(L"HttpQueryInfo: HTTP_QUERY_LOCATION"); if (bufLen >= location.size()) //HttpQueryInfo expected to write terminating zero throw SysError(L"HttpQueryInfo: HTTP_QUERY_LOCATION, buffer overflow"); location.resize(bufLen); - if (!location.empty()) - return sendHttpRequestImpl(location, userAgent, postParams, level + 1); + if (location.empty()) + throw SysError(L"Unresolvable redirect. Empty target Location."); + + throw UrlRedirectError(location); + } + + if (sc != HTTP_STATUS_OK) //200 + throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + //e.g. 404 - HTTP_STATUS_NOT_FOUND + +#else + assert(std::this_thread::get_id() == mainThreadId); + assert(wxApp::IsMainLoopRunning()); + + webAccess_.SetHeader(L"User-Agent", userAgent); + webAccess_.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? + + if (!webAccess_.Connect(server)) //will *not* fail for non-reachable url here! + throw SysError(L"wxHTTP::Connect"); + + if (postParams) + if (!webAccess_.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams))) + throw SysError(L"wxHTTP::SetPostText"); + + httpStream_.reset(webAccess_.GetInputStream(page)); //pass ownership + const int sc = webAccess_.GetResponse(); + + //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection + if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! + { + const std::wstring newUrl(webAccess_.GetHeader(L"Location")); + if (newUrl.empty()) + throw SysError(L"Unresolvable redirect. Empty target Location."); + + throw UrlRedirectError(newUrl); } - throw SysError(L"Unresolvable redirect."); + + if (sc != 200) //HTTP_STATUS_OK + throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + + if (!httpStream_ || webAccess_.GetError() != wxPROTO_NOERR) + throw SysError(L"wxHTTP::GetError (" + numberTo<std::wstring>(webAccess_.GetError()) + L")"); +#endif } - if (sc != HTTP_STATUS_OK) //200 - throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); - //e.g. 404 - HTTP_STATUS_NOT_FOUND + ~Impl() { cleanup(); } - std::string buffer; - const DWORD blockSize = 64 * 1024; - //internet says "HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... - for (;;) + size_t tryRead(void* buffer, size_t bytesToRead) //throw SysError; may return short, only 0 means EOF! { - buffer.resize(buffer.size() + blockSize); + if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! + throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); +#ifdef ZEN_WIN + //"HttpQueryInfo() + HTTP_QUERY_CONTENT_LENGTH" not supported by all http servers... DWORD bytesRead = 0; - if (!::InternetReadFile(hRequest, //_In_ HINTERNET hFile, - &*(buffer.begin() + buffer.size() - blockSize), //_Out_ LPVOID lpBuffer, - blockSize, //_In_ DWORD dwNumberOfBytesToRead, + if (!::InternetReadFile(hRequest_, //_In_ HINTERNET hFile, + buffer, //_Out_ LPVOID lpBuffer, + static_cast<DWORD>(bytesToRead), //_In_ DWORD dwNumberOfBytesToRead, &bytesRead)) //_Out_ LPDWORD lpdwNumberOfBytesRead THROW_LAST_SYS_ERROR(L"InternetReadFile"); +#else + httpStream_->Read(buffer, bytesToRead); - if (bytesRead > blockSize) //better safe than sorry + const wxStreamError ec = httpStream_->GetLastError(); + if (ec != wxSTREAM_NO_ERROR && ec != wxSTREAM_EOF) + throw SysError(L"wxInputStream::GetLastError (" + numberTo<std::wstring>(httpStream_->GetLastError()) + L")"); + + const size_t bytesRead = httpStream_->LastRead(); + //"if there are not enough bytes in the stream right now, LastRead() value will be + // less than size but greater than 0. If it is 0, it means that EOF has been reached." + assert(bytesRead > 0 || ec == wxSTREAM_EOF); +#endif + if (bytesRead > bytesToRead) //better safe than sorry throw SysError(L"InternetReadFile: buffer overflow."); - if (bytesRead < blockSize) - buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics + return bytesRead; //"zero indicates end of file" + } - if (bytesRead == 0) - return buffer; +private: + Impl (const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + + void cleanup() + { +#ifdef ZEN_WIN + if (hRequest_ ) ::InternetCloseHandle(hRequest_); + if (hSession_ ) ::InternetCloseHandle(hSession_); + if (hInternet_) ::InternetCloseHandle(hInternet_); +#endif } +#ifdef ZEN_WIN + HINTERNET hInternet_ = nullptr; + HINTERNET hSession_ = nullptr; + HINTERNET hRequest_ = nullptr; #else - assert(std::this_thread::get_id() == mainThreadId); - assert(wxApp::IsMainLoopRunning()); + wxHTTP webAccess_; + std::unique_ptr<wxInputStream> httpStream_; //must be deleted BEFORE webAccess is closed +#endif +}; - wxHTTP webAccess; - webAccess.SetHeader(L"User-Agent", userAgent); - webAccess.SetTimeout(10 /*[s]*/); //default: 10 minutes: WTF are these wxWidgets people thinking??? - if (!webAccess.Connect(server)) //will *not* fail for non-reachable url here! - throw SysError(L"wxHTTP::Connect"); +HttpInputStream::HttpInputStream(std::unique_ptr<Impl>&& pimpl) : pimpl_(std::move(pimpl)) {} - if (postParams) - if (!webAccess.SetPostText(L"application/x-www-form-urlencoded", utfCvrtTo<wxString>(*postParams))) - throw SysError(L"wxHTTP::SetPostText"); +HttpInputStream::~HttpInputStream() {} - std::unique_ptr<wxInputStream> httpStream(webAccess.GetInputStream(page)); //must be deleted BEFORE webAccess is closed - const int sc = webAccess.GetResponse(); +size_t HttpInputStream::tryRead(void* buffer, size_t bytesToRead) { return pimpl_->tryRead(buffer, bytesToRead); } //throw SysError + + +std::string HttpInputStream::readAll() //throw SysError +{ + std::string buffer; + const size_t blockSize = getBlockSize(); - //http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_Redirection - if (sc / 100 == 3) //e.g. 301, 302, 303, 307... we're not too greedy since we check location, too! + for (;;) { - if (level < 5) //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." - { - const std::wstring newUrl(webAccess.GetHeader(L"Location")); - if (!newUrl.empty()) - return sendHttpRequestImpl(newUrl, userAgent, postParams, level + 1); - } - throw SysError(L"Unresolvable redirect."); - } + buffer.resize(buffer.size() + blockSize); - if (sc != 200) //HTTP_STATUS_OK - throw SysError(replaceCpy<std::wstring>(L"HTTP status code %x.", L"%x", numberTo<std::wstring>(sc))); + const size_t bytesRead = pimpl_->tryRead(&*(buffer.end() - blockSize), blockSize); //throw SysError - if (!httpStream || webAccess.GetError() != wxPROTO_NOERR) - throw SysError(L"wxHTTP::GetError"); + if (bytesRead < blockSize) + buffer.resize(buffer.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics - std::string buffer; - int newValue = 0; - while ((newValue = httpStream->GetC()) != wxEOF) - buffer.push_back(static_cast<char>(newValue)); - return buffer; -#endif + if (bytesRead == 0) + return buffer; + } +} + + +namespace +{ +std::unique_ptr<HttpInputStream::Impl> sendHttpRequestImpl(const std::wstring& url, const std::wstring& userAgent, //throw SysError + const std::string* postParams) //issue POST if bound, GET otherwise +{ + std::wstring urlRed = url; + //"A user agent should not automatically redirect a request more than five times, since such redirections usually indicate an infinite loop." + for (int redirects = 0; redirects < 6; ++redirects) + try + { + return std::make_unique<HttpInputStream::Impl>(urlRed, userAgent, postParams); //throw SysError, UrlRedirectError + } + catch (const UrlRedirectError& e) { urlRed = e.newUrl; } + throw SysError(L"Too many redirects."); } @@ -228,31 +301,72 @@ std::string urlencode(const std::string& str) out += c; else { - const char hexDigits[] = "0123456789ABCDEF"; + const std::pair<char, char> hex = hexify(c); + out += '%'; - out += hexDigits[static_cast<unsigned char>(c) / 16]; - out += hexDigits[static_cast<unsigned char>(c) % 16]; + out += hex.first; + out += hex.second; + } + return out; +} + + +std::string urldecode(const std::string& str) +{ + std::string out; + for (size_t i = 0; i < str.size(); ++i) + { + const char c = str[i]; + if (c == '+') + out += ' '; + else if (c == '%' && str.size() - i >= 3 && + isHexDigit(str[i + 1]) && + isHexDigit(str[i + 2])) + { + out += unhexify(str[i + 1], str[i + 2]); + i += 2; } + else + out += c; + } return out; } } -std::string zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError +std::string zen::xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs) { - //convert post parameters into "application/x-www-form-urlencoded" - std::string flatParams; - for (const auto& pair : postParams) - flatParams += urlencode(pair.first) + '=' + urlencode(pair.second) + '&'; + std::string output; + for (const auto& pair : paramPairs) + output += urlencode(pair.first) + '=' + urlencode(pair.second) + '&'; //encode both key and value: https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 - if (!flatParams.empty()) - flatParams.pop_back(); + if (!output.empty()) + output.pop_back(); + return output; +} - return sendHttpRequestImpl(url, userAgent, &flatParams); //throw SysError + +std::vector<std::pair<std::string, std::string>> zen::xWwwFormUrlDecode(const std::string& str) +{ + std::vector<std::pair<std::string, std::string>> output; + + for (const std::string& nvPair : split(str, '&')) + if (!nvPair.empty()) + output.emplace_back(urldecode(beforeFirst(nvPair, '=', IF_MISSING_RETURN_ALL)), + urldecode(afterFirst (nvPair, '=', IF_MISSING_RETURN_NONE))); + return output; +} + + +HttpInputStream zen::sendHttpPost(const std::wstring& url, const std::wstring& userAgent, + const std::vector<std::pair<std::string, std::string>>& postParams) //throw SysError +{ + const std::string encodedParams = xWwwFormUrlEncode(postParams); + return sendHttpRequestImpl(url, userAgent, &encodedParams); //throw SysError } -std::string zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent) //throw SysError +HttpInputStream zen::sendHttpGet(const std::wstring& url, const std::wstring& userAgent) //throw SysError { return sendHttpRequestImpl(url, userAgent, nullptr); //throw SysError } @@ -17,9 +17,32 @@ namespace zen Windows: WinInet-based => may be called from worker thread Linux: wxWidgets-based => don't call from worker thread */ -std::string sendHttpPost(const std::wstring& url, const std::wstring& userAgent, const std::vector<std::pair<std::string, std::string>>& postParams); //throw SysError -std::string sendHttpGet (const std::wstring& url, const std::wstring& userAgent); //throw SysError +class HttpInputStream +{ +public: + std::string readAll(); //throw SysError + + //support zen/serialize.h Unbuffered Input Stream Concept + size_t tryRead(void* buffer, size_t bytesToRead); //throw SysError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0! + size_t getBlockSize() const { return 64 * 1024; } + + class Impl; + HttpInputStream(std::unique_ptr<Impl>&& pimpl); + HttpInputStream(HttpInputStream&&) = default; + ~HttpInputStream(); + +private: + std::unique_ptr<Impl> pimpl_; +}; + + +HttpInputStream sendHttpGet (const std::wstring& url, const std::wstring& userAgent); //throw SysError +HttpInputStream sendHttpPost(const std::wstring& url, const std::wstring& userAgent, + const std::vector<std::pair<std::string, std::string>>& postParams); //throw SysError bool internetIsAlive(); //noexcept + +std::string xWwwFormUrlEncode(const std::vector<std::pair<std::string, std::string>>& paramPairs); +std::vector<std::pair<std::string, std::string>> xWwwFormUrlDecode(const std::string& str); } #endif //HTTP_h_879083425703425702 diff --git a/wx+/image_tools.cpp b/wx+/image_tools.cpp index a6ecc3d3..90945a44 100644 --- a/wx+/image_tools.cpp +++ b/wx+/image_tools.cpp @@ -160,7 +160,7 @@ wxImage zen::createImageFromText(const wxString& text, const wxFont& font, const //for some reason wxDC::DrawText messes up "weak" bidi characters even when wxLayout_RightToLeft is set! (--> arrows in hebrew/arabic) //=> use mark characters instead: - const wchar_t rtlMark = L'\u200F'; + const wchar_t rtlMark = L'\u200F'; //UTF-8: E2 80 8F if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) textFmt = rtlMark + textFmt + rtlMark; diff --git a/wx+/popup_dlg.cpp b/wx+/popup_dlg.cpp index 918f44a5..f96884d9 100644 --- a/wx+/popup_dlg.cpp +++ b/wx+/popup_dlg.cpp @@ -86,27 +86,31 @@ public: #ifdef ZEN_WIN new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this" #endif - wxString titleTmp = cfg.title; + wxBitmap iconTmp; + wxString titleTmp; switch (type) { case DialogInfoType::INFO: - //"information" is meaningless as caption text! + //"Information" is meaningless as caption text! //confirmation doesn't use info icon - //m_bitmapMsgType->Hide(); - //m_bitmapMsgType->SetSize(30, -1); - //m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_info")); + //iconTmp = getResourceImage(L"msg_info"); break; case DialogInfoType::WARNING: - if (titleTmp.empty()) titleTmp = _("Warning"); - m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_warning")); + iconTmp = getResourceImage(L"msg_warning"); + titleTmp = _("Warning"); break; case DialogInfoType::ERROR2: - if (titleTmp.empty()) titleTmp = _("Error"); - m_bitmapMsgType->SetBitmap(getResourceImage(L"msg_error")); + iconTmp = getResourceImage(L"msg_error"); + titleTmp = _("Error"); break; } if (cfg.icon.IsOk()) - m_bitmapMsgType->SetBitmap(cfg.icon); + iconTmp = cfg.icon; + + if (!cfg.title.empty()) + titleTmp = cfg.title; + //----------------------------------------------- + m_bitmapMsgType->SetBitmap(iconTmp); if (titleTmp.empty()) SetTitle(wxTheApp->GetAppDisplayName()); @@ -140,7 +144,7 @@ public: if (!cfg.textDetail.empty()) { - const wxString& text = L"\n" + cfg.textDetail + L"\n"; //add empty top/bottom lines *instead* of using border space! + const wxString& text = L"\n" + trimCpy(cfg.textDetail) + L"\n"; //add empty top/bottom lines *instead* of using border space! setBestInitialSize(*m_textCtrlTextDetail, text, wxSize(maxWidth, maxHeight)); m_textCtrlTextDetail->ChangeValue(text); } @@ -185,14 +189,14 @@ private: void OnButtonAffirmative(wxCommandEvent& event) override { if (checkBoxValue_) - * checkBoxValue_ = m_checkBoxCustom->GetValue(); + *checkBoxValue_ = m_checkBoxCustom->GetValue(); EndModal(static_cast<int>(ConfirmationButton3::DO_IT)); } void OnButtonNegative(wxCommandEvent& event) override { if (checkBoxValue_) - * checkBoxValue_ = m_checkBoxCustom->GetValue(); + *checkBoxValue_ = m_checkBoxCustom->GetValue(); EndModal(static_cast<int>(ConfirmationButton3::DONT_DO_IT)); } @@ -244,8 +248,8 @@ class zen::ConfirmationDialog3 : public StandardPopupDialog { public: ConfirmationDialog3(wxWindow* parent, DialogInfoType type, const PopupDialogCfg3& cfg, const wxString& labelDoIt, const wxString& labelDontDoIt) : - StandardPopupDialog(parent, type, cfg.pdCfg), - buttonToDisableWhenChecked(cfg.buttonToDisableWhenChecked) + StandardPopupDialog(parent, type, cfg.pdCfg_), + buttonToDisableWhenChecked_(cfg.buttonToDisableWhenChecked_) { assert(contains(labelDoIt, L"&")); assert(contains(labelDontDoIt, L"&")); @@ -269,7 +273,7 @@ private: void updateGui() { - switch (buttonToDisableWhenChecked) + switch (buttonToDisableWhenChecked_) { case ConfirmationButton3::DO_IT: m_buttonAffirmative->Enable(!m_checkBoxCustom->GetValue()); @@ -282,7 +286,7 @@ private: } } - const ConfirmationButton3 buttonToDisableWhenChecked; + const ConfirmationButton3 buttonToDisableWhenChecked_; }; //######################################################################################## diff --git a/wx+/popup_dlg.h b/wx+/popup_dlg.h index 892c7a83..67f73a11 100644 --- a/wx+/popup_dlg.h +++ b/wx+/popup_dlg.h @@ -39,7 +39,7 @@ enum class ConfirmationButton3 enum class ConfirmationButton { - DO_IT = static_cast<int>(ConfirmationButton3::DO_IT), //[!] + DO_IT = static_cast<int>(ConfirmationButton3::DO_IT ), //[!] CANCEL = static_cast<int>(ConfirmationButton3::CANCEL), //Clang requires a "static_cast" }; @@ -73,23 +73,24 @@ private: struct PopupDialogCfg3 { - PopupDialogCfg3& setTitle (const wxString& label) { pdCfg.setTitle (label); return *this; } - PopupDialogCfg3& setMainInstructions (const wxString& label) { pdCfg.setMainInstructions (label); return *this; } //set at least one of these! - PopupDialogCfg3& setDetailInstructions(const wxString& label) { pdCfg.setDetailInstructions(label); return *this; } // - PopupDialogCfg3& setCheckBox(bool& value, const wxString& label) { pdCfg.setCheckBox(value, label); return *this; } + PopupDialogCfg3& setIcon (const wxBitmap& bmp ) { pdCfg_.setIcon (bmp); return *this; } + PopupDialogCfg3& setTitle (const wxString& label) { pdCfg_.setTitle (label); return *this; } + PopupDialogCfg3& setMainInstructions (const wxString& label) { pdCfg_.setMainInstructions (label); return *this; } //set at least one of these! + PopupDialogCfg3& setDetailInstructions(const wxString& label) { pdCfg_.setDetailInstructions(label); return *this; } // + PopupDialogCfg3& setCheckBox(bool& value, const wxString& label) { pdCfg_.setCheckBox(value, label); return *this; } PopupDialogCfg3& setCheckBox(bool& value, const wxString& label, ConfirmationButton3 disableWhenChecked) { assert(disableWhenChecked != ConfirmationButton3::CANCEL); setCheckBox(value, label); - buttonToDisableWhenChecked = disableWhenChecked; + buttonToDisableWhenChecked_ = disableWhenChecked; return *this; } private: friend class ConfirmationDialog3; - PopupDialogCfg pdCfg; - ConfirmationButton3 buttonToDisableWhenChecked = ConfirmationButton3::CANCEL; + PopupDialogCfg pdCfg_; + ConfirmationButton3 buttonToDisableWhenChecked_ = ConfirmationButton3::CANCEL; }; } diff --git a/wx+/popup_dlg_generated.cpp b/wx+/popup_dlg_generated.cpp index b726aa9a..6df18dce 100644 --- a/wx+/popup_dlg_generated.cpp +++ b/wx+/popup_dlg_generated.cpp @@ -34,7 +34,10 @@ PopupDialogGenerated::PopupDialogGenerated( wxWindow* parent, wxWindowID id, con m_staticTextMain = new wxStaticText( m_panel33, wxID_ANY, _("dummy"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticTextMain->Wrap( -1 ); - bSizer16->Add( m_staticTextMain, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 ); + bSizer16->Add( m_staticTextMain, 0, wxRIGHT, 10 ); + + + bSizer16->Add( 0, 5, 0, 0, 5 ); m_textCtrlTextDetail = new wxTextCtrl( m_panel33, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE|wxTE_READONLY|wxNO_BORDER ); bSizer16->Add( m_textCtrlTextDetail, 1, wxEXPAND, 5 ); diff --git a/wx+/std_button_layout.h b/wx+/std_button_layout.h index 59fda260..3e335882 100644 --- a/wx+/std_button_layout.h +++ b/wx+/std_button_layout.h @@ -16,14 +16,13 @@ namespace zen { struct StdButtons { - StdButtons() : btnYes(nullptr), btnNo(nullptr), btnCancel(nullptr) {} StdButtons& setAffirmative (wxButton* btn) { btnYes = btn; return *this; } StdButtons& setNegative (wxButton* btn) { btnNo = btn; return *this; } StdButtons& setCancel (wxButton* btn) { btnCancel = btn; return *this; } - wxButton* btnYes; - wxButton* btnNo; - wxButton* btnCancel; + wxButton* btnYes = nullptr; + wxButton* btnNo = nullptr; + wxButton* btnCancel = nullptr; }; void setStandardButtonLayout(wxBoxSizer& sizer, const StdButtons& buttons = StdButtons()); diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp index c2c562ce..8dc79d73 100644 --- a/wx+/tooltip.cpp +++ b/wx+/tooltip.cpp @@ -16,15 +16,15 @@ using namespace zen; -class Tooltip::TooltipDialogGenerated : public wxDialog +class Tooltip::TooltipDlgGenerated : public wxDialog { public: - TooltipDialogGenerated(wxWindow* parent, - wxWindowID id = wxID_ANY, - const wxString& title = {}, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = 0) : wxDialog(parent, id, title, pos, size, style) + TooltipDlgGenerated(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxString& title = {}, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0) : wxDialog(parent, id, title, pos, size, style) { //Suse Linux/X11: needs parent window, else there are z-order issues @@ -33,11 +33,11 @@ public: this->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); // wxBoxSizer* bSizer158 = new wxBoxSizer(wxHORIZONTAL); - m_bitmapLeft = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0); - bSizer158->Add(m_bitmapLeft, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); + bitmapLeft_ = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0); + bSizer158->Add(bitmapLeft_, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5); - m_staticTextMain = new wxStaticText(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, 0); - bSizer158->Add(m_staticTextMain, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); + staticTextMain_ = new wxStaticText(this, wxID_ANY, wxString(), wxDefaultPosition, wxDefaultSize, 0); + bSizer158->Add(staticTextMain_, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5); this->SetSizer(bSizer158); this->Layout(); @@ -48,57 +48,57 @@ public: #endif } - wxStaticText* m_staticTextMain; - wxStaticBitmap* m_bitmapLeft; + wxStaticText* staticTextMain_; + wxStaticBitmap* bitmapLeft_; }; void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp) { - if (!tipWindow) - tipWindow = new TooltipDialogGenerated(&parent_); //ownership passed to parent + if (!tipWindow_) + tipWindow_ = new TooltipDlgGenerated(&parent_); //ownership passed to parent const wxBitmap& newBmp = bmp ? *bmp : wxNullBitmap; - if (!isEqual(tipWindow->m_bitmapLeft->GetBitmap(), newBmp)) + if (!isEqual(tipWindow_->bitmapLeft_->GetBitmap(), newBmp)) { - tipWindow->m_bitmapLeft->SetBitmap(newBmp); - tipWindow->Refresh(); //needed if bitmap size changed! + tipWindow_->bitmapLeft_->SetBitmap(newBmp); + tipWindow_->Refresh(); //needed if bitmap size changed! } - if (text != tipWindow->m_staticTextMain->GetLabel()) + if (text != tipWindow_->staticTextMain_->GetLabel()) { - tipWindow->m_staticTextMain->SetLabel(text); - tipWindow->m_staticTextMain->Wrap(600); + tipWindow_->staticTextMain_->SetLabel(text); + tipWindow_->staticTextMain_->Wrap(600); } - tipWindow->GetSizer()->SetSizeHints(tipWindow); //~=Fit() + SetMinSize() + tipWindow_->GetSizer()->SetSizeHints(tipWindow_); //~=Fit() + SetMinSize() //Linux: Fit() seems to be broken => this needs to be called EVERY time inside show, not only if text or bmp change const wxPoint newPos = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? - mousePos - wxPoint(30 + tipWindow->GetSize().GetWidth(), 0) : + mousePos - wxPoint(30 + tipWindow_->GetSize().GetWidth(), 0) : mousePos + wxPoint(30, 0); - if (newPos != tipWindow->GetScreenPosition()) - tipWindow->SetSize(newPos.x, newPos.y, wxDefaultCoord, wxDefaultCoord); + if (newPos != tipWindow_->GetScreenPosition()) + tipWindow_->SetSize(newPos.x, newPos.y, wxDefaultCoord, wxDefaultCoord); //attention!!! possible endless loop: mouse pointer must NOT be within tipWindow! //else it will trigger a wxEVT_LEAVE_WINDOW on middle grid which will hide the window, causing the window to be shown again via this method, etc. - if (!tipWindow->IsShown()) - tipWindow->Show(); + if (!tipWindow_->IsShown()) + tipWindow_->Show(); } void Tooltip::hide() { - if (tipWindow) + if (tipWindow_) { #ifdef ZEN_LINUX //on wxGTK the tooltip is sometimes not shown again after it was hidden: e.g. drag-selection on middle grid - tipWindow->Destroy(); //apply brute force: - tipWindow = nullptr; // + tipWindow_->Destroy(); //apply brute force: + tipWindow_ = nullptr; // #else - tipWindow->Hide(); + tipWindow_->Hide(); #endif } } diff --git a/wx+/tooltip.h b/wx+/tooltip.h index 23c7adb1..f2a7043e 100644 --- a/wx+/tooltip.h +++ b/wx+/tooltip.h @@ -23,8 +23,8 @@ public: void hide(); private: - class TooltipDialogGenerated; - TooltipDialogGenerated* tipWindow = nullptr; + class TooltipDlgGenerated; + TooltipDlgGenerated* tipWindow_ = nullptr; wxWindow& parent_; }; } |