diff options
Diffstat (limited to 'wx+')
-rw-r--r-- | wx+/app_main.h | 2 | ||||
-rw-r--r-- | wx+/button.cpp | 2 | ||||
-rw-r--r-- | wx+/button.h | 2 | ||||
-rw-r--r-- | wx+/choice_enum.h | 2 | ||||
-rw-r--r-- | wx+/context_menu.h | 81 | ||||
-rw-r--r-- | wx+/file_drop.h | 39 | ||||
-rw-r--r-- | wx+/format_unit.cpp | 5 | ||||
-rw-r--r-- | wx+/format_unit.h | 2 | ||||
-rw-r--r-- | wx+/graph.cpp | 47 | ||||
-rw-r--r-- | wx+/graph.h | 5 | ||||
-rw-r--r-- | wx+/grid.cpp | 2052 | ||||
-rw-r--r-- | wx+/grid.h | 312 | ||||
-rw-r--r-- | wx+/image_tools.h | 2 | ||||
-rw-r--r-- | wx+/mouse_move_dlg.cpp | 2 | ||||
-rw-r--r-- | wx+/mouse_move_dlg.h | 2 | ||||
-rw-r--r-- | wx+/no_flicker.h | 2 | ||||
-rw-r--r-- | wx+/pch.h | 2 | ||||
-rw-r--r-- | wx+/rtl.h | 121 | ||||
-rw-r--r-- | wx+/serialize.h | 2 | ||||
-rw-r--r-- | wx+/shell_execute.h | 2 | ||||
-rw-r--r-- | wx+/string_conv.h | 2 | ||||
-rw-r--r-- | wx+/timespan.h | 2 | ||||
-rw-r--r-- | wx+/toggle_button.h | 2 | ||||
-rw-r--r-- | wx+/tooltip.cpp | 31 | ||||
-rw-r--r-- | wx+/tooltip.h | 5 |
25 files changed, 2590 insertions, 138 deletions
diff --git a/wx+/app_main.h b/wx+/app_main.h index ae36a8de..e39a8b43 100644 --- a/wx+/app_main.h +++ b/wx+/app_main.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef APPMAIN_H_INCLUDED diff --git a/wx+/button.cpp b/wx+/button.cpp index 80a9f8ba..9efdf071 100644 --- a/wx+/button.cpp +++ b/wx+/button.cpp @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "button.h" diff --git a/wx+/button.h b/wx+/button.h index 15ebc5a0..471a5b5a 100644 --- a/wx+/button.h +++ b/wx+/button.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef CUSTOMBUTTON_H_INCLUDED diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h index 4565bf81..e12c7a9b 100644 --- a/wx+/choice_enum.h +++ b/wx+/choice_enum.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef WX_CHOICE_ENUM_H_INCLUDED diff --git a/wx+/context_menu.h b/wx+/context_menu.h new file mode 100644 index 00000000..894da832 --- /dev/null +++ b/wx+/context_menu.h @@ -0,0 +1,81 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef CONTEXT_HEADER_18047302153418174632141234 +#define CONTEXT_HEADER_18047302153418174632141234 + +#include <vector> +#include <functional> +#include <wx/menu.h> +#include <wx/app.h> + +/* +A context menu supporting C++11 lambda callbacks! + +Usage: + ContextMenu menu; + menu.addItem(L"Some Label", [&]{ ...do something... }); -> capture by reference is fine, as long as captured variables have at least scope of ContextMenu::show()! + ... + menu.popup(wnd); +*/ + +namespace zen +{ +class ContextMenu : private wxEvtHandler +{ +public: + void addItem(const wxString& label, const std::function<void()>& command, const wxBitmap* bmp = NULL, bool enabled = true) + { + wxMenuItem* newItem = new wxMenuItem(&menu, wxID_ANY, label); + if (bmp) newItem->SetBitmap(*bmp); + if (!enabled) newItem->Enable(false); + menu.Append(newItem); //do NOT append item before setting bitmap! wxWidgets screws up for yet another crappy reason + menu.Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(command), /*pass ownership*/ this); + } + + void addCheckBox(const wxString& label, const std::function<void()>& command, bool checked, bool enabled = true) + { + wxMenuItem* newItem = menu.AppendCheckItem(wxID_ANY, label); + newItem->Check(checked); + if (!enabled) newItem->Enable(false); + menu.Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(command), /*pass ownership*/ this); + } + + void addRadio(const wxString& label, const std::function<void()>& command, bool checked, bool enabled = true) + { + wxMenuItem* newItem = menu.AppendRadioItem(wxID_ANY, label); + newItem->Check(checked); + if (!enabled) newItem->Enable(false); + menu.Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(ContextMenu::onSelection), new GenericCommand(command), /*pass ownership*/ this); + } + + void addSeparator() { menu.AppendSeparator(); } + + void popup(wxWindow& wnd) //show popup menu + process lambdas + { + wnd.PopupMenu(&menu); + wxTheApp->ProcessPendingEvents(); //make sure lambdas are evaluated before going out of scope; + //although all events seem to be processed within wxWindows::PopupMenu, we shouldn't trust wxWidgets in this regard + } + +private: + void onSelection(wxCommandEvent& event) + { + if (auto cmd = dynamic_cast<GenericCommand*>(event.m_callbackUserData)) + (cmd->fun_)(); + } + + struct GenericCommand : public wxObject + { + GenericCommand(const std::function<void()>& fun) : fun_(fun) {} + std::function<void()> fun_; + }; + + wxMenu menu; +}; +} + +#endif //CONTEXT_HEADER_18047302153418174632141234 diff --git a/wx+/file_drop.h b/wx+/file_drop.h index 1eaeede0..c2a14423 100644 --- a/wx+/file_drop.h +++ b/wx+/file_drop.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef FILE_DROP_H_INCLUDED @@ -14,15 +14,15 @@ namespace zen { //register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign) -//1. setup a window to emit FFS_DROP_FILE_EVENT +//1. setup a window to emit EVENT_DROP_FILE void setupFileDrop(wxWindow& wnd); //2. register events: -//wnd.Connect (FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(MyDlg::OnFilesDropped), NULL, this); -//wnd.Disconnect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(MyDlg::OnFilesDropped), NULL, this); +//wnd.Connect (EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), NULL, this); +//wnd.Disconnect(EVENT_DROP_FILE, FileDropEventHandler(MyDlg::OnFilesDropped), NULL, this); //3. do something: -//void MyDlg::OnFilesDropped(FFSFileDropEvent& event); +//void MyDlg::OnFilesDropped(FileDropEvent& event); @@ -49,21 +49,18 @@ wxEventType createNewEventType() } //define new event type -const wxEventType FFS_DROP_FILE_EVENT = createNewEventType(); +const wxEventType EVENT_DROP_FILE = createNewEventType(); -class FFSFileDropEvent : public wxCommandEvent +class FileDropEvent : public wxCommandEvent { public: - FFSFileDropEvent(const std::vector<wxString>& filesDropped, const wxWindow& dropWindow, wxPoint dropPos) : - wxCommandEvent(FFS_DROP_FILE_EVENT), + FileDropEvent(const std::vector<wxString>& filesDropped, const wxWindow& dropWindow, wxPoint dropPos) : + wxCommandEvent(EVENT_DROP_FILE), filesDropped_(filesDropped), dropWindow_(dropWindow), dropPos_(dropPos) {} - virtual wxEvent* Clone() const - { - return new FFSFileDropEvent(filesDropped_, dropWindow_, dropPos_); - } + virtual wxEvent* Clone() const { return new FileDropEvent(*this); } const std::vector<wxString>& getFiles() const { return filesDropped_; } const wxWindow& getDropWindow() const { return dropWindow_; } @@ -72,13 +69,13 @@ public: private: const std::vector<wxString> filesDropped_; const wxWindow& dropWindow_; - wxPoint dropPos_; + const wxPoint dropPos_; }; -typedef void (wxEvtHandler::*FFSFileDropEventFunction)(FFSFileDropEvent&); +typedef void (wxEvtHandler::*FileDropEventFunction)(FileDropEvent&); -#define FFSFileDropEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FFSFileDropEventFunction, &func) +#define FileDropEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FileDropEventFunction, &func) class WindowDropTarget : public wxFileDropTarget @@ -86,19 +83,21 @@ class WindowDropTarget : public wxFileDropTarget public: WindowDropTarget(wxWindow& dropWindow) : dropWindow_(dropWindow) {} +private: virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray) { std::vector<wxString> filenames(fileArray.begin(), fileArray.end()); if (!filenames.empty()) { //create a custom event on drop window: execute event after file dropping is completed! (after mouse is released) - FFSFileDropEvent evt(filenames, dropWindow_, wxPoint(x, y)); - dropWindow_.GetEventHandler()->AddPendingEvent(evt); + FileDropEvent evt(filenames, dropWindow_, wxPoint(x, y)); + auto handler = dropWindow_.GetEventHandler(); + if (handler) + handler->AddPendingEvent(evt); } return true; } -private: wxWindow& dropWindow_; }; diff --git a/wx+/format_unit.cpp b/wx+/format_unit.cpp index 13e53ba3..0a054534 100644 --- a/wx+/format_unit.cpp +++ b/wx+/format_unit.cpp @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "format_unit.h" @@ -137,7 +137,8 @@ std::wstring zen::remainingTimeToShortString(double timeInSec) std::wstring zen::fractionToShortString(double fraction) { - return replaceCpy(_("%x%"), L"%x", printNumber<std::wstring>(L"%3.2f", fraction * 100.0), false); + //return replaceCpy(_("%x%"), L"%x", printNumber<std::wstring>(L"%3.2f", fraction * 100.0), false); + return printNumber<std::wstring>(L"%3.2f", fraction * 100.0) + L'%'; //no need to internationalize faction!? } diff --git a/wx+/format_unit.h b/wx+/format_unit.h index 6eba90de..361818cc 100644 --- a/wx+/format_unit.h +++ b/wx+/format_unit.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef UTIL_H_INCLUDED diff --git a/wx+/graph.cpp b/wx+/graph.cpp index 4fdfb35d..7bbfa805 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "graph.h" @@ -145,7 +145,7 @@ void drawXLabel(wxDC& dc, double& xMin, double& xMax, const wxRect& clientArea, if (clientArea.GetHeight() <= 0 || clientArea.GetWidth() <= 0) return; - int optimalBlockWidth = dc.GetMultiLineTextExtent(wxT("100000000000000")).GetWidth(); + const int optimalBlockWidth = dc.GetMultiLineTextExtent(wxT("100000000000000")).GetWidth(); double valRangePerBlock = (xMax - xMin) * optimalBlockWidth / clientArea.GetWidth(); valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock); @@ -244,6 +244,8 @@ Graph2D::Graph2D(wxWindow* parent, //http://wiki.wxwidgets.org/Flicker-Free_Drawing Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Graph2D::onEraseBackGround), NULL, this); + //SetDoubleBuffered(true); slow as hell! + #if wxCHECK_VERSION(2, 9, 1) SetBackgroundStyle(wxBG_STYLE_PAINT); #else @@ -338,7 +340,7 @@ private: void Graph2D::render(wxDC& dc) const { { - //have everything including label background in natural window color by default (overwriting current background color) + //draw everything including label background in natural window color by default (overwriting current background color) const wxColor backColor = wxPanel::GetClassDefaultAttributes().colBg != wxNullColour ? wxPanel::GetClassDefaultAttributes().colBg : wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); @@ -403,24 +405,25 @@ void Graph2D::render(wxDC& dc) const double minWndX = attr.minXauto ? std::numeric_limits<double>::infinity() : attr.minX; //automatic: ensure values are initialized by first curve double maxWndX = attr.maxXauto ? -std::numeric_limits<double>::infinity() : attr.maxX; // if (!curves_.empty()) + for (auto iter = curves_.begin(); iter != curves_.end(); ++iter) + if (iter->first.get()) + { + const GraphData& graph = *iter->first; + assert(graph.getXBegin() <= graph.getXEnd()); + + if (attr.minXauto) + minWndX = std::min(minWndX, graph.getXBegin()); + if (attr.maxXauto) + maxWndX = std::max(maxWndX, graph.getXEnd()); + } + + if (minWndX < maxWndX && maxWndX - minWndX < std::numeric_limits<double>::infinity()) //valid x-range { - for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j) - { - if (!j->first.get()) continue; - const GraphData& graph = *j->first; - assert(graph.getXBegin() <= graph.getXEnd()); - - if (attr.minXauto) - minWndX = std::min(minWndX, graph.getXBegin()); - if (attr.maxXauto) - maxWndX = std::max(maxWndX, graph.getXEnd()); - } if (attr.labelposX != X_LABEL_NONE && //minWndX, maxWndX are just a suggestion, drawXLabel may enlarge them! attr.labelFmtX.get()) drawXLabel(dc, minWndX, maxWndX, xLabelArea, attr.labelHeightX, attr.labelposX == X_LABEL_BOTTOM, *attr.labelFmtX); - } - if (minWndX < maxWndX) //valid x-range - { + + //detect y value range std::vector<std::pair<std::vector<double>, int>> yValuesList(curves_.size()); double minWndY = attr.minYauto ? std::numeric_limits<double>::infinity() : attr.minY; //automatic: ensure values are initialized by first curve @@ -480,11 +483,11 @@ void Graph2D::render(wxDC& dc) const wxPoint currentPos = activeSel->refCurrentPos() - dataOrigin; //normalize positions - confine(startPos .x, 0, dataArea.width); //allow for one past the end(!) to enable "full range selections" - confine(currentPos.x, 0, dataArea.width); // + restrict(startPos .x, 0, dataArea.width); //allow for one past the end(!) to enable "full range selections" + restrict(currentPos.x, 0, dataArea.width); // - confine(startPos .y, 0, dataArea.height); // - confine(currentPos.y, 0, dataArea.height); // + restrict(startPos .y, 0, dataArea.height); // + restrict(currentPos.y, 0, dataArea.height); // //save current selection as double coordinates activeSel->refSelection().from = SelectionBlock::Point(cvrtX.screenToReal(startPos.x + 0.5), //+0.5 start selection in the middle of a pixel @@ -533,7 +536,7 @@ void Graph2D::render(wxDC& dc) const //finally draw curves for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j) { - std::vector<double>& yValues = yValuesList[j - curves_.begin()].first; //actual y-values + std::vector<double>& yValues = yValuesList[j - curves_.begin()].first; //actual y-values int offset = yValuesList[j - curves_.begin()].second; //x-value offset in pixel std::vector<wxPoint> curve; diff --git a/wx+/graph.h b/wx+/graph.h index aadba04b..19de4869 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef WX_PLOT_HEADER_2344252459 @@ -279,7 +279,8 @@ private: void onPaintEvent(wxPaintEvent& evt) { - wxAutoBufferedPaintDC dc(this); //double-buffer only on systems that require it + wxAutoBufferedPaintDC dc(this); //this one happily fucks up for RTL layout by not drawing the first column (x = 0)! + //wxPaintDC dc(this); render(dc); } diff --git a/wx+/grid.cpp b/wx+/grid.cpp new file mode 100644 index 00000000..bfb08eb1 --- /dev/null +++ b/wx+/grid.cpp @@ -0,0 +1,2052 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "grid.h" +#include <cassert> +#include <ctime> +#include <set> +#include <wx/dcbuffer.h> //for macro: wxALWAYS_NATIVE_DOUBLE_BUFFER +#include <wx/settings.h> +#include <wx/listbox.h> +#include <wx/tooltip.h> +#include <wx/timer.h> +#include <wx/utils.h> +#include <zen/string_tools.h> +#include <zen/scope_guard.h> +#include "format_unit.h" + +#ifdef FFS_LINUX +#include <gtk/gtk.h> +#endif + + +using namespace zen; + +wxColor zen::getColorSelectionGradientFrom() { return wxColor(137, 172, 255); } //blue: H:158 S:255 V:196 +wxColor zen::getColorSelectionGradientTo () { return wxColor(225, 234, 255); } // H:158 S:255 V:240 + +void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) +{ + wxDCPenChanger dummy (dc, col); + wxDCBrushChanger dummy2(dc, col); + dc.DrawRectangle(rect); +} + +namespace +{ +//------------ Grid Constants ------------------------------------------------------------------------------------ +const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value as Explorer! +const int DEFAULT_ROW_HEIGHT = 20; +const int DEFAULT_COL_LABEL_HEIGHT = 25; +const int COLUMN_BORDER_LEFT = 4; //for left-aligned text +const int COLUMN_LABEL_BORDER = COLUMN_BORDER_LEFT; +const int COLUMN_MOVE_DELAY = 5; //unit: [pixel] (from Explorer) +const int COLUMN_MIN_WIDTH = 40; //only honored when resizing manually! +const int ROW_LABEL_BORDER = 3; +const int COLUMN_RESIZE_TOLERANCE = 6; //unit [pixel] + +const wxColor COLOR_SELECTION_GRADIENT_NO_FOCUS_FROM = wxColour(192, 192, 192); //light grey wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW); +const wxColor COLOR_SELECTION_GRADIENT_NO_FOCUS_TO = wxColour(228, 228, 228); + +const wxColor COLOR_LABEL_GRADIENT_FROM = wxColour(200, 200, 200); //light grey +const wxColor COLOR_LABEL_GRADIENT_TO = *wxWHITE; + +const wxColor COLOR_LABEL_GRADIENT_FROM_FOCUS = getColorSelectionGradientFrom(); +const wxColor COLOR_LABEL_GRADIENT_TO_FOCUS = COLOR_LABEL_GRADIENT_TO; + +//wxColor getColorRowLabel () { return wxPanel::GetClassDefaultAttributes ().colBg; } // +wxColor getColorMainWinBackground() { return wxListBox::GetClassDefaultAttributes().colBg; } //cannot be initialized statically on wxGTK! + +const wxColor colorGridLine = wxColour(192, 192, 192); //light grey +//---------------------------------------------------------------------------------------------------------------- + + +//a fix for a poor wxWidgets implementation (wxAutoBufferedPaintDC skips one pixel on left side when RTL layout is active) +#ifndef wxALWAYS_NATIVE_DOUBLE_BUFFER +#error we need this one! +#endif + +#if wxALWAYS_NATIVE_DOUBLE_BUFFER +struct BufferedPaintDC : public wxPaintDC { BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) : wxPaintDC(&wnd) {} }; + +#else +class BufferedPaintDC : public wxMemoryDC +{ +public: + BufferedPaintDC(wxWindow& wnd, std::unique_ptr<wxBitmap>& buffer) : buffer_(buffer), paintDc(&wnd) + { + const wxSize clientSize = wnd.GetClientSize(); + if (!buffer_ || clientSize != wxSize(buffer->GetWidth(), buffer->GetHeight())) + buffer.reset(new wxBitmap(clientSize.GetWidth(), clientSize.GetHeight())); + + SelectObject(*buffer); + + if (paintDc.IsOk()) + SetLayoutDirection(paintDc.GetLayoutDirection()); + } + + ~BufferedPaintDC() + { + paintDc.SetLayoutDirection(wxLayout_LeftToRight); //workaround bug in wxDC::Blit() + SetLayoutDirection(wxLayout_LeftToRight); // + + const wxPoint origin = GetDeviceOrigin(); + paintDc.Blit(0, 0, buffer_->GetWidth(), buffer_->GetHeight(), this, -origin.x, -origin.y); + } + +private: + std::unique_ptr<wxBitmap>& buffer_; + wxPaintDC paintDc; +}; +#endif + + +//another fix for yet another poor wxWidgets implementation (wxDCClipper does *not* stack) +hash_map<wxDC*, wxRect> clippingAreas; + +class DcClipper +{ +public: + DcClipper(wxDC& dc, const wxRect& r) : dc_(dc) + { + auto iter = clippingAreas.find(&dc); + if (iter != clippingAreas.end()) + { + oldRect.reset(new wxRect(iter->second)); + + wxRect tmp = r; + tmp.Intersect(*oldRect); //better safe than sorry + dc_.SetClippingRegion(tmp); // + iter->second = tmp; + } + else + { + dc_.SetClippingRegion(r); + clippingAreas.insert(std::make_pair(&dc_, r)); + } + } + + ~DcClipper() + { + dc_.DestroyClippingRegion(); + if (oldRect.get() != NULL) + { + dc_.SetClippingRegion(*oldRect); + clippingAreas[&dc_] = *oldRect; + } + else + clippingAreas.erase(&dc_); + } + +private: + std::unique_ptr<wxRect> oldRect; + wxDC& dc_; +}; +} + +//---------------------------------------------------------------------------------------------------------------- +const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOUBLE = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType(); +const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOWN = wxNewEventType(); +const wxEventType zen::EVENT_GRID_MOUSE_LEFT_UP = wxNewEventType(); +const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_DOWN = wxNewEventType(); +const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_UP = wxNewEventType(); +const wxEventType zen::EVENT_GRID_SELECT_RANGE = wxNewEventType(); +//---------------------------------------------------------------------------------------------------------------- + +void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, int row, bool enabled, bool selected, bool hasFocus) +{ + drawCellBackground(dc, rect, enabled, selected, hasFocus, getColorMainWinBackground()); +} + + +void GridData::renderCell(Grid& grid, wxDC& dc, const wxRect& rect, int row, ColumnType colType) +{ + wxRect rectTmp = drawCellBorder(dc, rect); + + rectTmp.x += COLUMN_BORDER_LEFT; + rectTmp.width -= COLUMN_BORDER_LEFT; + drawCellText(dc, rectTmp, getValue(row, colType), grid.IsEnabled()); +} + + +size_t GridData::getBestSize(wxDC& dc, int row, ColumnType colType) +{ + return dc.GetTextExtent(getValue(row, colType)).GetWidth() + 2 * COLUMN_BORDER_LEFT; //some border on left and right side +} + + +wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle +{ + wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID)); + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); + dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); + + return wxRect(rect.GetTopLeft(), wxSize(rect.width - 1, rect.height - 1)); +} + + +void GridData::drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, bool hasFocus, const wxColor& backgroundColor) +{ + if (enabled) + { + if (selected) + { + if (hasFocus) + dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST); + else + dc.GradientFillLinear(rect, COLOR_SELECTION_GRADIENT_NO_FOCUS_FROM, COLOR_SELECTION_GRADIENT_NO_FOCUS_TO, wxEAST); + } + else + clearArea(dc, rect, backgroundColor); + } + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); +} + + +void GridData::drawCellText(wxDC& dc, const wxRect& rect, const wxString& text, bool enabled, int alignment) +{ + wxDCTextColourChanger dummy(dc, enabled ? dc.GetTextForeground() : wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT)); + + DcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about width, WTF? + dc.DrawLabel(text, rect, alignment); +} + + +void GridData::renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted) +{ + wxRect rectTmp = drawColumnLabelBorder(dc, rect); + drawColumnLabelBackground(dc, rectTmp, highlighted); + + rectTmp.x += COLUMN_BORDER_LEFT; + rectTmp.width -= COLUMN_BORDER_LEFT; + drawColumnLabelText(dc, rectTmp, getColumnLabel(colType)); +} + + +wxRect GridData::drawColumnLabelBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle +{ + //draw white line + { + wxDCPenChanger dummy(dc, *wxWHITE_PEN); + dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft()); + } + + //draw border (with gradient) + { + wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); + dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight()), dc.GetPen().GetColour(), COLOR_LABEL_GRADIENT_TO, wxNORTH); + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight() + wxPoint(1, 0)); + } + + return wxRect(rect.x + 1, rect.y, rect.width - 2, rect.height - 1); //we really don't like wxRect::Deflate, do we? +} + + +void GridData::drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted) +{ + if (highlighted) + dc.GradientFillLinear(rect, COLOR_LABEL_GRADIENT_FROM_FOCUS, COLOR_LABEL_GRADIENT_TO_FOCUS, wxNORTH); + else //regular background gradient + dc.GradientFillLinear(rect, COLOR_LABEL_GRADIENT_FROM, COLOR_LABEL_GRADIENT_TO, wxNORTH); //clear overlapping cells +} + + +void GridData::drawColumnLabelText(wxDC& dc, const wxRect& rect, const wxString& text) +{ + DcClipper clip(dc, rect); //wxDC::DrawLabel doesn't care about witdh, WTF? + dc.DrawLabel(text, rect, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); +} + +//---------------------------------------------------------------------------------------------------------------- + +/* + SubWindow + /|\ + | + ----------------------------------- + | | | | +CornerWin RowLabelWin ColLabelWin MainWin + +*/ + +class Grid::SubWindow : public wxWindow +{ +public: + SubWindow(Grid& parent) : + wxWindow(&parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxBORDER_NONE, wxPanelNameStr), + parent_(parent) + { + Connect(wxEVT_PAINT, wxPaintEventHandler(SubWindow::onPaintEvent), NULL, this); + Connect(wxEVT_SIZE, wxEventHandler(SubWindow::onSizeEvent), NULL, this); + //http://wiki.wxwidgets.org/Flicker-Free_Drawing + Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(SubWindow::onEraseBackGround), NULL, this); + + //SetDoubleBuffered(true); slow as hell! + +#if wxCHECK_VERSION(2, 9, 1) + SetBackgroundStyle(wxBG_STYLE_PAINT); +#else + SetBackgroundStyle(wxBG_STYLE_CUSTOM); +#endif + + Connect(wxEVT_SET_FOCUS, wxFocusEventHandler(SubWindow::onFocus), NULL, this); + Connect(wxEVT_KILL_FOCUS, wxFocusEventHandler(SubWindow::onFocus), NULL, this); + + Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(SubWindow::onMouseLeftDown ), NULL, this); + Connect(wxEVT_LEFT_UP, wxMouseEventHandler(SubWindow::onMouseLeftUp ), NULL, this); + Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(SubWindow::onMouseLeftDouble), NULL, this); + Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(SubWindow::onMouseRightDown ), NULL, this); + Connect(wxEVT_RIGHT_UP, wxMouseEventHandler(SubWindow::onMouseRightUp ), NULL, this); + Connect(wxEVT_MOTION, wxMouseEventHandler(SubWindow::onMouseMovement ), NULL, this); + Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(SubWindow::onLeaveWindow ), NULL, this); + Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(SubWindow::onMouseCaptureLost), NULL, this); + + Connect(wxEVT_CHAR, wxKeyEventHandler(SubWindow::onChar ), NULL, this); + Connect(wxEVT_KEY_UP, wxKeyEventHandler(SubWindow::onKeyUp ), NULL, this); + Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(SubWindow::onKeyDown), NULL, this); + } + + Grid& refParent() { return parent_; } + const Grid& refParent() const { return parent_; } + + template <class T> + bool sendEventNow(T& event) //return "true" if a suitable event handler function was found and executed, and the function did not call wxEvent::Skip. + { + if (wxEvtHandler* evtHandler = parent_.GetEventHandler()) + return evtHandler->ProcessEvent(event); + return false; + } + +protected: + void setToolTip(const wxString& text) //proper fix for wxWindow + { + wxToolTip* tt = GetToolTip(); + + const wxString oldText = tt ? tt->GetTip() : wxString(); + if (text != oldText) + { + if (text.IsEmpty()) + SetToolTip(NULL); //wxGTK doesn't allow wxToolTip with empty text! + else + { + //wxWidgets bug: tooltip multiline property is defined by first tooltip text containing newlines or not (same is true for maximum width) + if (!tt) + SetToolTip(new wxToolTip(wxT("a b\n\ + a b"))); //ugly, but is working (on Windows) + tt = GetToolTip(); //should be bound by now + if (tt) + tt->SetTip(text); + } + } + } + +private: + virtual void render(wxDC& dc, const wxRect& rect) = 0; + + virtual void onFocus(wxFocusEvent& event) { event.Skip(); } + + virtual void onMouseLeftDown (wxMouseEvent& event) { event.Skip(); } + virtual void onMouseLeftUp (wxMouseEvent& event) { event.Skip(); } + virtual void onMouseLeftDouble(wxMouseEvent& event) { event.Skip(); } + virtual void onMouseRightDown (wxMouseEvent& event) { event.Skip(); } + virtual void onMouseRightUp (wxMouseEvent& event) { event.Skip(); } + virtual void onMouseMovement (wxMouseEvent& event) { event.Skip(); } + virtual void onLeaveWindow (wxMouseEvent& event) { event.Skip(); } + virtual void onMouseCaptureLost(wxMouseCaptureLostEvent& event) { event.Skip(); } + + virtual void onChar (wxKeyEvent& event) { event.Skip(); } + virtual void onKeyUp (wxKeyEvent& event) { event.Skip(); } + virtual void onKeyDown(wxKeyEvent& event) { event.Skip(); } + + void onPaintEvent(wxPaintEvent& evt) + { + //wxAutoBufferedPaintDC dc(this); -> this one happily fucks up for RTL layout by not drawing the first column (x = 0)! + BufferedPaintDC dc(*this, buffer); + + assert(GetSize() == GetClientSize()); + + const wxRegion& updateReg = GetUpdateRegion(); + for (wxRegionIterator iter = updateReg; iter; ++iter) + render(dc, iter.GetRect()); + } + + void onSizeEvent(wxEvent& evt) + { + Refresh(); + evt.Skip(); + } + + void onEraseBackGround(wxEraseEvent& evt) {} + + Grid& parent_; + std::unique_ptr<wxBitmap> buffer; +}; + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- + +class Grid::CornerWin : public SubWindow +{ +public: + CornerWin(Grid& parent) : SubWindow(parent) {} + +private: + virtual bool AcceptsFocus() const { return false; } + + virtual void render(wxDC& dc, const wxRect& rect) + { + const wxRect& clientRect = GetClientRect(); + + dc.GradientFillLinear(clientRect, COLOR_LABEL_GRADIENT_FROM, COLOR_LABEL_GRADIENT_TO, wxNORTH); + + dc.SetPen(wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); + + { + wxDCPenChanger dummy(dc, COLOR_LABEL_GRADIENT_TO); + dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight()); + } + + dc.GradientFillLinear(wxRect(clientRect.GetBottomLeft (), clientRect.GetTopLeft ()), dc.GetPen().GetColour(), COLOR_LABEL_GRADIENT_TO, wxNORTH); + dc.GradientFillLinear(wxRect(clientRect.GetBottomRight(), clientRect.GetTopRight()), dc.GetPen().GetColour(), COLOR_LABEL_GRADIENT_TO, wxNORTH); + + dc.DrawLine(clientRect.GetBottomLeft(), clientRect.GetBottomRight()); + + wxRect rectShrinked = clientRect; + rectShrinked.Deflate(1); + dc.SetPen(*wxWHITE_PEN); + + //dc.DrawLine(clientRect.GetTopLeft(), clientRect.GetTopRight() + wxPoint(1, 0)); + dc.DrawLine(rectShrinked.GetTopLeft(), rectShrinked.GetBottomLeft() + wxPoint(0, 1)); + } +}; + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- + +class Grid::RowLabelWin : public SubWindow +{ +public: + RowLabelWin(Grid& parent) : + SubWindow(parent), + rowHeight(DEFAULT_ROW_HEIGHT) {} + + int getBestWidth(int rowFrom, int rowTo) + { + wxClientDC dc(this); + + int bestWidth = 0; + for (int i = rowFrom; i <= rowTo; ++i) + bestWidth = std::max(bestWidth, dc.GetTextExtent(formatRow(i)).GetWidth() + 2 * ROW_LABEL_BORDER); + return bestWidth; + } + + int getLogicalHeight() const { return refParent().getRowCount() * rowHeight; } + + int getRowAtPos(int posY) const //returns < 0 if row not found + { + if (posY >= 0 && rowHeight > 0) + { + const int row = posY / rowHeight; + if (row < static_cast<int>(refParent().getRowCount())) + return row; + } + return -1; + } + + int getRowHeight() const { return rowHeight; } + void setRowHeight(int height) { rowHeight = height; } + + wxRect getRowLabelArea(int row) const //returns empty rect if row not found + { + return wxRect(wxPoint(0, rowHeight * row), + wxSize(GetClientSize().GetWidth(), rowHeight)); + } + + std::pair<int, int> getRowsOnClient(const wxRect& clientRect) const //returns range [begin, end) + { + const int yFrom = refParent().CalcUnscrolledPosition(clientRect.GetTopLeft ()).y; + const int yTo = refParent().CalcUnscrolledPosition(clientRect.GetBottomRight()).y; + + const int rowBegin = std::max(yFrom / rowHeight, 0); + const int rowEnd = std::min((yTo / rowHeight) + 1, static_cast<int>(refParent().getRowCount())); + + return std::make_pair(rowBegin, rowEnd); + } + +private: + static wxString formatRow(int row) { return toStringSep(row + 1); } //convert number to std::wstring including thousands separator + + virtual bool AcceptsFocus() const { return false; } + + virtual void render(wxDC& dc, const wxRect& rect) + { + if (IsEnabled()) + clearArea(dc, rect, getColorMainWinBackground()); + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); + + wxFont labelFont = GetFont(); + labelFont.SetWeight(wxFONTWEIGHT_BOLD); + dc.SetFont(labelFont); + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + + const std::pair<int, int> rowRange = getRowsOnClient(rect); //returns range [begin, end) + + for (int row = rowRange.first; row < rowRange.second; ++row) + { + wxRect singleLabelArea = getRowLabelArea(row); + if (singleLabelArea.GetHeight() > 0) + { + singleLabelArea.y = refParent().CalcScrolledPosition(singleLabelArea.GetTopLeft()).y; + drawRowLabel(dc, singleLabelArea, row); + } + } + } + + void drawRowLabel(wxDC& dc, const wxRect& rect, int row) + { + //clearArea(dc, rect, getColorRowLabel()); + dc.GradientFillLinear(rect, COLOR_LABEL_GRADIENT_FROM, COLOR_LABEL_GRADIENT_TO, wxWEST); //clear overlapping cells + + //label text + wxRect textRect = rect; + textRect.Deflate(1); + { + DcClipper clip(dc, textRect); //wxDC::DrawLabel doesn't care about with, WTF? + dc.DrawLabel(formatRow(row), textRect, wxALIGN_CENTRE); + } + + //border lines + { + wxDCPenChanger dummy(dc, *wxWHITE_PEN); + dc.DrawLine(rect.GetTopLeft(), rect.GetTopRight()); + } + { + wxDCPenChanger dummy(dc, wxPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW), 1, wxSOLID)); + dc.DrawLine(rect.GetTopLeft(), rect.GetBottomLeft()); + dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); + dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); + } + } + + virtual void onMouseLeftDown(wxMouseEvent& event) { refParent().redirectRowLabelEvent(event); } + virtual void onMouseMovement(wxMouseEvent& event) { refParent().redirectRowLabelEvent(event); } + virtual void onMouseLeftUp (wxMouseEvent& event) { refParent().redirectRowLabelEvent(event); } + + int rowHeight; +}; + + +namespace +{ +class ColumnResizing +{ +public: + ColumnResizing(wxWindow& wnd, int col, size_t compPos, int startWidth, int clientPosX) : + wnd_(wnd), + col_(col), + compPos_(compPos), + startWidth_(startWidth), + clientPosX_(clientPosX) { wnd_.CaptureMouse(); } + ~ColumnResizing() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } + + int getColumn () const { return col_; } + size_t getComponentPos() const { return compPos_; } + int getStartWidth () const { return startWidth_; } + int getStartPosX () const { return clientPosX_; } + +private: + wxWindow& wnd_; + const int col_; + const size_t compPos_; + const int startWidth_; + const int clientPosX_; +}; + + +class ColumnMove +{ +public: + ColumnMove(wxWindow& wnd, int colFrom, size_t compPos, int clientPosX) : + wnd_(wnd), + colFrom_(colFrom), + compPos_(compPos), + colTo_(colFrom), + clientPosX_(clientPosX), + singleClick_(true) { wnd_.CaptureMouse(); } + ~ColumnMove() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } + + int getColumnFrom() const { return colFrom_; } + int& refColumnTo() { return colTo_; } + size_t getComponentPos() const { return compPos_; } + int getStartPosX () const { return clientPosX_; } + + bool isRealMove() const { return !singleClick_; } + bool setRealMove() { return singleClick_ = false; } + +private: + wxWindow& wnd_; + const int colFrom_; + const size_t compPos_; + int colTo_; + const int clientPosX_; + bool singleClick_; +}; +} + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- + +class Grid::ColLabelWin : public SubWindow +{ +public: + ColLabelWin(Grid& parent) : SubWindow(parent), highlight(-1, 0) {} + +private: + virtual bool AcceptsFocus() const { return false; } + + virtual void render(wxDC& dc, const wxRect& rect) + { + if (IsEnabled()) + clearArea(dc, rect, getColorMainWinBackground()); + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); + + wxFont labelFont = GetFont(); + labelFont.SetWeight(wxFONTWEIGHT_BOLD); + dc.SetFont(labelFont); + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + + const int colLabelHeight = refParent().colLabelHeight; + + wxPoint labelAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0)).x, 0); //client coordinates + + std::vector<std::vector<VisibleColumn>> compAbsWidths = refParent().getAbsoluteWidths(); //resolve negative/stretched widths + for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) + for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) + { + const size_t col = iterCol - iterComp->begin(); + const int width = iterCol->width_; //don't use unsigned for calculations! + + if (labelAreaTL.x > rect.GetRight()) + return; //done + if (labelAreaTL.x + width > rect.x) + drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(width, colLabelHeight)), col, iterCol->type_, iterComp - compAbsWidths.begin()); + labelAreaTL.x += width; + } + } + + void drawColumnLabel(wxDC& dc, const wxRect& rect, int col, ColumnType colType, size_t compPos) + { + if (auto dataView = refParent().getDataProvider(compPos)) + { + const bool isHighlighted = activeResizing ? col == activeResizing->getColumn () && compPos == activeResizing->getComponentPos() : //highlight column on mouse-over + activeMove ? col == activeMove ->getColumnFrom() && compPos == activeMove ->getComponentPos() : + /**/ col == highlight.first && compPos == highlight.second; + + DcClipper clip(dc, rect); + dataView->renderColumnLabel(refParent(), dc, rect, colType, isHighlighted); + + //draw move target location + if (refParent().columnMoveAllowed(compPos)) + if (activeMove && activeMove->isRealMove() && activeMove->getComponentPos() == compPos) + { + if (col + 1 == activeMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position + dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), *wxBLUE, COLOR_LABEL_GRADIENT_TO, wxNORTH); + else if (col == activeMove->refColumnTo() && col == 0) //pos 0 + dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), *wxBLUE, COLOR_LABEL_GRADIENT_TO, wxNORTH); + } + } + } + + virtual void onMouseLeftDown(wxMouseEvent& event) + { + if (FindFocus() != &refParent().getMainWin()) + refParent().getMainWin().SetFocus(); + + activeResizing.reset(); + activeMove.reset(); + + if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) + { + if (action->wantResize) + { + if (event.LeftDClick()) //auto-size visible range on double-click + { + const int bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error + if (bestWidth >= 0) + refParent().setColWidth(action->col, action->compPos, std::max(COLUMN_MIN_WIDTH, bestWidth)); + } + else + { + if (Opt<size_t> colWidth = refParent().getAbsoluteWidth(action->col, action->compPos)) + activeResizing.reset(new ColumnResizing(*this, action->col, action->compPos, *colWidth, event.GetPosition().x)); + } + } + else //a move or single click + activeMove.reset(new ColumnMove(*this, action->col, action->compPos, event.GetPosition().x)); + } + event.Skip(); + } + + virtual void onMouseLeftUp(wxMouseEvent& event) + { + activeResizing.reset(); //nothing else to do, actual work done by onMouseMovement() + + if (activeMove) + { + const size_t compPos = activeMove->getComponentPos(); + if (activeMove->isRealMove()) + { + if (refParent().columnMoveAllowed(compPos)) + { + const int colFrom = activeMove->getColumnFrom(); + int colTo = activeMove->refColumnTo(); + + if (colTo > colFrom) //simulate "colFrom" deletion + --colTo; + + refParent().moveColumn(colFrom, colTo, compPos); + } + } + else //notify single label click + { + const Opt<ColumnType> colType = refParent().colToType(activeMove->getColumnFrom(), compPos); + if (colType) + { + GridClickEvent clickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, -1, *colType, compPos); + sendEventNow(clickEvent); + } + } + activeMove.reset(); + } + + refParent().updateWindowSizes(); //looks strange if done during onMouseMovement() + refParent().Refresh(); + event.Skip(); + } + + virtual void onMouseCaptureLost(wxMouseCaptureLostEvent& event) + { + activeResizing.reset(); + activeMove.reset(); + Refresh(); + //event.Skip(); -> we DID handle it! + } + + virtual void onMouseLeftDouble(wxMouseEvent& event) + { + const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()); + if (action && action->wantResize) + { + //auto-size visible range on double-click + const int bestWidth = refParent().getBestColumnSize(action->col, action->compPos); //return -1 on error + if (bestWidth >= 0) + { + const size_t newWidth = std::max(COLUMN_MIN_WIDTH, bestWidth); + refParent().setColWidth(action->col, action->compPos, newWidth); + + const Opt<ColumnType> colType = refParent().colToType(action->col, action->compPos); + if (colType) + { + //notify column resize + GridColumnResizeEvent sizeEvent(newWidth, *colType, action->compPos); + sendEventNow(sizeEvent); + } + } + } + event.Skip(); + } + + virtual void onMouseMovement(wxMouseEvent& event) + { + if (activeResizing) + { + const int col = activeResizing->getColumn(); + const size_t compPos = activeResizing->getComponentPos(); + + if (Opt<size_t> colWidth = refParent().getAbsoluteWidth(col, compPos)) + { + const int newWidth = std::max(COLUMN_MIN_WIDTH, activeResizing->getStartWidth() + event.GetPosition().x - activeResizing->getStartPosX()); + if (newWidth != static_cast<int>(*colWidth)) + { + refParent().setColWidth(col, compPos, newWidth); + + const Opt<ColumnType> colType = refParent().colToType(col, compPos); + if (colType) + { + //notify column resize + GridColumnResizeEvent sizeEvent(newWidth, *colType, compPos); + sendEventNow(sizeEvent); + } + + refParent().Refresh(); + } + } + } + else if (activeMove) + { + const int clientPosX = event.GetPosition().x; + if (std::abs(clientPosX - activeMove->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) + { + activeMove->setRealMove(); + + const int col = refParent().clientPosToMoveTargetColumn(event.GetPosition(), activeMove->getComponentPos()); + if (col >= 0) + activeMove->refColumnTo() = col; + } + } + else + { + const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()); + if (action) + { + highlight.first = action->col; + highlight.second = action->compPos; + + if (action->wantResize) + SetCursor(wxCURSOR_SIZEWE); //set window-local only! :) + else + SetCursor(*wxSTANDARD_CURSOR); + } + else + { + highlight.first = -1; + SetCursor(*wxSTANDARD_CURSOR); + } + } + + //change tooltip + const wxString toolTip = [&]() -> wxString + { + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + + const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos), column < 0 if not found + if (colInfo) + { + auto prov = refParent().getDataProvider(colInfo->second); + if (prov) + return prov->getToolTip(colInfo->first); + } + return wxString(); + }(); + setToolTip(toolTip); + + Refresh(); + event.Skip(); + } + + virtual void onLeaveWindow(wxMouseEvent& event) + { + highlight.first = -1; //onLeaveWindow() does not respect mouse capture! -> however highlight is drawn unconditionally during move/resize! + Refresh(); + event.Skip(); + } + + virtual void onMouseRightDown(wxMouseEvent& event) + { + const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition()); + if (action) + { + const Opt<ColumnType> colType = refParent().colToType(action->col, action->compPos); + if (colType) + { + //notify right click + GridClickEvent clickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, -1, *colType, action->compPos); + sendEventNow(clickEvent); + } + } + event.Skip(); + } + + std::unique_ptr<ColumnResizing> activeResizing; + std::unique_ptr<ColumnMove> activeMove; + std::pair<int, size_t> highlight; //(column, component) mouse-over, row < 0 if none +}; + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- + +class Grid::MainWin : public SubWindow +{ +public: + MainWin(Grid& parent, + RowLabelWin& rowLabelWin, + ColLabelWin& colLabelWin) : SubWindow(parent), + rowLabelWin_(rowLabelWin), + colLabelWin_(colLabelWin), + cursor(-1, -1), + selectionAnchor(-1) {} + + void makeRowVisible(int row) + { + const wxRect labelRect = rowLabelWin_.getRowLabelArea(row); //returns empty rect if column not found + if (labelRect.height > 0) + { + int scrollPosX = 0; + refParent().GetViewStart(&scrollPosX, NULL); + + int pixelsPerUnitY = 0; + refParent().GetScrollPixelsPerUnit(NULL, &pixelsPerUnitY); + if (pixelsPerUnitY <= 0) return; + + const int clientPosY = refParent().CalcScrolledPosition(labelRect.GetTopLeft()).y; + if (clientPosY < 0) + { + const int scrollPosY = labelRect.GetTopLeft().y / pixelsPerUnitY; + refParent().Scroll(scrollPosX, scrollPosY); + refParent().updateWindowSizes(); //may show horizontal scroll bar + } + else if (clientPosY + labelRect.GetHeight() > rowLabelWin_.GetClientSize().GetHeight()) + { + auto execScroll = [&](int clientHeight) + { + const int scrollPosY = std::ceil((labelRect.GetTopLeft().y - clientHeight + + labelRect.GetHeight()) / static_cast<double>(pixelsPerUnitY)); + refParent().Scroll(scrollPosX, scrollPosY); + refParent().updateWindowSizes(); //may show horizontal scroll bar + }; + + const int clientHeightBefore = rowLabelWin_.GetClientSize().GetHeight(); + execScroll(clientHeightBefore); + + //client height may decrease after scroll due to a new horizontal scrollbar, resulting in a partially visible last row + const int clientHeightAfter = rowLabelWin_.GetClientSize().GetHeight(); + if (clientHeightAfter < clientHeightBefore) + execScroll(clientHeightAfter); + } + } + } + + void setCursor(int row, size_t compPos) + { + cursor = std::make_pair(row, compPos); + selectionAnchor = row; + } + std::pair<int, size_t> getCursor() const { return cursor; } // (row, component position) + +private: + virtual void render(wxDC& dc, const wxRect& rect) + { + if (IsEnabled()) + clearArea(dc, rect, getColorMainWinBackground()); + else + clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); + + dc.SetFont(GetFont()); + dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); + + const int rowHeight = rowLabelWin_.getRowHeight(); + + const wxPoint topLeft = refParent().CalcUnscrolledPosition(rect.GetTopLeft()); + const wxPoint bottomRight = refParent().CalcUnscrolledPosition(rect.GetBottomRight()); + + const int rowFirst = std::max(topLeft .y / rowHeight, 0); // [rowFirst, rowLast) + const int rowLast = std::min(bottomRight.y / rowHeight + 1, static_cast<int>(refParent().getRowCount())); + + wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates + + std::vector<std::vector<VisibleColumn>> compAbsWidths = refParent().getAbsoluteWidths(); //resolve negative/stretched widths + for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) + { + const int compWidth = std::accumulate(iterComp->begin(), iterComp->end(), 0, + [](int val, const VisibleColumn& vc) { return val + vc.width_; }); + + const size_t compPos = iterComp - compAbsWidths.begin(); + if (auto prov = refParent().getDataProvider(compPos)) + { + //draw background lines + { + DcClipper dummy(dc, rect); //solve issues with drawBackground() painting in area outside of rect (which is not also refreshed by renderCell()) -> keep small scope! + for (int row = rowFirst; row < rowLast; ++row) + drawBackground(*prov, dc, wxRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(compWidth, rowHeight)), row, compPos); + } + + //draw single cells + for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) + { + const int width = iterCol->width_; //don't use unsigned for calculations! + + if (cellAreaTL.x > rect.GetRight()) + return; //done + + if (cellAreaTL.x + width > rect.x) + for (int row = rowFirst; row < rowLast; ++row) + { + const wxRect& cellRect = wxRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, width, rowHeight); + DcClipper clip(dc, cellRect); + prov->renderCell(refParent(), dc, cellRect, row, iterCol->type_); + } + cellAreaTL.x += width; + } + } + else + cellAreaTL.x += compWidth; + } + } + + void drawBackground(GridData& prov, wxDC& dc, const wxRect& rect, int row, size_t compPos) + { + Grid& grid = refParent(); + //check if user is currently selecting with mouse + bool drawSelection = grid.isSelected(row, compPos); + if (activeSelection) + { + const int rowFrom = std::min(activeSelection->getStartRow(), activeSelection->getCurrentRow()); + const int rowTo = std::max(activeSelection->getStartRow(), activeSelection->getCurrentRow()); + + if (compPos == activeSelection->getComponentPos() && rowFrom <= row && row <= rowTo) + drawSelection = activeSelection->isPositiveSelect(); //overwrite default + } + + prov.renderRowBackgound(dc, rect, row, grid.IsEnabled(), drawSelection, wxWindow::FindFocus() == &grid.getMainWin()); + } + + virtual void onMouseLeftDown (wxMouseEvent& event) { onMouseDown(event); } + virtual void onMouseLeftUp (wxMouseEvent& event) { onMouseUp (event); } + virtual void onMouseRightDown(wxMouseEvent& event) { onMouseDown(event); } + virtual void onMouseRightUp (wxMouseEvent& event) { onMouseUp (event); } + + virtual void onMouseLeftDouble(wxMouseEvent& event) + { + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const int row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position + const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos), column < 0 if not found + if (colInfo) + { + //notify event + GridClickEvent mouseEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, colInfo->first, colInfo->second); + sendEventNow(mouseEvent); + } + event.Skip(); + } + + void onMouseDown(wxMouseEvent& event) //handle left and right mouse button clicks (almost) the same + { + if (FindFocus() != this) //doesn't seem to happen automatically for right mouse button + SetFocus(); + + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + + const int row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position + const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos), column < 0 if not found + const ColumnType colType = colInfo ? colInfo->first : DUMMY_COLUMN_TYPE; + const int compPos = colInfo ? colInfo->second : -1; + + //notify event + GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, colType, compPos); + if (!sendEventNow(mouseEvent)) //if event was not processed externally... + { + if (!event.RightDown() || !refParent().isSelected(row, compPos)) //do NOT start a new selection if user right-clicks on a selected area! + { + if (row >= 0 && compPos >= 0) + cursor = std::make_pair(row, compPos); + + if (event.ControlDown()) + { + if (row >= 0 && compPos >= 0) + activeSelection.reset(new MouseSelection(*this, row, compPos, !refParent().isSelected(row, compPos))); + selectionAnchor = row; + } + else if (event.ShiftDown()) + { + if (row >= 0 && compPos >= 0) + activeSelection.reset(new MouseSelection(*this, selectionAnchor, compPos, true)); + refParent().clearSelectionAll(); + } + else + { + if (row >= 0 && compPos >= 0) + activeSelection.reset(new MouseSelection(*this, row, compPos, true)); + selectionAnchor = row; + refParent().clearSelectionAll(); + } + } + Refresh(); + } + + event.Skip(); //allow changin focus + } + + void onMouseUp(wxMouseEvent& event) + { + //const int currentRow = clientPosToRow(event.GetPosition()); -> this one may point to row which is not in visible area! + + if (activeSelection) + { + const int rowFrom = activeSelection->getStartRow(); + const int rowTo = activeSelection->getCurrentRow(); + const size_t compPos = activeSelection->getComponentPos(); + const bool positive = activeSelection->isPositiveSelect(); + refParent().selectRange(rowFrom, rowTo, compPos, positive); + + cursor.first = activeSelection->getCurrentRow(); //slight deviation from Explorer: change cursor while dragging mouse! -> unify behavior with shift + direction keys + + activeSelection.reset(); + } + + //this one may point to row which is not in visible area! + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + + const int row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position + const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos), column < 0 if not found + + const ColumnType colType = colInfo ? colInfo->first : DUMMY_COLUMN_TYPE; //we probably should notify even if colInfo is invalid! + const int compPos = colInfo ? colInfo->second : 0; + + //notify event + GridClickEvent mouseEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, colType, compPos); + sendEventNow(mouseEvent); + + Refresh(); + event.Skip(); //allow changing focus + } + + virtual void onMouseCaptureLost(wxMouseCaptureLostEvent& event) + { + activeSelection.reset(); + Refresh(); + //event.Skip(); -> we DID handle it! + } + + virtual void onMouseMovement(wxMouseEvent& event) + { + if (activeSelection) + activeSelection->evalMousePos(); //eval on both mouse movement + timer event! + + //change tooltip + const wxString toolTip = [&]() -> wxString + { + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + + const int row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position + const auto colInfo = refParent().getColumnAtPos(absPos.x); //returns (column type, compPos), column < 0 if not found + if (colInfo && row >= 0) + { + auto prov = refParent().getDataProvider(colInfo->second); + if (prov) + return prov->getToolTip(row, colInfo->first); + } + return wxString(); + }(); + + setToolTip(toolTip); + + event.Skip(); + } + + virtual void onKeyDown(wxKeyEvent& event) + { + int keyCode = event.GetKeyCode(); + if (GetLayoutDirection() == wxLayout_RightToLeft) + { + if (keyCode == WXK_LEFT) + keyCode = WXK_RIGHT; + else if (keyCode == WXK_RIGHT) + keyCode = WXK_LEFT; + else if (keyCode == WXK_NUMPAD_LEFT) + keyCode = WXK_NUMPAD_RIGHT; + else if (keyCode == WXK_NUMPAD_RIGHT) + keyCode = WXK_NUMPAD_LEFT; + } + + const int rowCount = refParent().getRowCount(); + + auto setSingleSelection = [&](int row, int compPos) + { + numeric::restrict(row, 0, rowCount - 1); + numeric::restrict(compPos, 0, static_cast<int>(refParent().comp.size()) - 1); + refParent().setGridCursor(row, compPos); + }; + + auto setSelectionRange = [&](int row) + { + numeric::restrict(row, 0, rowCount - 1); + + cursor.first = row; + if (selectionAnchor < 0) + selectionAnchor = row; + this->makeRowVisible(row); + + auto& comp = refParent().comp; + std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); //clear selection, do NOT fire event + refParent().selectRange(selectionAnchor, row, cursor.second); //set new selection + fire event + }; + + switch (keyCode) + { + case WXK_UP: + case WXK_NUMPAD_UP: + if (event.ShiftDown()) + setSelectionRange(cursor.first - 1); + else if (event.ControlDown()) + refParent().scrollDelta(0, -1); + else + setSingleSelection(cursor.first - 1, cursor.second); + break; + + case WXK_DOWN: + case WXK_NUMPAD_DOWN: + if (event.ShiftDown()) + setSelectionRange(cursor.first + 1); + else if (event.ControlDown()) + refParent().scrollDelta(0, 1); + else + setSingleSelection(cursor.first + 1, cursor.second); + break; + + case WXK_LEFT: + case WXK_NUMPAD_LEFT: + if (event.ControlDown()) + refParent().scrollDelta(-1, 0); + else if (event.ShiftDown()) + ; + else + setSingleSelection(cursor.first, cursor.second - 1); + break; + + case WXK_RIGHT: + case WXK_NUMPAD_RIGHT: + if (event.ControlDown()) + refParent().scrollDelta(1, 0); + else if (event.ShiftDown()) + ; + else + setSingleSelection(cursor.first, cursor.second + 1); + break; + + case WXK_HOME: + case WXK_NUMPAD_HOME: + if (event.ShiftDown()) + setSelectionRange(0); + else if (event.ControlDown()) + setSingleSelection(0, 0); + else + setSingleSelection(0, cursor.second); + break; + + case WXK_END: + case WXK_NUMPAD_END: + if (event.ShiftDown()) + setSelectionRange(rowCount - 1); + else if (event.ControlDown()) + setSingleSelection(rowCount - 1, refParent().comp.size() - 1); + else + setSingleSelection(rowCount - 1, cursor.second); + break; + + case WXK_PAGEUP: + case WXK_NUMPAD_PAGEUP: + if (event.ShiftDown()) + setSelectionRange(cursor.first - GetClientSize().GetHeight() / rowLabelWin_.getRowHeight()); + else if (event.ControlDown()) + ; + else + setSingleSelection(cursor.first - GetClientSize().GetHeight() / rowLabelWin_.getRowHeight(), cursor.second); + break; + + case WXK_PAGEDOWN: + case WXK_NUMPAD_PAGEDOWN: + if (event.ShiftDown()) + setSelectionRange(cursor.first + GetClientSize().GetHeight() / rowLabelWin_.getRowHeight()); + else if (event.ControlDown()) + ; + else + setSingleSelection(cursor.first + GetClientSize().GetHeight() / rowLabelWin_.getRowHeight(), cursor.second); + break; + + case 'A': //Ctrl + A - select all + if (event.ControlDown()) + refParent().selectRange(0, rowCount, cursor.second); + break; + + case WXK_NUMPAD_ADD: //CTRL + '+' - auto-size all + if (event.ControlDown()) + refParent().autoSizeColumns(cursor.second); + break; + } + + Refresh(); + event.Skip(); + } + + virtual void onFocus(wxFocusEvent& event) { Refresh(); event.Skip(); } + + class MouseSelection : private wxEvtHandler + { + public: + MouseSelection(MainWin& wnd, + int rowStart, + size_t compPos, + bool positiveSelect) : wnd_(wnd), rowStart_(rowStart), compPos_(compPos), rowCurrent_(rowStart), positiveSelect_(positiveSelect), toScrollX(0), toScrollY(0), tickCountLast(clock()) + { + wnd_.CaptureMouse(); + timer.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), NULL, this); + timer.Start(100); //timer interval in ms + evalMousePos(); + } + ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } + + int getStartRow () const { return rowStart_; } + size_t getComponentPos () const { return compPos_; } + int getCurrentRow () const { return rowCurrent_; } + bool isPositiveSelect() const { return positiveSelect_; } //are we selecting or unselecting? + + void evalMousePos() + { + const clock_t now = clock(); + const double deltaTime = static_cast<double>(now - tickCountLast) / CLOCKS_PER_SEC; //unit: [sec] + tickCountLast = now; + + wxMouseState mouseState = wxGetMouseState(); + const wxPoint clientPos = wnd_.ScreenToClient(wxPoint(mouseState.GetX(), mouseState.GetY())); + const wxSize clientSize = wnd_.GetClientSize(); + + //scroll while dragging mouse + const int overlapPixY = clientPos.y < 0 ? clientPos.y : + clientPos.y >= clientSize.GetHeight() ? clientPos.y - (clientSize.GetHeight() - 1) : 0; + const int overlapPixX = clientPos.x < 0 ? clientPos.x : + clientPos.x >= clientSize.GetWidth() ? clientPos.x - (clientSize.GetWidth() - 1) : 0; + + int pixelsPerUnitY = 0; + wnd_.refParent().GetScrollPixelsPerUnit(NULL, &pixelsPerUnitY); + if (pixelsPerUnitY <= 0) return; + + const double mouseDragSpeedIncScrollU = pixelsPerUnitY > 0 ? MOUSE_DRAG_ACCELERATION * wnd_.rowLabelWin_.getRowHeight() / pixelsPerUnitY : 0; //unit: [scroll units / (pixel * sec)] + + auto autoScroll = [&](int overlapPix, double& toScroll) + { + if (overlapPix != 0) + { + const double scrollSpeed = overlapPix * mouseDragSpeedIncScrollU; //unit: [scroll units / sec] + toScroll += scrollSpeed * deltaTime; + } + else + toScroll = 0; + }; + + autoScroll(overlapPixX, toScrollX); + autoScroll(overlapPixY, toScrollY); + + if (toScrollX != 0 || 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 + } + + { + //select current row *after* scrolling + wxPoint clientPosTrimmed = clientPos; + numeric::restrict(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window! + + wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed); + int currentRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 if no row at this position + + //make sure "current row" is always at a valid position while moving! + if (currentRow < 0) + currentRow = static_cast<int>(wnd_.refParent().getRowCount()) - 1; //seems, we hit the empty space at the end + + if (currentRow >= 0 && rowCurrent_ != currentRow) + { + rowCurrent_ = currentRow; + wnd_.Refresh(); + } + } + } + + private: + void onTimer(wxEvent& event) { evalMousePos(); } + + MainWin& wnd_; + const int rowStart_; + const size_t compPos_; + int rowCurrent_; + const bool positiveSelect_; + wxTimer timer; + double toScrollX; //count outstanding scroll units to scroll while dragging mouse + double toScrollY; // + clock_t tickCountLast; + }; + + virtual void ScrollWindow(int dx, int dy, const wxRect* rect) + { + wxWindow::ScrollWindow(dx, dy, rect); + rowLabelWin_.ScrollWindow(0, dy, rect); + colLabelWin_.ScrollWindow(dx, 0, rect); + + refParent().updateWindowSizes(false); //row label width has changed -> do *not* update scrollbars: recursion on wxGTK! + rowLabelWin_.Update(); //update while dragging scroll thumb + } + + RowLabelWin& rowLabelWin_; + ColLabelWin& colLabelWin_; + + std::unique_ptr<MouseSelection> activeSelection; //bound while user is selecting with mouse + + std::pair<int, int> cursor; //(row, component position) -1 if none + int selectionAnchor; //-1 if none +}; + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- + +Grid::Grid(wxWindow* parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) : wxScrolledWindow(parent, id, pos, size, style | wxWANTS_CHARS, name), + showScrollbarX(true), + showScrollbarY(true), + colLabelHeight(DEFAULT_COL_LABEL_HEIGHT), + drawRowLabel(true), + comp(1), + colSizeOld(0) +{ + Connect(wxEVT_PAINT, wxPaintEventHandler(Grid::onPaintEvent ), NULL, this); + Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Grid::onEraseBackGround), NULL, this); + Connect(wxEVT_SIZE, wxEventHandler (Grid::onSizeEvent ), NULL, this); + + cornerWin_ = new CornerWin (*this); // + rowLabelWin_ = new RowLabelWin(*this); //owership handled by "this" + colLabelWin_ = new ColLabelWin(*this); // + mainWin_ = new MainWin (*this, *rowLabelWin_, *colLabelWin_); // + + SetTargetWindow(mainWin_); + + SetInitialSize(size); //"Most controls will use this to set their initial size" -> why not + + assert(GetClientSize() == GetSize()); //borders are NOT allowed for Grid + //reason: updateWindowSizes() wants to use "GetSize()" as a "GetClientSize()" including scrollbars +} + + +void Grid::updateWindowSizes(bool updateScrollbar) +{ + /* We have to deal with a nasty circular dependency: + + mainWin_->GetClientSize() + /|\ + SetScrollbars -> show/hide scrollbars depending on whether client size is big enough + /|\ + GetClientRect(); -> possibly trimmed by scrollbars + /|\ + mainWin_->GetClientSize() -> also trimmed, since it's a sub-window ! + + */ + + //update scrollbars: showing/hiding scrollbars changes client size! + if (updateScrollbar) + { + //help SetScrollbars() do a better job + //mainWin_->SetSize(std::max(0, GetSize().GetWidth () - rowLabelWidth), -> not working! + // std::max(0, GetSize().GetHeight() - colLabelHeight)); + + int scrollPosX = 0; + int scrollPosY = 0; + GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position + + const int pixPerScrollUnitY = std::max(1, rowLabelWin_->getRowHeight()); + const int pixPerScrollUnitX = pixPerScrollUnitY; + + const int scrollUnitsX = std::ceil(static_cast<double>( getMinAbsoluteWidthTotal()) / pixPerScrollUnitX); + const int scrollUnitsY = std::ceil(static_cast<double>(rowLabelWin_->getLogicalHeight()) / pixPerScrollUnitY); + + SetScrollbars(pixPerScrollUnitX, pixPerScrollUnitY, //another abysmal wxWidgets design decision: why is precision needlessly reduced to "pixelsPerUnit"???? + scrollUnitsX, scrollUnitsY, + scrollPosX, scrollPosY); + } + + const wxRect clientRect = GetClientRect(); + + const int mainWinHeight = std::max(0, clientRect.height - colLabelHeight); + + int rowLabelWidth = 0; //calculate optimal row label width + if (drawRowLabel) + { + const int heightTotal = rowLabelWin_->getLogicalHeight(); + if (heightTotal > 0) + { + int yFrom = CalcUnscrolledPosition(wxPoint(0, 0)).y; + int yTo = CalcUnscrolledPosition(wxPoint(0, mainWinHeight - 1)).y ; + numeric::restrict(yFrom, 0, heightTotal - 1); + numeric::restrict(yTo, 0, heightTotal - 1); + + const int rowFrom = rowLabelWin_->getRowAtPos(yFrom); + const int rowTo = rowLabelWin_->getRowAtPos(yTo); + if (rowFrom >= 0 && rowTo >= 0) + rowLabelWidth = rowLabelWin_->getBestWidth(rowFrom, rowTo); + } + } + + const int mainWinWidth = std::max(0, clientRect.width - rowLabelWidth); + + cornerWin_ ->SetSize(0, 0, rowLabelWidth, colLabelHeight); + rowLabelWin_->SetSize(0, colLabelHeight, rowLabelWidth, mainWinHeight); + colLabelWin_->SetSize(rowLabelWidth, 0, mainWinWidth, colLabelHeight); + mainWin_ ->SetSize(rowLabelWidth, colLabelHeight, mainWinWidth, mainWinHeight); +} + + +void Grid::onPaintEvent(wxPaintEvent& event) { wxPaintDC dc(this); } + + +void Grid::setColumnLabelHeight(int height) +{ + colLabelHeight = std::max(0, height); + updateWindowSizes(); +} + + +void Grid::showRowLabel(bool show) +{ + drawRowLabel = show; + updateWindowSizes(); +} + + +std::vector<int> Grid::getSelectedRows(size_t compPos) const +{ + return compPos < comp.size() ? comp[compPos].selection.get() : std::vector<int>(); +} + + +void Grid::scrollDelta(int deltaX, int deltaY) +{ + int scrollPosX = 0; + int scrollPosY = 0; + GetViewStart(&scrollPosX, &scrollPosY); + + scrollPosX += deltaX; + scrollPosY += deltaY; + + //const int unitsTotalX = GetScrollLines(wxHORIZONTAL); + //const int unitsTotalY = GetScrollLines(wxVERTICAL); + + //if (unitsTotalX <= 0 || unitsTotalY <= 0) return; -> premature + //numeric::restrict(scrollPosX, 0, unitsTotalX - 1); //make sure scroll target is in valid range + //numeric::restrict(scrollPosY, 0, unitsTotalY - 1); // + + Scroll(scrollPosX, scrollPosY); + updateWindowSizes(); //may show horizontal scroll bar +} + + +void Grid::redirectRowLabelEvent(wxMouseEvent& event) +{ + event.m_x = 0; + mainWin_->ProcessEvent(event); + + if (event.ButtonDown() && wxWindow::FindFocus() != mainWin_) + mainWin_->SetFocus(); +} + + +size_t Grid::getRowCount() const +{ + return comp.empty() ? 0 : comp.front().dataView_ ? comp.front().dataView_->getRowCount() : 0; +} + + +void Grid::Refresh(bool eraseBackground, const wxRect* rect) +{ + const size_t rowCountNew = getRowCount(); + if (colSizeOld != rowCountNew) + { + colSizeOld = rowCountNew; + std::for_each(comp.begin(), comp.end(), [&](Component& c) { c.selection.init(rowCountNew); }); + updateWindowSizes(); + } + wxScrolledWindow::Refresh(eraseBackground, rect); +} + + +void Grid::setRowHeight(int height) +{ + rowLabelWin_->setRowHeight(height); + updateWindowSizes(); +} + + +void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr, size_t compPos) +{ + if (compPos < comp.size()) + { + //hold ownership of non-visible columns + comp[compPos].oldColAttributes = attr; + + std::vector<VisibleColumn> visibleCols; + std::for_each(attr.begin(), attr.end(), + [&](const ColumnAttribute& ca) + { + if (ca.visible_) + visibleCols.push_back(Grid::VisibleColumn(ca.type_, ca.width_)); + }); + + //set ownership of visible columns + comp[compPos].visibleCols = visibleCols; + + updateWindowSizes(); + Refresh(); + } +} + + +std::vector<Grid::ColumnAttribute> Grid::getColumnConfig(size_t compPos) const +{ + if (compPos < comp.size()) + { + auto iterVcols = comp[compPos].visibleCols.begin(); + auto iterVcolsend = comp[compPos].visibleCols.end(); + + std::set<ColumnType> visibleTypes; + std::transform(iterVcols, iterVcolsend, std::inserter(visibleTypes, visibleTypes.begin()), + [](const VisibleColumn& vc) { return vc.type_; }); + + //get non-visible columns (+ outdated visible ones) + std::vector<ColumnAttribute> output = comp[compPos].oldColAttributes; + + //update visible columns but keep order of non-visible ones! + std::for_each(output.begin(), output.end(), + [&](ColumnAttribute& ca) + { + if (visibleTypes.find(ca.type_) != visibleTypes.end()) + { + if (iterVcols != iterVcolsend) + { + ca.visible_ = true; //paranoia + ca.type_ = iterVcols->type_; + ca.width_ = iterVcols->width_; + ++iterVcols; + } + } + else + ca.visible_ = false; //paranoia + }); + + return output; + } + return std::vector<ColumnAttribute>(); +} + + +void Grid::showScrollBars(bool horizontal, bool vertical) +{ +#ifdef FFS_WIN + showScrollbarX = horizontal; + showScrollbarY = vertical; + updateWindowSizes(); + +#elif defined FFS_LINUX //get rid of scrollbars, but preserve scrolling behavior! + GtkWidget* gridWidget = wxWindow::m_widget; + GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget); + gtk_scrolled_window_set_policy(scrolledWindow, + horizontal ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER, + vertical ? GTK_POLICY_AUTOMATIC : GTK_POLICY_NEVER); +#endif +} + + +#ifdef FFS_WIN //get rid of scrollbars, but preserve scrolling behavior! +void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) +{ + if ((orientation == wxHORIZONTAL && !showScrollbarX) || (orientation == wxVERTICAL && !showScrollbarY)) + wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh); + else + wxWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); +} + + +#ifndef WM_MOUSEHWHEEL //MinGW is clueless... +#define WM_MOUSEHWHEEL 0x020E +#endif + +WXLRESULT Grid::MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (nMsg == WM_MOUSEHWHEEL) + { + const int distance = GET_WHEEL_DELTA_WPARAM(wParam); + const int delta = WHEEL_DELTA; + const int rotations = distance / delta; + + static int linesPerRotation = -1; + if (linesPerRotation < 0) + if (!::SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &linesPerRotation, 0)) + linesPerRotation = 3; + + SetFocus(); + scrollDelta(rotations * linesPerRotation, 0); //in scroll units + } + + return wxScrolledWindow::MSWDefWindowProc(nMsg, wParam, lParam);; +} +#endif + + +wxWindow& Grid::getCornerWin () { return *cornerWin_; } +wxWindow& Grid::getRowLabelWin() { return *rowLabelWin_; } +wxWindow& Grid::getColLabelWin() { return *colLabelWin_; } +wxWindow& Grid::getMainWin () { return *mainWin_; } + + +wxRect Grid::getColumnLabelArea(ColumnType colType, size_t compPos) const +{ + std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + if (compPos < compAbsWidths.size()) + { + auto iterComp = compAbsWidths.begin() + compPos; + + auto iterCol = std::find_if(iterComp->begin(), iterComp->end(), [&](const VisibleColumn& vc) { return vc.type_ == colType; }); + if (iterCol != iterComp->end()) + { + int posX = std::accumulate(compAbsWidths.begin(), iterComp, 0, + [](int val, const std::vector<VisibleColumn>& cols) + { + return val + std::accumulate(cols.begin(), cols.end(), 0, [](int val2, const VisibleColumn& vc) { return val2 + vc.width_; }); + }); + + posX += std::accumulate(iterComp->begin(), iterCol, 0, [](int val, const VisibleColumn& vc) { return val + vc.width_; }); + + return wxRect(wxPoint(posX, 0), wxSize(iterCol->width_, colLabelHeight)); + } + } + return wxRect(); +} + + +Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const +{ + const int absPosX = CalcUnscrolledPosition(pos).x; + if (absPosX >= 0) + { + int accuWidth = 0; + + std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) + { + const size_t compPos = iterComp - compAbsWidths.begin(); + const int resizeTolerance = columnResizeAllowed(compPos) ? COLUMN_RESIZE_TOLERANCE : 0; + + for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) + { + const size_t col = iterCol - iterComp->begin(); + + accuWidth += iterCol->width_; + if (std::abs(absPosX - accuWidth) < resizeTolerance) + { + ColAction out = {}; + out.wantResize = true; + out.col = col; + out.compPos = compPos; + return out; + } + else if (absPosX < accuWidth) + { + ColAction out = {}; + out.wantResize = false; + out.col = col; + out.compPos = compPos; + return out; + } + } + } + } + return NoValue(); +} + + +void Grid::moveColumn(size_t colFrom, size_t colTo, size_t compPos) +{ + if (compPos < comp.size()) + { + auto& visibleCols = comp[compPos].visibleCols; + if (colFrom < visibleCols.size() && + colTo < visibleCols.size() && + colTo != colFrom) + { + const auto colAtt = visibleCols[colFrom]; + visibleCols.erase (visibleCols.begin() + colFrom); + visibleCols.insert(visibleCols.begin() + colTo, colAtt); + } + } +} + + +int Grid::clientPosToMoveTargetColumn(const wxPoint& pos, size_t compPos) const +{ + std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + if (compPos < compAbsWidths.size()) + { + auto iterComp = compAbsWidths.begin() + compPos; + const int absPosX = CalcUnscrolledPosition(pos).x; + + int accuWidth = std::accumulate(compAbsWidths.begin(), iterComp, 0, + [](int val, const std::vector<VisibleColumn>& cols) + { + return val + std::accumulate(cols.begin(), cols.end(), 0, + [](int val2, const VisibleColumn& vc) { return val2 + vc.width_; }); + }); + + for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) + { + const int width = iterCol->width_; //beware dreaded unsigned conversions! + accuWidth += width; + + if (absPosX < accuWidth - width / 2) + return iterCol - iterComp->begin(); + } + return iterComp->size(); + } + return -1; +} + + +Opt<ColumnType> Grid::colToType(size_t col, size_t compPos) const +{ + if (compPos < comp.size()) + { + auto& visibleCols = comp[compPos].visibleCols; + if (col < visibleCols.size()) + return visibleCols[col].type_; + } + return NoValue(); +} + + +int Grid::getRowAtPos(int posY) const { return rowLabelWin_->getRowAtPos(posY); } + + +Opt<std::pair<ColumnType, size_t>> Grid::getColumnAtPos(int posX) const +{ + if (posX >= 0) + { + std::vector<std::vector<VisibleColumn>> compAbsWidths = getAbsoluteWidths(); //resolve negative/stretched widths + + int accWidth = 0; + for (auto iterComp = compAbsWidths.begin(); iterComp != compAbsWidths.end(); ++iterComp) + for (auto iterCol = iterComp->begin(); iterCol != iterComp->end(); ++iterCol) + { + accWidth += iterCol->width_; + if (posX < accWidth) + { + const size_t compPos = iterComp - compAbsWidths.begin(); + return std::make_pair(iterCol->type_, compPos); + } + } + } + return NoValue(); +} + + +wxRect Grid::getCellArea(int row, ColumnType colType, size_t compPos) const +{ + const wxRect& colArea = getColumnLabelArea(colType, compPos); + const wxRect& rowArea = rowLabelWin_->getRowLabelArea(row); + return wxRect(wxPoint(colArea.x, rowArea.y), wxSize(colArea.width, rowArea.height)); +} + + +void Grid::setGridCursor(int row, size_t compPos) +{ + if (compPos < comp.size()) + { + std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); //clear selection, do NOT fire event + selectRange(row, row, compPos); //set new selection + fire event + + mainWin_->setCursor(row, compPos); + mainWin_->makeRowVisible(row); + mainWin_->Refresh(); + } +} + + +void Grid::selectRange(int rowFrom, int rowTo, size_t compPos, bool positive) +{ + if (compPos < comp.size()) + { + comp[compPos].selection.selectRange(rowFrom, rowTo, positive); + + //notify event + GridRangeSelectEvent selectionEvent(rowFrom, rowTo, compPos, positive); + if (wxEvtHandler* evtHandler = GetEventHandler()) + evtHandler->ProcessEvent(selectionEvent); + } +} + + +void Grid::clearSelectionAll() +{ + std::for_each(comp.begin(), comp.end(), [](Grid::Component& c) { c.selection.clear(); }); + + //notify event + GridRangeSelectEvent unselectionEvent(-1, -1, static_cast<size_t>(-1), false); + if (wxEvtHandler* evtHandler = GetEventHandler()) + evtHandler->ProcessEvent(unselectionEvent); +} + + +void Grid::scrollTo(int row) +{ + const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if column not found + if (labelRect.height > 0) + { + int pixelsPerUnitY = 0; + GetScrollPixelsPerUnit(NULL, &pixelsPerUnitY); + if (pixelsPerUnitY >= 0) + { + int scrollPosX = 0; + GetViewStart(&scrollPosX, NULL); + int scrollPosY = labelRect.GetTopLeft().y / pixelsPerUnitY; + + Scroll(scrollPosX, scrollPosY); + updateWindowSizes(); //may show horizontal scroll bar + Refresh(); + } + } +} + + +std::pair<int, size_t> Grid::getGridCursor() const +{ + return mainWin_->getCursor(); +} + + +int Grid::getBestColumnSize(size_t col, size_t compPos) const +{ + if (compPos < comp.size()) + { + auto& visibleCols = comp[compPos].visibleCols; + auto dataView = comp[compPos].dataView_; + if (dataView && col < visibleCols.size()) + { + const ColumnType type = visibleCols[col].type_; + + wxClientDC dc(mainWin_); + dc.SetFont(mainWin_->GetFont()); + + size_t maxSize = 0; + const std::pair<int, int> rowRange = rowLabelWin_->getRowsOnClient(mainWin_->GetClientRect()); //returns range [begin, end) + + for (int row = rowRange.first; row < rowRange.second; ++row) + maxSize = std::max(maxSize, dataView->getBestSize(dc, row, type)); + + return maxSize; + } + } + return -1; +} + + +void Grid::autoSizeColumns(size_t compPos) +{ + if (compPos < comp.size() && comp[compPos].allowColumnResize) + { + auto& visibleCols = comp[compPos].visibleCols; + for (auto iter = visibleCols.begin(); iter != visibleCols.end(); ++iter) + { + const int col = iter - visibleCols.begin(); + const int bestWidth = getBestColumnSize(col, compPos); //return -1 on error + if (bestWidth >= 0) + { + const size_t newWidth = std::max(COLUMN_MIN_WIDTH, bestWidth); + iter->width_ = newWidth; + + //notify column resize (asynchronously!) + GridColumnResizeEvent sizeEvent(newWidth, iter->type_, compPos); + wxEvtHandler* evtHandler = GetEventHandler(); + if (evtHandler) + evtHandler->AddPendingEvent(sizeEvent); + } + } + updateWindowSizes(); + Refresh(); + } +} + + +int Grid::getMinAbsoluteWidthTotal() const +{ + int minWidthTotal = 0; + //bool haveStretchedCols = false; + std::for_each(comp.begin(), comp.end(), + [&](const Component& c) + { + std::for_each(c.visibleCols.begin(), c.visibleCols.end(), + [&](const VisibleColumn& vc) + { + if (vc.width_ >= 0) + minWidthTotal += vc.width_; + else + { + //haveStretchedCols = true; + minWidthTotal += COLUMN_MIN_WIDTH; //use "min width" if column is stretched + } + }); + }); + return minWidthTotal; +} + + +std::vector<std::vector<Grid::VisibleColumn>> Grid::getAbsoluteWidths() const //evaluate negative widths as stretched absolute values! structure matches "comp" +{ + std::vector<std::vector<VisibleColumn>> output; + + std::vector<std::pair<int, VisibleColumn*>> stretchedCols; //(factor, column to stretch) + int factorTotal = 0; + int minWidthTotal = 0; + + output.reserve(comp.size()); + std::for_each(comp.begin(), comp.end(), + [&](const Component& c) + { + output.push_back(std::vector<VisibleColumn>()); + auto& compWidths = output.back(); + + compWidths.reserve(c.visibleCols.size()); + std::for_each(c.visibleCols.begin(), c.visibleCols.end(), + [&](const VisibleColumn& vc) + { + if (vc.width_ >= 0) + { + compWidths.push_back(Grid::VisibleColumn(vc.type_, vc.width_)); + minWidthTotal += vc.width_; + } + else //stretched column + { + compWidths.push_back(Grid::VisibleColumn(vc.type_, COLUMN_MIN_WIDTH)); //use "min width" if column is stretched + minWidthTotal += COLUMN_MIN_WIDTH; + + stretchedCols.push_back(std::make_pair(vc.width_, &compWidths.back())); + factorTotal += vc.width_; + } + }); + }); + + if (!stretchedCols.empty()) + { + const int widthToFill = mainWin_->GetClientSize().GetWidth() - minWidthTotal; + if (widthToFill > 0) + { + int widthRemaining = widthToFill; + for (auto iter = stretchedCols.begin(); iter != stretchedCols.end(); ++iter) + { + const int addWidth = (widthToFill * iter->first) / factorTotal; //round down + iter->second->width_ += addWidth; + widthRemaining -= addWidth; + } + + if (widthRemaining > 0) //should be empty, except for rounding errors + stretchedCols.back().second->width_ += widthRemaining; + } + } + return output; +} @@ -1,37 +1,116 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef WX_TREE_LIST_HEADER_8347021348317 -#define WX_TREE_LIST_HEADER_8347021348317 +#ifndef GENERIC_GRID_HEADER_83470213483173 +#define GENERIC_GRID_HEADER_83470213483173 -#include <set> #include <memory> +#include <numeric> #include <vector> #include <wx/scrolwin.h> +#include <zen/basic_math.h> +#include <zen/optional.h> + +//a user-friendly, extensible and high-performance grid control namespace zen { +typedef enum { DUMMY_COLUMN_TYPE = static_cast<size_t>(-1) } ColumnType; + +//----- Events ----------------------------------------------------------------------------------------------- +extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridClickEvent +extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; // +extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent + +extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOUBLE; // +extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOWN; // +extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEvent +extern const wxEventType EVENT_GRID_MOUSE_RIGHT_DOWN; // +extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; // + +extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent +//NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window... +//=> range always specifies *valid* rows + +//example: wnd.Connect(EVENT_GRID_COL_LABEL_LEFT_CLICK, GridClickEventHandler(MyDlg::OnLeftClick), NULL, this); + + +struct GridClickEvent : public wxMouseEvent +{ + GridClickEvent(wxEventType et, const wxMouseEvent& me, int row, ColumnType colType, size_t compPos) : wxMouseEvent(me), row_(row), colType_(colType), compPos_(compPos) { SetEventType(et); } + virtual wxEvent* Clone() const { return new GridClickEvent(*this); } + + const int row_; + const ColumnType colType_; + const size_t compPos_; +}; + +struct GridColumnResizeEvent : public wxCommandEvent +{ + GridColumnResizeEvent(int width, ColumnType colType, size_t compPos) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), width_(width), compPos_(compPos) {} + virtual wxEvent* Clone() const { return new GridColumnResizeEvent(*this); } + + const ColumnType colType_; + const int width_; + const size_t compPos_; +}; + +struct GridRangeSelectEvent : public wxCommandEvent +{ + GridRangeSelectEvent(int rowFrom, int rowTo, size_t compPos, bool positive) : wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFrom_(rowFrom), rowTo_(rowTo), compPos_(compPos), positive_(positive) {} + virtual wxEvent* Clone() const { return new GridRangeSelectEvent(*this); } + + const int rowFrom_; + const int rowTo_; + const size_t compPos_; + const bool positive_; +}; + +typedef void (wxEvtHandler::*GridClickEventFunction )(GridClickEvent&); +typedef void (wxEvtHandler::*GridColumnResizeEventFunction)(GridColumnResizeEvent&); +typedef void (wxEvtHandler::*GridRangeSelectEventFunction )(GridRangeSelectEvent&); + +#define GridClickEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) + +#define GridColumnResizeEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) + +#define GridRangeSelectEventHandler(func) \ + (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func) //------------------------------------------------------------------------------------------------------------ class Grid; +wxColor getColorSelectionGradientFrom(); +wxColor getColorSelectionGradientTo(); -class GridDataView +void clearArea(wxDC& dc, const wxRect& rect, const wxColor& col); + +class GridData { public: - virtual ~GridDataView() {} + virtual ~GridData() {} + + virtual size_t getRowCount() const = 0; //if there are multiple grid components, only the first one will be polled for row count! - virtual wxString getValue(int row, int col) = 0; - virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, int row, int col, bool selected); //default implementation + //grid area + virtual wxString getValue(int row, ColumnType colType) const = 0; + virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, int row, bool enabled, bool selected, bool hasFocus); //default implementation + virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, int row, ColumnType colType); // + virtual size_t getBestSize(wxDC& dc, int row, ColumnType colType); //must correspond to renderCell()! + virtual wxString getToolTip(int row, ColumnType colType) const { return wxString(); } - virtual wxString getColumnLabel(int col) = 0; - virtual void renderColumnLabel(Grid& tree, wxDC& dc, const wxRect& rect, int col, bool highlighted); //default implementation + //label area + virtual wxString getColumnLabel(ColumnType colType) const = 0; + virtual void renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted); //default implementation + virtual wxString getToolTip(ColumnType colType) const { return wxString(); } -protected: +protected: //optional helper routines static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle - static void drawCellBackground(wxDC& dc, const wxRect& rect, bool selected, bool enabled, bool hasFocus); - static void drawCellText (wxDC& dc, const wxRect& rect, const wxString& text, bool enabled); + static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, bool hasFocus, const wxColor& backgroundColor); + static void drawCellText (wxDC& dc, const wxRect& rect, const wxString& text, bool enabled, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); static wxRect drawColumnLabelBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle static void drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted); @@ -39,7 +118,6 @@ protected: }; - class Grid : public wxScrolledWindow { public: @@ -50,86 +128,200 @@ public: long style = wxTAB_TRAVERSAL | wxNO_BORDER, const wxString& name = wxPanelNameStr); - //------------ set grid data -------------------------------------------------- - void setRowCount(int rowCount); - int getRowCount() const; + size_t getRowCount() const; void setRowHeight(int height); - int getRowHeight(); - void setColumnConfig(const std::vector<int>& widths); //set column count + widths - std::vector<int> getColumnConfig() const; + //grid component := a grid is divided into multiple components each of which is essentially a set of connected columns + void setComponentCount(size_t count) { comp.resize(count); updateWindowSizes(); } + size_t getComponentCount() const { return comp.size(); } + + struct ColumnAttribute + { + ColumnAttribute(ColumnType type, int width, bool visible = true) : type_(type), width_(width), visible_(visible) {} + ColumnType type_; + int width_; //if negative, treat as proportional stretch! + bool visible_; + }; - void setDataProvider(const std::shared_ptr<GridDataView>& dataView) { dataView_ = dataView; } + void setColumnConfig(const std::vector<ColumnAttribute>& attr, size_t compPos = 0); //set column count + widths + std::vector<ColumnAttribute> getColumnConfig(size_t compPos = 0) const; + + void setDataProvider(const std::shared_ptr<GridData>& dataView, size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].dataView_ = dataView; } + /**/ GridData* getDataProvider(size_t compPos = 0) { return compPos < comp.size() ? comp[compPos].dataView_.get() : NULL; } + const GridData* getDataProvider(size_t compPos = 0) const { return compPos < comp.size() ? comp[compPos].dataView_.get() : NULL; } //----------------------------------------------------------------------------- - - void setColumnLabelHeight(int height); - void showRowLabel(bool visible); - void showScrollBars(bool horizontal, bool vertical); + void setColumnLabelHeight(int height); + void showRowLabel(bool visible); - std::vector<int> getSelectedRows() const; + void showScrollBars(bool horizontal, bool vertical); -private: - void onSizeEvent(wxEvent& evt) { updateWindowSizes(); } + std::vector<int> getSelectedRows(size_t compPos = 0) const; + void clearSelection(size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].selection.clear(); } + + void scrollDelta(int deltaX, int deltaY); //in scroll units - void updateWindowSizes(); + wxWindow& getCornerWin (); + wxWindow& getRowLabelWin(); + wxWindow& getColLabelWin(); + wxWindow& getMainWin (); - int getScrollUnitsTotalX() const; - int getScrollUnitsTotalY() const; + int getRowAtPos(int posY) const; //returns < 0 if column not found; absolute coordinates! + Opt<std::pair<ColumnType, size_t>> getColumnAtPos(int posX) const; //returns (column type, component pos) - int getPixelsPerScrollUnit() const; + wxRect getCellArea(int row, ColumnType colType, size_t compPos = 0) const; //returns empty rect if column not found; absolute coordinates! - void scrollDelta(int deltaX, int deltaY); //in scroll units + void enableColumnMove (bool value, size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].allowColumnMove = value; } + void enableColumnResize(bool value, size_t compPos = 0) { if (compPos < comp.size()) comp[compPos].allowColumnResize = value; } + + void setGridCursor(int row, size_t compPos = 0); //set + show + select cursor + std::pair<int, size_t> getGridCursor() const; //(row, component pos) row == -1 if none + + void scrollTo(int row); + + virtual void Refresh(bool eraseBackground = true, const wxRect* rect = NULL); + virtual bool Enable( bool enable = true) { Refresh(); return wxScrolledWindow::Enable(enable); } + void autoSizeColumns(size_t compPos = 0); + +private: + void onPaintEvent(wxPaintEvent& event); + void onEraseBackGround(wxEraseEvent& event) {} //[!] + void onSizeEvent(wxEvent& evt) { updateWindowSizes(); } + + void updateWindowSizes(bool updateScrollbar = true); void redirectRowLabelEvent(wxMouseEvent& event); #ifdef FFS_WIN virtual WXLRESULT MSWDefWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam); //support horizontal mouse wheel -void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh); //get rid of scrollbars, but preserve scrolling behavior! + void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh); //get rid of scrollbars, but preserve scrolling behavior! #endif - GridDataView* getDataProvider() { return dataView_.get(); } - - static const int defaultRowHeight = 20; - static const int defaultColLabelHeight = 25; - static const int columnBorderLeft = 4; //for left-aligned text - static const int columnLabelBorder = columnBorderLeft; - static const int columnMoveDelay = 5; //unit [pixel] (Explorer) - static const int columnMinWidth = 30; - static const int rowLabelBorder = 3; - static const int columnResizeTolerance = 5; //unit [pixel] - static const int mouseDragScrollIncrementY = 2; //in scroll units - static const int mouseDragScrollIncrementX = 1; // - - friend class GridDataView; + + int getBestColumnSize(size_t col, size_t compPos) const; //return -1 on error + + friend class GridData; class SubWindow; class CornerWin; class RowLabelWin; class ColLabelWin; class MainWin; + class Selection + { + public: + void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); } + + std::vector<int> get() const + { + std::vector<int> selection; + for (auto iter = rowSelectionValue.begin(); iter != rowSelectionValue.end(); ++iter) + if (*iter != 0) + selection.push_back(iter - rowSelectionValue.begin()); + return selection; + } + + void clear() { selectRange(0, rowSelectionValue.size(), false); } + + bool isSelected(size_t row) const { return row < rowSelectionValue.size() ? rowSelectionValue[row] != 0 : false; } + + void selectRange(int rowFrom, int rowTo, bool positive = true) //select [rowFrom, rowTo], very tolerant: trims and swaps if required! + { + int rowFirst = std::min(rowFrom, rowTo); + int rowLast = std::max(rowFrom, rowTo) + 1; + + numeric::restrict(rowFirst, 0, static_cast<int>(rowSelectionValue.size())); + numeric::restrict(rowLast, 0, static_cast<int>(rowSelectionValue.size())); + + std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive); + } + + private: + std::vector<char> rowSelectionValue; //effectively a vector<bool> of size "number of rows" + }; + + struct VisibleColumn + { + VisibleColumn(ColumnType type, int width) : type_(type), width_(width) {} + ColumnType type_; + int width_; //may be NEGATIVE => treat as proportional stretch! use getAbsoluteWidths() to evaluate!!! + }; + + struct Component + { + Component() : allowColumnMove(true), allowColumnResize(true) {} + + std::shared_ptr<GridData> dataView_; + Selection selection; + bool allowColumnMove; + bool allowColumnResize; + + 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*! + }; + + int getMinAbsoluteWidthTotal() const; //assigns minimum width to stretched columns + std::vector<std::vector<VisibleColumn>> getAbsoluteWidths() const; //evaluate negative widths as stretched absolute values! structure matches "comp" + + Opt<size_t> getAbsoluteWidth(size_t col, size_t compPos) const //resolve stretched columns + { + const auto& absWidth = getAbsoluteWidths(); + if (compPos < absWidth.size() && col < absWidth[compPos].size()) + return absWidth[compPos][col].width_; + return NoValue(); + } + + void setColWidth(size_t col, size_t compPos, int width) //width may be >= 0: absolute, or < 0: stretched + { + if (compPos < comp.size() && col < comp[compPos].visibleCols.size()) + comp[compPos].visibleCols[col].width_ = width; + } + + wxRect getColumnLabelArea(ColumnType colType, size_t compPos) const; //returns empty rect if column not found + + void selectRange(int rowFrom, int rowTo, size_t compPos, bool positive = true); //select range + notify event! + void clearSelectionAll(); //clear selection + notify event + + bool isSelected(int row, size_t compPos) const { return compPos < comp.size() ? comp[compPos].selection.isSelected(row) : false; } + + bool columnMoveAllowed (size_t compPos) const { return compPos < comp.size() ? comp[compPos].allowColumnMove : false; } + bool columnResizeAllowed(size_t compPos) const { return compPos < comp.size() ? comp[compPos].allowColumnResize : false; } + + struct ColAction + { + bool wantResize; //"!wantResize" means "move" or "single click" + size_t col; + size_t compPos; + }; + Opt<ColAction> clientPosToColumnAction(const wxPoint& pos) const; + void moveColumn(size_t colFrom, size_t colTo, size_t compPos); + int clientPosToMoveTargetColumn(const wxPoint& pos, size_t compPos) const; + + Opt<ColumnType> colToType(size_t col, size_t compPos) const; + + /* Visual layout: - ---------------------------- - |CornerWin | RowLabelWin | - |--------------------------- - |ColLabelWin | MainWin | - ---------------------------- + ------------------------------------------------ + |CornerWin | RowLabelWin: | + |-------------------------- Comp1 | Comp2 ... | row label and main window are vertically tiled into one or more "components" + |ColLabelWin | MainWin: | + ------------------------------------------------ */ CornerWin* cornerWin_; RowLabelWin* rowLabelWin_; ColLabelWin* colLabelWin_; MainWin* mainWin_; - std::shared_ptr<GridDataView> dataView_; + bool showScrollbarX; + bool showScrollbarY; - bool showScrollbarX; - bool showScrollbarY; + int colLabelHeight; + bool drawRowLabel; - int colLabelHeight; - int drawRowLabel; + std::vector<Component> comp; + size_t colSizeOld; //at the time of last Grid::Refresh() }; } - -#endif //WX_TREE_LIST_HEADER_8347021348317 +#endif //GENERIC_GRID_HEADER_83470213483173 diff --git a/wx+/image_tools.h b/wx+/image_tools.h index 687025d0..82fd88cc 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef IMAGE_TOOLS_HEADER_45782456427634254 diff --git a/wx+/mouse_move_dlg.cpp b/wx+/mouse_move_dlg.cpp index 7edaa5db..20e6d420 100644 --- a/wx+/mouse_move_dlg.cpp +++ b/wx+/mouse_move_dlg.cpp @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "mouse_move_dlg.h" diff --git a/wx+/mouse_move_dlg.h b/wx+/mouse_move_dlg.h index c97ef19c..142dac49 100644 --- a/wx+/mouse_move_dlg.h +++ b/wx+/mouse_move_dlg.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef MOUSEMOVEWINDOW_H_INCLUDED diff --git a/wx+/no_flicker.h b/wx+/no_flicker.h index 0dceba97..f745a12a 100644 --- a/wx+/no_flicker.h +++ b/wx+/no_flicker.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef NO_FLICKER_HEADER_893421590321532 @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef FFS_PRECOMPILED_HEADER diff --git a/wx+/rtl.h b/wx+/rtl.h new file mode 100644 index 00000000..b8844ca8 --- /dev/null +++ b/wx+/rtl.h @@ -0,0 +1,121 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef RTL_H_0183487180058718273432148 +#define RTL_H_0183487180058718273432148 + +#include <memory> +#include <wx/dcmemory.h> +#include <wx/dcmirror.h> +#include <wx/image.h> +#include <wx/icon.h> +#include <wx/app.h> + +namespace zen +{ +//functions supporting right-to-left GUI layout + +void drawBitmapRtlMirror(wxDC& dc, + const wxBitmap& image, + const wxRect& rect, + int alignment, + std::unique_ptr<wxBitmap>& buffer); //mirror image if layout is RTL + fix some strange wxDC::Blit bug on RTL + +void drawBitmapRtlNoMirror(wxDC& dc, //wxDC::DrawLabel does already NOT mirror by default (but does a crappy job at it, surprise) + const wxBitmap& image, + const wxRect& rect, + int alignment, + std::unique_ptr<wxBitmap>& buffer); + +void drawIconRtlNoMirror(wxDC& dc, + const wxIcon& icon, //wxDC::DrawIcon DOES mirror by default + const wxPoint& pt, + std::unique_ptr<wxBitmap>& buffer); + +wxBitmap mirrorIfRtl(const wxBitmap& bmp); + + + + + + + + + + + +//---------------------- implementation ------------------------ +namespace +{ +template <class DrawImageFun> +void drawRtlImpl(wxDC& dc, const wxRect& rect, std::unique_ptr<wxBitmap>& buffer, bool doMirror, DrawImageFun draw) +{ + if (dc.GetLayoutDirection() == wxLayout_RightToLeft) + { + if (!buffer || buffer->GetWidth() != rect.width || buffer->GetHeight() < rect.height) //[!] since we do a mirror, width needs to match exactly! + buffer.reset(new wxBitmap(rect.width, rect.height)); + + wxMemoryDC memDc(*buffer); + memDc.Blit(wxPoint(0, 0), rect.GetSize(), &dc, rect.GetTopLeft()); //blit in: background is mirrored due to memDc, dc having different layout direction! + + if (!doMirror) + { + *buffer = wxBitmap(buffer->ConvertToImage().Mirror()); + memDc.SelectObject(*buffer); + } + + draw(memDc, wxRect(0, 0, rect.width, rect.height)); + //note: we cannot simply use memDc.SetLayoutDirection(wxLayout_RightToLeft) due to some strange 1 pixel bug! so it's a quadruple mirror! :> + + if (!doMirror) + { + *buffer = wxBitmap(buffer->ConvertToImage().Mirror()); + memDc.SelectObject(*buffer); + } + + dc.Blit(rect.GetTopLeft(), rect.GetSize(), &memDc, wxPoint(0, 0)); //blit out: mirror once again + } + else + draw(dc, rect); +} +} + + +inline +void drawBitmapRtlMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, std::unique_ptr<wxBitmap>& buffer) +{ + return drawRtlImpl(dc, rect, buffer, true, [&](wxDC& dc2, const wxRect& rect2) { dc2.DrawLabel(wxString(), image, rect2, alignment); }); +} + +inline +void drawBitmapRtlNoMirror(wxDC& dc, const wxBitmap& image, const wxRect& rect, int alignment, std::unique_ptr<wxBitmap>& buffer) +{ + if (dc.GetLayoutDirection() == wxLayout_RightToLeft) + if ((alignment & wxALIGN_CENTER_HORIZONTAL) == 0) //we still *do* want to mirror alignment! + alignment ^= wxALIGN_RIGHT; + static_assert(wxALIGN_LEFT == 0, "doh"); + return drawRtlImpl(dc, rect, buffer, false, [&](wxDC& dc2, const wxRect& rect2) { dc2.DrawLabel(wxString(), image, rect2, alignment); }); +} + +inline +void drawIconRtlNoMirror(wxDC& dc, const wxIcon& icon, const wxPoint& pt, std::unique_ptr<wxBitmap>& buffer) +{ + wxRect rect(pt.x, pt.y, icon.GetWidth(), icon.GetHeight()); + return drawRtlImpl(dc, rect, buffer, false, [&](wxDC& dc2, const wxRect& rect2) { dc2.DrawIcon(icon, rect2.GetTopLeft()); }); +} + + +inline +wxBitmap mirrorIfRtl(const wxBitmap& bmp) +{ + if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) + return bmp.ConvertToImage().Mirror(); + else + return bmp; +} +} + +#endif //RTL_H_0183487180058718273432148 diff --git a/wx+/serialize.h b/wx+/serialize.h index 020b8709..4a06c001 100644 --- a/wx+/serialize.h +++ b/wx+/serialize.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef SERIALIZE_H_INCLUDED diff --git a/wx+/shell_execute.h b/wx+/shell_execute.h index 00faf6ea..310fe88c 100644 --- a/wx+/shell_execute.h +++ b/wx+/shell_execute.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef EXECUTE_HEADER_23482134578134134 diff --git a/wx+/string_conv.h b/wx+/string_conv.h index 76249aca..65764b1f 100644 --- a/wx+/string_conv.h +++ b/wx+/string_conv.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef STRINGCONV_H_INCLUDED diff --git a/wx+/timespan.h b/wx+/timespan.h index 698171fa..241a029e 100644 --- a/wx+/timespan.h +++ b/wx+/timespan.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef WX_TIMESPAN_CTRL_HEADER_INCLUDED diff --git a/wx+/toggle_button.h b/wx+/toggle_button.h index 98a39e32..cce7f5b2 100644 --- a/wx+/toggle_button.h +++ b/wx+/toggle_button.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef TOGGLEBUTTON_H_INCLUDED diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp index 9c2587b2..7b4c51e0 100644 --- a/wx+/tooltip.cpp +++ b/wx+/tooltip.cpp @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #include "tooltip.h" @@ -9,6 +9,8 @@ #include <wx/sizer.h> #include <wx/statbmp.h> #include <wx/settings.h> +#include <wx/app.h> +#include <wx+/image_tools.h> using namespace zen; @@ -46,7 +48,7 @@ public: }; -Tooltip::Tooltip() : tipWindow(new PopupFrameGenerated(NULL)), lastBmp(NULL) +Tooltip::Tooltip() : tipWindow(new PopupFrameGenerated(NULL)) { #ifdef FFS_WIN //neither looks good nor works at all on Linux tipWindow->Disable(); //prevent window stealing focus! @@ -61,13 +63,12 @@ Tooltip::~Tooltip() } -void Tooltip::show(const wxString& text, wxPoint pos, const wxBitmap* bmp) +void Tooltip::show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp) { - if (bmp != lastBmp) - { - lastBmp = bmp; - tipWindow->m_bitmapLeft->SetBitmap(bmp == NULL ? wxNullBitmap : *bmp); - } + const wxBitmap& newBmp = bmp ? *bmp : wxNullBitmap; + + if (!isEqual(tipWindow->m_bitmapLeft->GetBitmap(), newBmp)) + tipWindow->m_bitmapLeft->SetBitmap(newBmp); if (text != tipWindow->m_staticTextMain->GetLabel()) { @@ -75,14 +76,16 @@ void Tooltip::show(const wxString& text, wxPoint pos, const wxBitmap* bmp) tipWindow->m_staticTextMain->Wrap(600); } -#ifdef FFS_LINUX - tipWindow->Fit(); //Alas Fit() seems to be somewhat broken => this needs to be called EVERY time inside show, not only if text or bmp change. -#endif + tipWindow->Fit(); //Linux: Fit() seems to be somewhat 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, 0); - if (pos != tipWindow->GetScreenPosition()) - tipWindow->SetSize(pos.x + 30, pos.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 which will hide the window, causing the window to be shown again via this method, etc. + //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(); diff --git a/wx+/tooltip.h b/wx+/tooltip.h index d9053e2c..7babe3c3 100644 --- a/wx+/tooltip.h +++ b/wx+/tooltip.h @@ -1,7 +1,7 @@ // ************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: http://www.gnu.org/licenses/gpl.html * -// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved * // ************************************************************************** #ifndef CUSTOMTOOLTIP_H_INCLUDED @@ -17,13 +17,12 @@ public: Tooltip(); ~Tooltip(); - void show(const wxString& text, wxPoint pos, const wxBitmap* bmp = NULL); //absolute screen coordinates + void show(const wxString& text, wxPoint mousePos, const wxBitmap* bmp = NULL); //absolute screen coordinates void hide(); private: class PopupFrameGenerated; PopupFrameGenerated* tipWindow; - const wxBitmap* lastBmp; //buffer last used bitmap pointer }; } |