summaryrefslogtreecommitdiff
path: root/wx+
diff options
context:
space:
mode:
Diffstat (limited to 'wx+')
-rw-r--r--wx+/async_task.h39
-rw-r--r--wx+/file_drop.h7
-rw-r--r--wx+/grid.cpp356
-rw-r--r--wx+/grid.h28
-rw-r--r--wx+/http.cpp410
-rw-r--r--wx+/http.h27
-rw-r--r--wx+/image_tools.cpp2
-rw-r--r--wx+/popup_dlg.cpp38
-rw-r--r--wx+/popup_dlg.h17
-rw-r--r--wx+/popup_dlg_generated.cpp5
-rw-r--r--wx+/std_button_layout.h7
-rw-r--r--wx+/tooltip.cpp62
-rw-r--r--wx+/tooltip.h4
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)
diff --git a/wx+/grid.h b/wx+/grid.h
index b8b6711e..f08d6e41 100644
--- a/wx+/grid.h
+++ b/wx+/grid.h
@@ -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
}
diff --git a/wx+/http.h b/wx+/http.h
index febe8f24..cf385d5e 100644
--- a/wx+/http.h
+++ b/wx+/http.h
@@ -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_;
};
}
bgstack15