diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:20:50 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:20:50 +0200 |
commit | 7e706cf64654aea466c059c307e5723e2423ed5d (patch) | |
tree | e85f0d28d7c81b6d21419fc38e1a654cca2212b1 /ui | |
parent | 5.5 (diff) | |
download | FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.tar.gz FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.tar.bz2 FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.zip |
5.6
Diffstat (limited to 'ui')
-rw-r--r-- | ui/batch_config.cpp | 4 | ||||
-rw-r--r-- | ui/batch_status_handler.cpp | 10 | ||||
-rw-r--r-- | ui/column_attr.h | 57 | ||||
-rw-r--r-- | ui/custom_grid.cpp | 448 | ||||
-rw-r--r-- | ui/custom_grid.h | 15 | ||||
-rw-r--r-- | ui/grid_view.cpp | 2 | ||||
-rw-r--r-- | ui/gui_generated.cpp | 40 | ||||
-rw-r--r-- | ui/gui_generated.h | 6 | ||||
-rw-r--r-- | ui/gui_status_handler.cpp | 49 | ||||
-rw-r--r-- | ui/main_dlg.cpp | 925 | ||||
-rw-r--r-- | ui/main_dlg.h | 50 | ||||
-rw-r--r-- | ui/search.cpp | 56 | ||||
-rw-r--r-- | ui/search.h | 4 | ||||
-rw-r--r-- | ui/tree_view.cpp | 10 | ||||
-rw-r--r-- | ui/tree_view.h | 2 | ||||
-rw-r--r-- | ui/triple_splitter.cpp | 230 | ||||
-rw-r--r-- | ui/triple_splitter.h | 87 |
17 files changed, 1277 insertions, 718 deletions
diff --git a/ui/batch_config.cpp b/ui/batch_config.cpp index 75033bf8..059996e8 100644 --- a/ui/batch_config.cpp +++ b/ui/batch_config.cpp @@ -61,7 +61,7 @@ private: void OnFilesDropped(FileDropEvent& event); void addFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, bool addFront = false); - void removeAddFolderPair(const int pos); + void removeAddFolderPair(int pos); void clearAddFolderPairs(); void updateGuiForFolderPair(); @@ -830,7 +830,7 @@ void BatchDialog::addFolderPair(const std::vector<zen::FolderPairEnh>& newPairs, } -void BatchDialog::removeAddFolderPair(const int pos) +void BatchDialog::removeAddFolderPair(int pos) { wxWindowUpdateLocker dummy(m_panelOverview); //avoid display distortion diff --git a/ui/batch_status_handler.cpp b/ui/batch_status_handler.cpp index c0958025..baab3ed3 100644 --- a/ui/batch_status_handler.cpp +++ b/ui/batch_status_handler.cpp @@ -127,7 +127,8 @@ BatchStatusHandler::BatchStatusHandler(bool showProgress, totalTime.Start(); //measure total time - //::wxSetEnv(L"logfile", logFile->getLogfileName()); + //if (logFile) + // ::wxSetEnv(L"logfile", utfCvrtTo<wxString>(logFile->getFilename())); -> requires a command line interpreter to take advantage of } @@ -306,6 +307,8 @@ void BatchStatusHandler::reportWarning(const std::wstring& warningMessage, bool& ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& errorMessage) { + errorLog.logMsg(errorMessage, TYPE_ERROR); //always, even for "retry" + switch (handleError_) { case xmlAccess::ON_ERROR_POPUP: @@ -321,26 +324,23 @@ ProcessCallback::Response BatchStatusHandler::reportError(const std::wstring& er case ReturnErrorDlg::BUTTON_IGNORE: if (ignoreNextErrors) //falsify only handleError_ = xmlAccess::ON_ERROR_IGNORE; - errorLog.logMsg(errorMessage, TYPE_ERROR); return ProcessCallback::IGNORE_ERROR; case ReturnErrorDlg::BUTTON_RETRY: return ProcessCallback::RETRY; case ReturnErrorDlg::BUTTON_CANCEL: - errorLog.logMsg(errorMessage, TYPE_ERROR); abortThisProcess(); + break; } } break; //used if last switch didn't find a match case xmlAccess::ON_ERROR_EXIT: //abort - errorLog.logMsg(errorMessage, TYPE_ERROR); abortThisProcess(); break; case xmlAccess::ON_ERROR_IGNORE: - errorLog.logMsg(errorMessage, TYPE_ERROR); return ProcessCallback::IGNORE_ERROR; } diff --git a/ui/column_attr.h b/ui/column_attr.h index 517961f4..ed98f403 100644 --- a/ui/column_attr.h +++ b/ui/column_attr.h @@ -25,11 +25,12 @@ enum ColumnTypeRim struct ColumnAttributeRim { - ColumnAttributeRim() : type_(COL_TYPE_DIRECTORY), width_(0), visible_(false) {} - ColumnAttributeRim(ColumnTypeRim type, int width, bool visible) : type_(type), width_(width), visible_(visible) {} + ColumnAttributeRim() : type_(COL_TYPE_DIRECTORY), offset_(0), stretch_(0), visible_(false) {} + ColumnAttributeRim(ColumnTypeRim type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} ColumnTypeRim type_; - int width_; //negative value stretches proportionally! + int offset_; + int stretch_; bool visible_; }; @@ -39,26 +40,26 @@ namespace std::vector<ColumnAttributeRim> getDefaultColumnAttributesLeft() { std::vector<ColumnAttributeRim> attr; - attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, 150, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, 0, true)); + attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, -280, 1, true)); //stretch to full width and substract sum of fixed size widths! + attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false)); return attr; } std::vector<ColumnAttributeRim> getDefaultColumnAttributesRight() { std::vector<ColumnAttributeRim> attr; - attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, false)); //already shown on left side - attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, 150, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, true)); - attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, false)); - attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_FULL_PATH, 250, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DIRECTORY, 200, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_REL_PATH, 200, 0, false)); //already shown on left side + attr.push_back(ColumnAttributeRim(COL_TYPE_FILENAME, -80, 1, true)); //stretch to full width and substract sum of fixed size widths! + attr.push_back(ColumnAttributeRim(COL_TYPE_SIZE, 80, 0, true)); + attr.push_back(ColumnAttributeRim(COL_TYPE_DATE, 112, 0, false)); + attr.push_back(ColumnAttributeRim(COL_TYPE_EXTENSION, 60, 0, false)); return attr; } } @@ -83,11 +84,12 @@ enum ColumnTypeNavi struct ColumnAttributeNavi { - ColumnAttributeNavi() : type_(COL_TYPE_NAVI_DIRECTORY), width_(0), visible_(false) {} - ColumnAttributeNavi(ColumnTypeNavi type, int width, bool visible) : type_(type), width_(width), visible_(visible) {} + ColumnAttributeNavi() : type_(COL_TYPE_NAVI_DIRECTORY), offset_(0), stretch_(0), visible_(false) {} + ColumnAttributeNavi(ColumnTypeNavi type, int offset, int stretch, bool visible) : type_(type), offset_(offset), stretch_(stretch), visible_(visible) {} ColumnTypeNavi type_; - int width_; //negative value stretches proportionally! + int offset_; + int stretch_; bool visible_; }; @@ -100,19 +102,8 @@ inline std::vector<ColumnAttributeNavi> getDefaultColumnAttributesNavi() { std::vector<ColumnAttributeNavi> attr; - - ColumnAttributeNavi newEntry; - - newEntry.type_ = COL_TYPE_NAVI_DIRECTORY; - newEntry.visible_ = true; - newEntry.width_ = -1; //stretch, old value: 280; - attr.push_back(newEntry); - - newEntry.type_ = COL_TYPE_NAVI_BYTES; - newEntry.visible_ = true; - newEntry.width_ = 60; //GTK needs a few pixels more - attr.push_back(newEntry); - + attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_DIRECTORY, -60, 1, true)); //stretch to full width and substract sum of fixed size widths! + attr.push_back(ColumnAttributeNavi(COL_TYPE_NAVI_BYTES, 60, 0, true)); //GTK needs a few pixels width more return attr; } } diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp index f340a819..0e27f21e 100644 --- a/ui/custom_grid.cpp +++ b/ui/custom_grid.cpp @@ -39,7 +39,8 @@ const wxColour COLOR_NOT_ACTIVE (228, 228, 228); //light grey const Zstring ICON_FILE_FOLDER = Zstr("folder"); const int CHECK_BOX_IMAGE = 12; //width of checkbox image -const int CHECK_BOX_WIDTH = CHECK_BOX_IMAGE + 2; //width of first block +const int CHECK_BOX_SPACE_LEFT = 2; +const int CHECK_BOX_WIDTH = CHECK_BOX_SPACE_LEFT + CHECK_BOX_IMAGE; //width of first block const size_t ROW_COUNT_NO_DATA = 10; @@ -58,9 +59,9 @@ class hierarchy: -void refreshCell(Grid& grid, size_t row, ColumnType colType, size_t compPos) +void refreshCell(Grid& grid, size_t row, ColumnType colType) { - wxRect cellArea = grid.getCellArea(row, colType, compPos); //returns empty rect if column not found; absolute coordinates! + wxRect cellArea = grid.getCellArea(row, colType); //returns empty rect if column not found; absolute coordinates! if (cellArea.height > 0) { cellArea.SetTopLeft(grid.CalcScrolledPosition(cellArea.GetTopLeft())); @@ -115,7 +116,7 @@ struct IconManager class GridDataBase : public GridData { public: - GridDataBase(Grid& grid) : grid_(grid) {} + GridDataBase(Grid& grid, const std::shared_ptr<const zen::GridView>& gridDataView) : grid_(grid), gridDataView_(gridDataView) {} void holdOwnership(const std::shared_ptr<GridEventManager>& evtMgr) { evtMgr_ = evtMgr; } @@ -123,18 +124,42 @@ protected: Grid& refGrid() { return grid_; } const Grid& refGrid() const { return grid_; } + const GridView* getGridDataView() const { return gridDataView_.get(); } + + const FileSystemObject* getRawData(size_t row) const + { + if (auto view = getGridDataView()) + return view->getObject(row); + return nullptr; + } + private: + virtual size_t getRowCount() const + { + if (gridDataView_) + { + if (gridDataView_->rowsTotal() == 0) + return ROW_COUNT_NO_DATA; + return gridDataView_->rowsOnView(); + } + else + return ROW_COUNT_NO_DATA; + + //return std::max(MIN_ROW_COUNT, gridDataView_ ? gridDataView_->rowsOnView() : 0); + } + std::shared_ptr<GridEventManager> evtMgr_; Grid& grid_; - + std::shared_ptr<const GridView> gridDataView_; }; + //######################################################################################################## template <SelectedSide side> class GridDataRim : public GridDataBase { public: - GridDataRim(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataBase(grid), gridDataView_(gridDataView), compPos_(compPos) {} + GridDataRim(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataBase(grid, gridDataView) {} void setIconManager(const std::shared_ptr<IconManager>& iconMgr) { iconMgr_ = iconMgr; } @@ -165,7 +190,7 @@ public: if (iconMgr_->iconBuffer.requestFileIcon(fileName)) { //do a *full* refresh for *every* failed load to update partial DC updates while scrolling - refreshCell(refGrid(), currentRow, static_cast<ColumnType>(COL_TYPE_FILENAME), compPos_); + refreshCell(refGrid(), currentRow, static_cast<ColumnType>(COL_TYPE_FILENAME)); setFailedLoad(currentRow, false); } else //not yet in buffer: mark for async. loading @@ -203,6 +228,15 @@ protected: //accessibility, support high-contrast schemes => work with user-defined background color! const auto backCol = getBackGroundColor(row); + auto incChannel = [](unsigned char c, int diff) { return static_cast<unsigned char>(std::max(0, std::min(255, c + diff))); }; + + auto getAdjustedColor = [&](int diff) + { + return wxColor(incChannel(backCol.Red (), diff), + incChannel(backCol.Green(), diff), + incChannel(backCol.Blue (), diff)); + }; + auto colorDist = [](const wxColor& lhs, const wxColor& rhs) //just some metric { return numeric::power<2>(static_cast<int>(lhs.Red ()) - static_cast<int>(rhs.Red ())) + @@ -210,15 +244,10 @@ protected: numeric::power<2>(static_cast<int>(lhs.Blue ()) - static_cast<int>(rhs.Blue ())); }; - const int levelDiff = 20; - const int level = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? - levelDiff : -levelDiff; //brighten or darken - - auto incChannel = [level](unsigned char c) { return static_cast<unsigned char>(std::max(0, std::min(255, c + level))); }; + const int signLevel = colorDist(backCol, *wxBLACK) < colorDist(backCol, *wxWHITE) ? 1 : -1; //brighten or darken - const wxColor backColAlt(incChannel(backCol.Red ()), - incChannel(backCol.Green()), - incChannel(backCol.Blue ())); + const wxColor colOutter = getAdjustedColor(signLevel * 20); + const wxColor colInner = getAdjustedColor(signLevel * 10); //clearArea(dc, rect, backColAlt); @@ -228,8 +257,8 @@ protected: wxRect rectLower = rect; rectLower.y += rectUpper.height; rectLower.height -= rectUpper.height; - dc.GradientFillLinear(rectUpper, backColAlt, getBackGroundColor(row), wxSOUTH); - dc.GradientFillLinear(rectLower, backColAlt, getBackGroundColor(row), wxNORTH); + dc.GradientFillLinear(rectUpper, colOutter, colInner, wxSOUTH); + dc.GradientFillLinear(rectLower, colOutter, colInner, wxNORTH); } else clearArea(dc, rect, getBackGroundColor(row)); @@ -259,8 +288,6 @@ protected: return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } - const FileSystemObject* getRawData(size_t row) const { return gridDataView_ ? gridDataView_->getObject(row) : nullptr; } - private: enum DisplayType { @@ -270,7 +297,6 @@ private: DISP_TYPE_INACTIVE, }; - DisplayType getRowDisplayType(size_t row) const { const FileSystemObject* fsObj = getRawData(row); @@ -306,20 +332,6 @@ private: return output; } - virtual size_t getRowCount() const - { - if (gridDataView_) - { - if (gridDataView_->rowsTotal() == 0) - return ROW_COUNT_NO_DATA; - return gridDataView_->rowsOnView(); - } - else - return ROW_COUNT_NO_DATA; - - //return std::max(MIN_ROW_COUNT, gridDataView_ ? gridDataView_->rowsOnView() : 0); - } - virtual wxString getValue(size_t row, ColumnType colType) const { if (const FileSystemObject* fsObj = getRawData(row)) @@ -348,8 +360,9 @@ private: if (!fsObj_.isEmpty<side>()) value = zen::toGuiString(fileObj.getFileSize<side>()); - //if (!fsObj_.isEmpty<side>()) -> test file id - // value = toGuiString(fileObj.getFileId<side>().second); + // -> test file id + //if (!fsObj_.isEmpty<side>()) + // value = toGuiString(fileObj.getFileId<side>().second) + L" " + toGuiString(fileObj.getFileId<side>().first); break; case COL_TYPE_DATE: //date if (!fsObj_.isEmpty<side>()) @@ -587,12 +600,12 @@ private: drawColumnLabelText(dc, rectInside, getColumnLabel(colType)); //draw sort marker - if (gridDataView_) + if (getGridDataView()) { - auto sortInfo = gridDataView_->getSortInfo(); + auto sortInfo = getGridDataView()->getSortInfo(); if (sortInfo) { - if (colType == static_cast<ColumnType>(sortInfo->type_) && (compPos_ == gridview::COMP_LEFT) == sortInfo->onLeft_) + if (colType == static_cast<ColumnType>(sortInfo->type_) && (side == LEFT_SIDE) == sortInfo->onLeft_) { const wxBitmap& marker = GlobalResources::getImage(sortInfo->ascending_ ? L"sortAscending" : L"sortDescending"); wxPoint markerBegin = rectInside.GetTopLeft() + wxPoint((rectInside.width - marker.GetWidth()) / 2, 0); @@ -639,7 +652,7 @@ private: const FileSystemObject* fsObj = getRawData(row); if (fsObj && !fsObj->isEmpty<side>()) { - toolTip = toWx(gridDataView_->getFolderPairCount() > 1 ? //gridDataView_ bound in this path + toolTip = toWx(getGridDataView() && getGridDataView()->getFolderPairCount() > 1 ? fsObj->getFullName<side>() : fsObj->getRelativeName<side>()); @@ -669,10 +682,8 @@ private: return toolTip; } - std::shared_ptr<const zen::GridView> gridDataView_; std::shared_ptr<IconManager> iconMgr_; //optional std::vector<char> failedLoads; //effectively a vector<bool> of size "number of rows" - const size_t compPos_; std::unique_ptr<wxBitmap> buffer; //avoid costs of recreating this temporal variable }; @@ -680,7 +691,7 @@ private: class GridDataLeft : public GridDataRim<LEFT_SIDE> { public: - GridDataLeft(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataRim<LEFT_SIDE>(gridDataView, grid, compPos) {} + GridDataLeft(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataRim<LEFT_SIDE>(gridDataView, grid) {} void setNavigationMarker(std::vector<const HierarchyObject*>&& markedFiles, std::vector<const HierarchyObject*>&& markedContainer) @@ -754,20 +765,17 @@ private: class GridDataRight : public GridDataRim<RIGHT_SIDE> { public: - GridDataRight(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataRim<RIGHT_SIDE>(gridDataView, grid, compPos) {} + GridDataRight(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : GridDataRim<RIGHT_SIDE>(gridDataView, grid) {} }; - - //######################################################################################################## class GridDataMiddle : public GridDataBase { public: GridDataMiddle(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) : - GridDataBase(grid), - gridDataView_(gridDataView), + GridDataBase(grid, gridDataView), showSyncAction_(true) {} void onSelectBegin(const wxPoint& clientPos, size_t row, ColumnType colType) @@ -775,14 +783,14 @@ public: if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE && row < refGrid().getRowCount()) { - refGrid().clearSelection(gridview::COMP_MIDDLE); + refGrid().clearSelection(); dragSelection.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row))); } } void onSelectEnd(size_t rowFrom, size_t rowTo) //we cannot reuse row from "onSelectBegin": rowFrom and rowTo may be different if user is holding shift { - refGrid().clearSelection(gridview::COMP_MIDDLE); + refGrid().clearSelection(); //issue custom event if (dragSelection) @@ -826,7 +834,7 @@ public: } } - void onMouseMovement(const wxPoint& clientPos, size_t row, ColumnType colType, size_t compPos) + void onMouseMovement(const wxPoint& clientPos, size_t row, ColumnType colType) { //manage block highlighting and custom tooltip if (dragSelection) @@ -835,13 +843,13 @@ public: } else { - if (compPos == gridview::COMP_MIDDLE && static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE) + if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE) { if (highlight) //refresh old highlight - refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); + refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); highlight.reset(new std::pair<size_t, BlockPosition>(row, mousePosToBlock(clientPos, row))); - refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); + refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); //show custom tooltip showToolTip(row, refGrid().getMainWin().ClientToScreen(clientPos)); @@ -855,7 +863,7 @@ public: { if (highlight) { - refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); + refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); highlight.reset(); } @@ -865,8 +873,6 @@ public: void showSyncAction(bool value) { showSyncAction_ = value; } private: - virtual size_t getRowCount() const { return 0; /*if there are multiple grid components, only the first one will be polled for row count!*/ } - virtual wxString getValue(size_t row, ColumnType colType) const { if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE) @@ -891,11 +897,15 @@ private: { if (const FileSystemObject* fsObj = getRawData(row)) { - wxRect rectInside = drawCellBorder(dc, rect); + //wxRect rectInside = drawCellBorder(dc, rect); + wxRect rectInside = rect; + + rectInside.width -= CHECK_BOX_SPACE_LEFT; + rectInside.x += CHECK_BOX_SPACE_LEFT; //draw checkbox wxRect checkBoxArea = rectInside; - checkBoxArea.SetWidth(CHECK_BOX_WIDTH); + checkBoxArea.SetWidth(CHECK_BOX_IMAGE); const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->first : false; const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->second : BLOCKPOS_CHECK_BOX; @@ -905,8 +915,8 @@ private: else //default drawBitmapRtlMirror(dc, GlobalResources::getImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), checkBoxArea, wxALIGN_CENTER, buffer); - rectInside.width -= CHECK_BOX_WIDTH; - rectInside.x += CHECK_BOX_WIDTH; + rectInside.width -= CHECK_BOX_IMAGE; + rectInside.x += CHECK_BOX_IMAGE; //synchronization preview if (showSyncAction_) @@ -964,8 +974,6 @@ private: } } - const FileSystemObject* getRawData(size_t row) const { return gridDataView_ ? gridDataView_->getObject(row) : nullptr; } - wxColor getBackGroundColor(size_t row) const { if (const FileSystemObject* fsObj = getRawData(row)) @@ -1019,9 +1027,9 @@ private: case FILE_EQUAL: break; //usually white case FILE_CONFLICT: + case FILE_DIFFERENT_METADATA: //= sub-category of equal, but hint via background that sync direction follows conflict-setting return COLOR_YELLOW; - case FILE_DIFFERENT_METADATA: - return COLOR_YELLOW_LIGHT; + //return COLOR_YELLOW_LIGHT; } } } @@ -1042,7 +1050,7 @@ private: { const int absX = refGrid().CalcUnscrolledPosition(clientPos).x; - const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); //returns empty rect if column not found; absolute coordinates! + const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE)); //returns empty rect if column not found; absolute coordinates! if (rect.width > CHECK_BOX_WIDTH && rect.height > 0) { const FileSystemObject* const fsObj = getRawData(row); @@ -1136,9 +1144,8 @@ private: case FILE_DIFFERENT: return L"different"; case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal return L"equal"; - case FILE_DIFFERENT_METADATA: - return L"conflict"; case FILE_CONFLICT: return L"conflict"; } @@ -1155,7 +1162,6 @@ private: virtual wxString getToolTip(ColumnType colType) const { return showSyncAction_ ? _("Action") : _("Category"); } - std::shared_ptr<const zen::GridView> gridDataView_; bool showSyncAction_; std::unique_ptr<std::pair<size_t, BlockPosition>> highlight; //(row, block) current mouse highlight std::unique_ptr<std::pair<size_t, BlockPosition>> dragSelection; //(row, block) @@ -1165,58 +1171,108 @@ private: //######################################################################################################## +const wxEventType EVENT_ALIGN_SCROLLBARS = wxNewEventType(); + class GridEventManager : private wxEvtHandler { public: - GridEventManager(Grid& grid, + GridEventManager(Grid& gridL, + Grid& gridC, + Grid& gridR, GridDataLeft& provLeft, GridDataMiddle& provMiddle, - GridDataRight& provRight) : grid_(grid), provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight) + GridDataRight& provRight) : + gridL_(gridL), gridC_(gridC), gridR_(gridR), + provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight) { - grid_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumn), nullptr, this); + gridL_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnL), nullptr, this); + gridR_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumnR), nullptr, this); + + gridL_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDownL), nullptr, this); + gridC_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDownC), nullptr, this); + gridR_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDownR), nullptr, this); + + gridC_.getMainWin().Connect(wxEVT_MOTION, wxMouseEventHandler(GridEventManager::onCenterMouseMovement), nullptr, this); + gridC_.getMainWin().Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(GridEventManager::onCenterMouseLeave ), nullptr, this); + + gridC_.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridEventManager::onCenterSelectBegin), nullptr, this); + gridC_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onCenterSelectEnd ), nullptr, this); + + //clear selection of other grid when selecting on + gridL_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionL), nullptr, this); + gridR_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onGridSelectionR), nullptr, this); + + //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead: + gridL_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridL), NULL, this); + gridC_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridC), NULL, this); + gridR_.getMainWin().Connect(wxEVT_PAINT, wxEventHandler(GridEventManager::onPaintGridR), NULL, this); + + gridL_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + gridL_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessL), NULL, this); + + gridR_.Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + gridR_.Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(GridEventManager::onGridAccessR), NULL, this); + + Connect(EVENT_ALIGN_SCROLLBARS, wxEventHandler(GridEventManager::onAlignScrollBars), NULL, this); + } - grid_.getMainWin().Connect(wxEVT_MOTION, wxMouseEventHandler(GridEventManager::onMouseMovement), nullptr, this); - grid_.getMainWin().Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(GridEventManager::onMouseLeave ), nullptr, this); - grid_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDown ), nullptr, this); +private: + void onCenterSelectBegin(GridClickEvent& event) + { - grid_.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridEventManager::onSelectBegin), nullptr, this); - grid_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onSelectEnd ), nullptr, this); + provMiddle_.onSelectBegin(event.GetPosition(), event.row_, event.colType_); + event.Skip(); } -private: - void onMouseMovement(wxMouseEvent& event) + void onCenterSelectEnd(GridRangeSelectEvent& event) { - const wxPoint& topLeftAbs = grid_.CalcUnscrolledPosition(event.GetPosition()); - const int row = grid_.getRowAtPos(topLeftAbs.y); //returns < 0 if column not found; absolute coordinates! - if (auto colInfo = grid_.getColumnAtPos(topLeftAbs.x)) //(column type, component position) + if (event.positive_) //we do NOT want to react on GridRangeSelectEvent() within Grid::clearSelectionAll() directly following right mouse click! + provMiddle_.onSelectEnd(event.rowFrom_, event.rowTo_); + event.Skip(); + } + + void onCenterMouseMovement(wxMouseEvent& event) + { + const wxPoint& topLeftAbs = gridC_.CalcUnscrolledPosition(event.GetPosition()); + const int row = gridC_.getRowAtPos(topLeftAbs.y); //returns < 0 if column not found; absolute coordinates! + if (auto colInfo = gridC_.getColumnAtPos(topLeftAbs.x)) //(column type, component position) { //redirect mouse movement to middle grid component - provMiddle_.onMouseMovement(event.GetPosition(), row, colInfo->first, colInfo->second); + provMiddle_.onMouseMovement(event.GetPosition(), row, colInfo->first); } event.Skip(); } - void onMouseLeave(wxMouseEvent& event) + void onCenterMouseLeave(wxMouseEvent& event) { provMiddle_.onMouseLeave(); event.Skip(); } - void onSelectBegin(GridClickEvent& event) - { - if (event.compPos_ == gridview::COMP_MIDDLE) - provMiddle_.onSelectBegin(event.GetPosition(), event.row_, event.colType_); - event.Skip(); - } + void onGridSelectionL(GridRangeSelectEvent& event) { onGridSelection(gridL_, gridR_); event.Skip(); } + void onGridSelectionR(GridRangeSelectEvent& event) { onGridSelection(gridR_, gridL_); event.Skip(); } - void onSelectEnd(GridRangeSelectEvent& event) + void onGridSelection(const Grid& grid, Grid& other) { - if (event.compPos_ == gridview::COMP_MIDDLE) - provMiddle_.onSelectEnd(event.rowFrom_, event.rowTo_); - event.Skip(); + if (!wxGetKeyState(WXK_CONTROL)) //clear other grid unless user is holding CTRL + other.clearSelection(); } - void onKeyDown(wxKeyEvent& event) + void onKeyDownL(wxKeyEvent& event) { onKeyDown(event, gridL_); } + void onKeyDownC(wxKeyEvent& event) { onKeyDown(event, gridC_); } + void onKeyDownR(wxKeyEvent& event) { onKeyDown(event, gridR_); } + + void onKeyDown(wxKeyEvent& event, const Grid& grid) { int keyCode = event.GetKeyCode(); if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) @@ -1233,7 +1289,7 @@ private: //skip middle component when navigating via keyboard - const auto row = grid_.getGridCursor().first; + const auto row = grid.getGridCursor().first; if (event.ShiftDown()) ; @@ -1244,47 +1300,119 @@ private: { case WXK_LEFT: case WXK_NUMPAD_LEFT: - grid_.setGridCursor(row, gridview::COMP_LEFT); + gridL_.setGridCursor(row); + gridL_.SetFocus(); return; //swallow event case WXK_RIGHT: case WXK_NUMPAD_RIGHT: - grid_.setGridCursor(row, gridview::COMP_RIGHT); + gridR_.setGridCursor(row); + gridR_.SetFocus(); return; //swallow event } event.Skip(); } - void onResizeColumn(GridColumnResizeEvent& event) + void onResizeColumnL(GridColumnResizeEvent& event) { resizeOtherSide(gridL_, gridR_, event.colType_, event.offset_); } + void onResizeColumnR(GridColumnResizeEvent& event) { resizeOtherSide(gridR_, gridL_, event.colType_, event.offset_); } + + void resizeOtherSide(const Grid& src, Grid& trg, ColumnType type, ptrdiff_t offset) { - auto resizeOtherSide = [&](size_t compPosOther) + //find stretch factor of resized column: type is unique due to makeConsistent()! + std::vector<Grid::ColumnAttribute> cfgSrc = src.getColumnConfig(); + auto iter = std::find_if(cfgSrc.begin(), cfgSrc.end(), [&](Grid::ColumnAttribute& ca) { return ca.type_ == type; }); + if (iter == cfgSrc.end()) + return; + const ptrdiff_t stretchSrc = iter->stretch_; + + //we do not propagate resizings on stretched columns to the other side: awkward user experience + if (stretchSrc > 0) + return; + + //apply resized offset to other side, but only if stretch factors match! + std::vector<Grid::ColumnAttribute> cfgTrg = trg.getColumnConfig(); + std::for_each(cfgTrg.begin(), cfgTrg.end(), [&](Grid::ColumnAttribute& ca) { - std::vector<Grid::ColumnAttribute> colAttr = grid_.getColumnConfig(compPosOther); + if (ca.type_ == type && ca.stretch_ == stretchSrc) + ca.offset_ = offset; + }); + trg.setColumnConfig(cfgTrg); + } - std::for_each(colAttr.begin(), colAttr.end(), [&](Grid::ColumnAttribute& ca) - { - if (ca.type_ == event.colType_) - ca.width_ = event.width_; - }); + void onGridAccessL(wxEvent& event) { gridL_.SetFocus(); event.Skip(); } + void onGridAccessR(wxEvent& event) { gridR_.SetFocus(); event.Skip(); } + + void onPaintGridL(wxEvent& event) { onPaintGrid(gridL_); event.Skip(); } + void onPaintGridC(wxEvent& event) { onPaintGrid(gridC_); event.Skip(); } + void onPaintGridR(wxEvent& event) { onPaintGrid(gridR_); event.Skip(); } - grid_.setColumnConfig(colAttr, compPosOther); //set column count + widths + void onPaintGrid(const Grid& grid) + { + //align scroll positions of all three grids *synchronously* during paint event! (wxGTK has visible delay when this is done asynchronously, no delay on Windows) + + //determine lead grid + const Grid* lead = nullptr; + Grid* follow1 = nullptr; + Grid* follow2 = nullptr; + auto setGrids = [&](const Grid& l, Grid& f1, Grid& f2) { lead = &l; follow1 = &f1; follow2 = &f2; }; + + if (wxWindow::FindFocus() == &gridC_.getMainWin()) + setGrids(gridC_, gridL_, gridR_); + else if (wxWindow::FindFocus() == &gridR_.getMainWin()) + setGrids(gridR_, gridL_, gridC_); + else //default: left panel + setGrids(gridL_, gridC_, gridR_); + + //align other grids only while repainting the lead grid to avoid scrolling and updating a grid at the same time! + if (lead != &grid) return; + + auto scroll = [](Grid& target, int y) //support polling + { + //scroll vertically only - scrolling horizontally becomes annoying if left and right sides have different widths; + //e.g. h-scroll on left would be undone when scrolling vertically on right which doesn't have a h-scrollbar + int yOld = 0; + target.GetViewStart(nullptr, &yOld); + if (yOld != y) + target.Scroll(-1, y); }; + int y = 0; + lead->GetViewStart(nullptr, &y); + scroll(*follow1, y); + scroll(*follow2, y); + + //harmonize placement of horizontal scrollbar to avoid grids getting out of sync! + //since this affects the grid that is currently repainted as well, we do work asynchronously! + //avoids at least this problem: remaining graphics artifact when changing from Grid::SB_SHOW_ALWAYS to Grid::SB_SHOW_NEVER at location of old scrollbar (Windows only) + wxCommandEvent alignEvent(EVENT_ALIGN_SCROLLBARS); + AddPendingEvent(alignEvent); //waits until next idle event - may take up to a second if the app is busy on wxGTK! + } - switch (event.compPos_) + void onAlignScrollBars(wxEvent& event) + { + auto needsHorizontalScrollbars = [](Grid& grid) -> bool { - case gridview::COMP_LEFT: - resizeOtherSide(gridview::COMP_RIGHT); - break; - case gridview::COMP_MIDDLE: - break; - case gridview::COMP_RIGHT: - resizeOtherSide(gridview::COMP_LEFT); - break; - } + const wxWindow& mainWin = grid.getMainWin(); + return mainWin.GetVirtualSize().GetWidth() > mainWin.GetClientSize().GetWidth(); + //assuming Grid::updateWindowSizes() does its job well, this should suffice! + //CAVEAT: if horizontal and vertical scrollbar are circular dependent from each other + //(h-scrollbar is shown due to v-scrollbar consuming horizontal width, ect...) + //while in fact both are NOT needed, this special case results in a bogus need for scrollbars! + //see https://sourceforge.net/tracker/?func=detail&aid=3514183&group_id=234430&atid=1093083 + // => since we're outside the Grid abstraction, we should not duplicate code to handle this special case as it seems to be insignificant + }; + + Grid::ScrollBarStatus sbStatusX = needsHorizontalScrollbars(gridL_) || + needsHorizontalScrollbars(gridR_) ? + Grid::SB_SHOW_ALWAYS : Grid::SB_SHOW_NEVER; + gridL_.showScrollBars(sbStatusX, Grid::SB_SHOW_NEVER); + gridC_.showScrollBars(sbStatusX, Grid::SB_SHOW_NEVER); + gridR_.showScrollBars(sbStatusX, Grid::SB_SHOW_AUTOMATIC); } - Grid& grid_; + Grid& gridL_; + Grid& gridC_; + Grid& gridR_; GridDataLeft& provLeft_; GridDataMiddle& provMiddle_; GridDataRight& provRight_; @@ -1293,32 +1421,36 @@ private: //######################################################################################################## -void gridview::init(Grid& grid, const std::shared_ptr<const zen::GridView>& gridDataView) +void gridview::init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_ptr<const zen::GridView>& gridDataView) { - grid.setComponentCount(3); - - auto provLeft_ = std::make_shared<GridDataLeft >(gridDataView, grid, gridview::COMP_LEFT ); - auto provMiddle_ = std::make_shared<GridDataMiddle>(gridDataView, grid); - auto provRight_ = std::make_shared<GridDataRight >(gridDataView, grid, gridview::COMP_RIGHT); + auto provLeft_ = std::make_shared<GridDataLeft >(gridDataView, gridLeft); + auto provMiddle_ = std::make_shared<GridDataMiddle>(gridDataView, gridCenter); + auto provRight_ = std::make_shared<GridDataRight >(gridDataView, gridRight); - grid.setDataProvider(provLeft_ , gridview::COMP_LEFT); //data providers reference grid => - grid.setDataProvider(provMiddle_, gridview::COMP_MIDDLE); //ownership must belong *exclusively* to grid! - grid.setDataProvider(provRight_ , gridview::COMP_RIGHT); + gridLeft .setDataProvider(provLeft_); //data providers reference grid => + gridCenter.setDataProvider(provMiddle_); //ownership must belong *exclusively* to grid! + gridRight .setDataProvider(provRight_); - auto evtMgr = std::make_shared<GridEventManager>(grid, *provLeft_, *provMiddle_, *provRight_); + auto evtMgr = std::make_shared<GridEventManager>(gridLeft, gridCenter, gridRight, *provLeft_, *provMiddle_, *provRight_); provLeft_ ->holdOwnership(evtMgr); provMiddle_->holdOwnership(evtMgr); provRight_ ->holdOwnership(evtMgr); - grid.enableColumnMove (false, gridview::COMP_MIDDLE); - grid.enableColumnResize(false, gridview::COMP_MIDDLE); + gridCenter.enableColumnMove (false); + gridCenter.enableColumnResize(false); - std::vector<Grid::ColumnAttribute> attribMiddle; - attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5)); - attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), 60)); - attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5)); + gridCenter.showRowLabel(false); + gridRight .showRowLabel(false); - grid.setColumnConfig(attribMiddle, gridview::COMP_MIDDLE); + //gridLeft .showScrollBars(Grid::SB_SHOW_AUTOMATIC, Grid::SB_SHOW_NEVER); -> redundant: configuration happens in GridEventManager::onAlignScrollBars() + //gridCenter.showScrollBars(Grid::SB_SHOW_NEVER, Grid::SB_SHOW_NEVER); + + gridCenter.SetSize(60 /*+ 2 * 5*/, -1); + std::vector<Grid::ColumnAttribute> attribMiddle; + //attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5, 0, true)); + attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), 60, 0, true)); + //attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5, 0, true)); + gridCenter.setColumnConfig(attribMiddle); } @@ -1348,7 +1480,7 @@ std::vector<Grid::ColumnAttribute> gridview::convertConfig(const std::vector<Col std::vector<Grid::ColumnAttribute> output; std::transform(attribClean.begin(), attribClean.end(), std::back_inserter(output), - [&](const ColumnAttributeRim& a) { return Grid::ColumnAttribute(static_cast<ColumnType>(a.type_), a.width_, a.visible_); }); + [&](const ColumnAttributeRim& ca) { return Grid::ColumnAttribute(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return output; } @@ -1359,7 +1491,7 @@ std::vector<ColumnAttributeRim> gridview::convertConfig(const std::vector<Grid:: std::vector<ColumnAttributeRim> output; std::transform(attribs.begin(), attribs.end(), std::back_inserter(output), - [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeRim(static_cast<ColumnTypeRim>(ca.type_), ca.width_, ca.visible_); }); + [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeRim(static_cast<ColumnTypeRim>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return makeConsistent(output); } @@ -1392,13 +1524,14 @@ private: }; } -void gridview::setupIcons(Grid& grid, bool show, IconBuffer::IconSize sz) +void gridview::setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz) { - auto* provLeft = dynamic_cast<GridDataLeft*>(grid.getDataProvider(gridview::COMP_LEFT)); - auto* provRight = dynamic_cast<GridDataRight*>(grid.getDataProvider(gridview::COMP_RIGHT)); + auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft .getDataProvider()); + auto* provRight = dynamic_cast<GridDataRight*>(gridRight.getDataProvider()); if (provLeft && provRight) { + int newRowHeight = 0; if (show) { auto iconMgr = std::make_shared<IconManager>(sz); @@ -1406,44 +1539,53 @@ void gridview::setupIcons(Grid& grid, bool show, IconBuffer::IconSize sz) provLeft ->setIconManager(iconMgr); provRight->setIconManager(iconMgr); - grid.setRowHeight(iconMgr->iconBuffer.getSize() + 1); //+ 1 for line between rows + newRowHeight = iconMgr->iconBuffer.getSize() + 1; //+ 1 for line between rows } else { provLeft ->setIconManager(nullptr); provRight->setIconManager(nullptr); - grid.setRowHeight(IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 1); //+ 1 for line between rows + newRowHeight = IconBuffer(IconBuffer::SIZE_SMALL).getSize() + 1; //+ 1 for line between rows } - grid.Refresh(); + gridLeft .setRowHeight(newRowHeight); + gridCenter.setRowHeight(newRowHeight); + gridRight .setRowHeight(newRowHeight); } else assert(false); } -void gridview::clearSelection(Grid& grid) +void gridview::clearSelection(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) +{ + gridLeft .clearSelection(); + gridCenter.clearSelection(); + gridRight .clearSelection(); +} + +void gridview::refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight) { - grid.clearSelection(gridview::COMP_LEFT); - grid.clearSelection(gridview::COMP_MIDDLE); - grid.clearSelection(gridview::COMP_RIGHT); + gridLeft .Refresh(); + gridCenter.Refresh(); + gridRight .Refresh(); } -void gridview::setNavigationMarker(Grid& grid, +void gridview::setNavigationMarker(Grid& gridLeft, std::vector<const HierarchyObject*>&& markedFiles, std::vector<const HierarchyObject*>&& markedContainer) { - if (auto* provLeft = dynamic_cast<GridDataLeft*>(grid.getDataProvider(gridview::COMP_LEFT))) + if (auto* provLeft = dynamic_cast<GridDataLeft*>(gridLeft.getDataProvider())) provLeft->setNavigationMarker(std::move(markedFiles), std::move(markedContainer)); else assert(false); - grid.Refresh(); + gridLeft.Refresh(); } -void gridview::showSyncAction(Grid& grid, bool value) +void gridview::showSyncAction(Grid& gridCenter, bool value) { - if (auto* provMiddle = dynamic_cast<GridDataMiddle*>(grid.getDataProvider(gridview::COMP_MIDDLE))) + if (auto* provMiddle = dynamic_cast<GridDataMiddle*>(gridCenter.getDataProvider())) provMiddle->showSyncAction(value); else assert(false); @@ -1503,9 +1645,9 @@ wxBitmap zen::getCmpResultImage(CompareFilesResult cmpResult) case FILE_DIFFERENT: return GlobalResources::getImage(L"differentSmall"); case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal return GlobalResources::getImage(L"equalSmall"); case FILE_CONFLICT: - case FILE_DIFFERENT_METADATA: return GlobalResources::getImage(L"conflictSmall"); } return wxNullBitmap; diff --git a/ui/custom_grid.h b/ui/custom_grid.h index 58a421e6..98092ade 100644 --- a/ui/custom_grid.h +++ b/ui/custom_grid.h @@ -17,23 +17,20 @@ namespace zen //setup grid to show grid view within three components: namespace gridview { -static const size_t COMP_LEFT = 0; -static const size_t COMP_MIDDLE = 1; -static const size_t COMP_RIGHT = 2; - -void init(Grid& grid, const std::shared_ptr<const GridView>& gridDataView); +void init(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, const std::shared_ptr<const GridView>& gridDataView); std::vector<Grid::ColumnAttribute> convertConfig(const std::vector<ColumnAttributeRim>& attribs); //+ make consistent std::vector<ColumnAttributeRim> convertConfig(const std::vector<Grid::ColumnAttribute>& attribs); // -void showSyncAction(Grid& grid, bool value); +void showSyncAction(Grid& gridCenter, bool value); -void setupIcons(Grid& grid, bool show, IconBuffer::IconSize sz); +void setupIcons(Grid& gridLeft, Grid& gridCenter, Grid& gridRight, bool show, IconBuffer::IconSize sz); -void clearSelection(Grid& grid); //clear all components +void clearSelection(Grid& gridLeft, Grid& gridCenter, Grid& gridRight); //clear all components +void refresh(Grid& gridLeft, Grid& gridCenter, Grid& gridRight); //mark rows selected in navigation/compressed tree and navigate to leading object -void setNavigationMarker(Grid& grid, +void setNavigationMarker(Grid& gridLeft, std::vector<const HierarchyObject*>&& markedFiles, //mark files/symlinks directly within a container std::vector<const HierarchyObject*>&& markedContainer); //mark full container including child-objects } diff --git a/ui/grid_view.cpp b/ui/grid_view.cpp index 2162394f..8764e233 100644 --- a/ui/grid_view.cpp +++ b/ui/grid_view.cpp @@ -161,11 +161,11 @@ GridView::StatusCmpResult GridView::updateCmpResult(bool hideFiltered, //maps so if (!differentFilesActive) return false; break; case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal output.existsEqual = true; if (!equalFilesActive) return false; break; case FILE_CONFLICT: - case FILE_DIFFERENT_METADATA: //no extra button on screen output.existsConflict = true; if (!conflictFilesActive) return false; break; diff --git a/ui/gui_generated.cpp b/ui/gui_generated.cpp index c0f83cec..1f58d6a5 100644 --- a/ui/gui_generated.cpp +++ b/ui/gui_generated.cpp @@ -359,9 +359,27 @@ MainDialogGenerated::MainDialogGenerated( wxWindow* parent, wxWindowID id, const wxBoxSizer* bSizer1711; bSizer1711 = new wxBoxSizer( wxVERTICAL ); - m_gridMain = new zen::Grid( m_panelCenter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); - m_gridMain->SetScrollRate( 5, 5 ); - bSizer1711->Add( m_gridMain, 1, wxEXPAND, 5 ); + m_splitterMain = new zen::TripleSplitter( m_panelCenter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + wxBoxSizer* bSizer1781; + bSizer1781 = new wxBoxSizer( wxHORIZONTAL ); + + m_gridMainL = new zen::Grid( m_splitterMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridMainL->SetScrollRate( 5, 5 ); + bSizer1781->Add( m_gridMainL, 1, wxEXPAND, 5 ); + + m_gridMainC = new zen::Grid( m_splitterMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridMainC->SetScrollRate( 5, 5 ); + bSizer1781->Add( m_gridMainC, 0, wxEXPAND, 5 ); + + m_gridMainR = new zen::Grid( m_splitterMain, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL ); + m_gridMainR->SetScrollRate( 5, 5 ); + bSizer1781->Add( m_gridMainR, 1, wxEXPAND, 5 ); + + + m_splitterMain->SetSizer( bSizer1781 ); + m_splitterMain->Layout(); + bSizer1781->Fit( m_splitterMain ); + bSizer1711->Add( m_splitterMain, 1, wxEXPAND, 5 ); m_panelStatusBar = new wxPanel( m_panelCenter, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxSTATIC_BORDER|wxTAB_TRAVERSAL ); wxBoxSizer* bSizer451; @@ -2676,22 +2694,25 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS wxBoxSizer* bSizer184; bSizer184 = new wxBoxSizer( wxHORIZONTAL ); + wxBoxSizer* bSizer178; + bSizer178 = new wxBoxSizer( wxVERTICAL ); + m_staticText83 = new wxStaticText( m_panel39, wxID_ANY, _("If you like FreeFileSync"), wxDefaultPosition, wxDefaultSize, 0 ); m_staticText83->Wrap( -1 ); m_staticText83->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), 70, 93, 92, false, wxEmptyString ) ); m_staticText83->SetForegroundColour( wxColour( 0, 0, 0 ) ); - bSizer184->Add( m_staticText83, 0, wxALL, 5 ); - - - bSizer184->Add( 0, 0, 1, wxEXPAND, 5 ); + bSizer178->Add( m_staticText83, 0, wxALL, 5 ); m_hyperlink3 = new wxHyperlinkCtrl( m_panel39, wxID_ANY, _("Donate with PayPal"), wxT("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=zhnmju123@gmx.de&lc=US¤cy_code=EUR"), wxDefaultPosition, wxDefaultSize, wxHL_DEFAULT_STYLE ); m_hyperlink3->SetFont( wxFont( 10, 70, 90, 92, true, wxEmptyString ) ); m_hyperlink3->SetBackgroundColour( wxColour( 221, 221, 255 ) ); m_hyperlink3->SetToolTip( _("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=zhnmju123@gmx.de&lc=US¤cy_code=EUR") ); - bSizer184->Add( m_hyperlink3, 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 ); + bSizer178->Add( m_hyperlink3, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxBOTTOM|wxRIGHT|wxLEFT, 5 ); + + + bSizer184->Add( bSizer178, 1, wxALIGN_CENTER_VERTICAL, 5 ); m_bitmapPaypal = new wxStaticBitmap( m_panel39, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0 ); m_bitmapPaypal->SetToolTip( _("Donate with PayPal") ); @@ -2699,9 +2720,6 @@ AboutDlgGenerated::AboutDlgGenerated( wxWindow* parent, wxWindowID id, const wxS bSizer184->Add( m_bitmapPaypal, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 ); - bSizer184->Add( 0, 0, 1, wxEXPAND, 5 ); - - m_panel39->SetSizer( bSizer184 ); m_panel39->Layout(); bSizer184->Fit( m_panel39 ); diff --git a/ui/gui_generated.h b/ui/gui_generated.h index fa388377..29ef091d 100644 --- a/ui/gui_generated.h +++ b/ui/gui_generated.h @@ -16,6 +16,7 @@ #include "folder_history_box.h" #include "../wx+/dir_picker.h" #include "../wx+/grid.h" +#include "triple_splitter.h" #include "../wx+/toggle_button.h" #include "exec_finished_box.h" #include "../wx+/graph.h" @@ -106,7 +107,10 @@ protected: wxBoxSizer* bSizerAddFolderPairs; zen::Grid* m_gridNavi; wxPanel* m_panelCenter; - zen::Grid* m_gridMain; + zen::TripleSplitter* m_splitterMain; + zen::Grid* m_gridMainL; + zen::Grid* m_gridMainC; + zen::Grid* m_gridMainR; wxPanel* m_panelStatusBar; wxBoxSizer* bSizerStatusLeftDirectories; wxStaticBitmap* m_bitmapSmallDirectoryLeft; diff --git a/ui/gui_status_handler.cpp b/ui/gui_status_handler.cpp index 57785403..b25e4249 100644 --- a/ui/gui_status_handler.cpp +++ b/ui/gui_status_handler.cpp @@ -279,37 +279,38 @@ void SyncStatusHandler::reportInfo(const std::wstring& text) ProcessCallback::Response SyncStatusHandler::reportError(const std::wstring& errorMessage) { + errorLog.logMsg(errorMessage, TYPE_ERROR); //always, even for "retry" + switch (handleError_) { case ON_GUIERROR_POPUP: - break; - case ON_GUIERROR_IGNORE: - errorLog.logMsg(errorMessage, TYPE_ERROR); - return ProcessCallback::IGNORE_ERROR; - } + { + PauseTimers dummy(syncStatusFrame); + forceUiRefresh(); - PauseTimers dummy(syncStatusFrame); - forceUiRefresh(); + bool ignoreNextErrors = false; + switch (showErrorDlg(parentDlg_, + ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, + errorMessage, + &ignoreNextErrors)) + { + case ReturnErrorDlg::BUTTON_IGNORE: + if (ignoreNextErrors) //falsify only + handleError_ = ON_GUIERROR_IGNORE; + return ProcessCallback::IGNORE_ERROR; - bool ignoreNextErrors = false; - switch (showErrorDlg(parentDlg_, - ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, - errorMessage, - &ignoreNextErrors)) - { - case ReturnErrorDlg::BUTTON_IGNORE: - if (ignoreNextErrors) //falsify only - handleError_ = ON_GUIERROR_IGNORE; - errorLog.logMsg(errorMessage, TYPE_ERROR); - return ProcessCallback::IGNORE_ERROR; + case ReturnErrorDlg::BUTTON_RETRY: + return ProcessCallback::RETRY; - case ReturnErrorDlg::BUTTON_RETRY: - return ProcessCallback::RETRY; + case ReturnErrorDlg::BUTTON_CANCEL: + abortThisProcess(); + break; + } + } + break; - case ReturnErrorDlg::BUTTON_CANCEL: - errorLog.logMsg(errorMessage, TYPE_ERROR); - abortThisProcess(); - break; + case ON_GUIERROR_IGNORE: + return ProcessCallback::IGNORE_ERROR; } assert(false); diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp index 03a94e33..7681cde2 100644 --- a/ui/main_dlg.cpp +++ b/ui/main_dlg.cpp @@ -36,6 +36,7 @@ #include "grid_view.h" #include "../lib/resources.h" #include <zen/file_handling.h> +#include <zen/serialize.h> #include <zen/file_id.h> #include <zen/recycler.h> #include "../lib/resolve_path.h" @@ -43,7 +44,6 @@ #include <wx+/toggle_button.h> #include "folder_pair.h" #include <wx+/rtl.h> -#include <wx+/serialize.h> #include "search.h" #include "../lib/help_provider.h" #include "batch_config.h" @@ -54,6 +54,7 @@ #include <wx+/image_tools.h> #include <wx+/no_flicker.h> #include <wx+/grid.h> +#include "../lib/error_log.h" using namespace zen; using namespace std::rel_ops; @@ -91,32 +92,18 @@ public: DirectoryNameMainImpl(MainDialog& mainDlg, wxWindow& dropWindow1, Grid& dropGrid, - size_t compPos, //accept left or right half only! wxDirPickerCtrl& dirPicker, FolderHistoryBox& dirName, wxStaticText& staticText) : DirectoryName(dropWindow1, dirPicker, dirName, &staticText, &dropGrid.getMainWin()), mainDlg_(mainDlg), - dropGrid_(dropGrid), - compPos_(compPos) {} + dropGrid_(dropGrid) {} virtual bool acceptDrop(const std::vector<wxString>& droppedFiles, const wxPoint& clientPos, const wxWindow& wnd) { if (droppedFiles.empty()) return false; - if (&wnd == &dropGrid_.getMainWin()) - { - const wxPoint absPos = dropGrid_.CalcUnscrolledPosition(clientPos); - - const Opt<std::pair<ColumnType, size_t>> colInfo = dropGrid_.Grid::getColumnAtPos(absPos.x); - const bool dropOnLeft = colInfo ? colInfo->second != gridview::COMP_RIGHT : false; - - if ((compPos_ == gridview::COMP_LEFT && !dropOnLeft) || //accept left or right half of m_gridMain only! - (compPos_ == gridview::COMP_RIGHT && dropOnLeft)) // - return false; - } - switch (xmlAccess::getMergeType(toZ(droppedFiles))) //throw() { case xmlAccess::MERGE_BATCH: @@ -140,7 +127,6 @@ private: MainDialog& mainDlg_; Grid& dropGrid_; - size_t compPos_; }; //------------------------------------------------------------------ @@ -240,15 +226,13 @@ public: //prepare drag & drop dirNameLeft(mainDialog, *mainDialog.m_panelTopLeft, - *mainDialog.m_gridMain, - gridview::COMP_LEFT, + *mainDialog.m_gridMainL, *mainDialog.m_dirPickerLeft, *mainDialog.m_directoryLeft, *mainDialog.m_staticTextFinalPathLeft), dirNameRight(mainDialog, *mainDialog.m_panelTopRight, - *mainDialog.m_gridMain, - gridview::COMP_RIGHT, + *mainDialog.m_gridMainR, *mainDialog.m_dirPickerRight, *mainDialog.m_directoryRight, *mainDialog.m_staticTextFinalPathRight) {} @@ -337,7 +321,7 @@ public: paneInfo.IsFloating()) return false; //prevent main dialog move - return true;; //allow dialog move + return true; //allow dialog move } private: @@ -347,7 +331,7 @@ private: //################################################################################################################################## -MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::XmlGlobalSettings& settings) : +MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, const xmlAccess::XmlGlobalSettings& globalSettings) : MainDialogGenerated(nullptr) { xmlAccess::XmlGuiConfig guiCfg; //structure to receive gui settings, already defaulted!! @@ -357,7 +341,7 @@ MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::Xml filenames = cfgFileNames; else //next: use last used selection { - filenames = settings.gui.lastUsedConfigFiles; //2. now try last used files + filenames = globalSettings.gui.lastUsedConfigFiles; //2. now try last used files //------------------------------------------------------------------------------------------ //check existence of all directories in parallel! @@ -403,7 +387,7 @@ MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::Xml const bool startComparisonImmediately = !cfgFileNames.empty() && loadCfgSuccess; init(guiCfg, - settings, + globalSettings, startComparisonImmediately); setLastUsedConfig(filenames, loadCfgSuccess ? guiCfg : xmlAccess::XmlGuiConfig()); //simulate changed config on parsing errors @@ -412,12 +396,12 @@ MainDialog::MainDialog(const std::vector<wxString>& cfgFileNames, xmlAccess::Xml MainDialog::MainDialog(const std::vector<wxString>& referenceFiles, const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) : MainDialogGenerated(nullptr) { init(guiCfg, - settings, + globalSettings, startComparison); setLastUsedConfig(referenceFiles, guiCfg); @@ -426,40 +410,44 @@ MainDialog::MainDialog(const std::vector<wxString>& referenceFiles, MainDialog::~MainDialog() { - wxWindowUpdateLocker dummy(this); - - writeGlobalSettings(); //set before saving last used config since "activeConfigFiles" will be replaced + try //save "GlobalSettings.xml" + { + xmlAccess::writeConfig(getGlobalCfgBeforeExit()); //throw FfsXmlError + } + catch (const xmlAccess::FfsXmlError& e) + { + wxMessageBox(e.toString().c_str(), _("Error"), wxOK | wxICON_ERROR, this); + } - //save "LastRun.ffs_gui" configuration - const xmlAccess::XmlGuiConfig guiCfg = getConfig(); - try + try //save "LastRun.ffs_gui" { - xmlAccess::writeConfig(guiCfg, toZ(lastRunConfigName())); - //setLastUsedConfig(lastRunConfigName(), guiCfg); -> may be removed!? + xmlAccess::writeConfig(getConfig(), toZ(lastRunConfigName())); //throw FfsXmlError } - //don't annoy users on read-only drives: no error checking should be fine since this is not a config the user explicitly wanted to save + //don't annoy users on read-only drives: it's enough to show a single error message when saving global config catch (const xmlAccess::FfsXmlError&) {} //important! event source wxTheApp is NOT dependent on this instance -> disconnect! wxTheApp->Disconnect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this); wxTheApp->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this); - //no need for wxEventHandler::Disconnect() here; event sources are components of this window and are destroyed, too - auiMgr.UnInit(); + + //no need for wxEventHandler::Disconnect() here; event sources are components of this window and are destroyed, too } void MainDialog::onQueryEndSession() { - writeGlobalSettings(); + try { xmlAccess::writeConfig(getGlobalCfgBeforeExit()); } + catch (const xmlAccess::FfsXmlError&) {} //we try our best do to something useful in this extreme situation - no reason to notify or even log errors here! + try { xmlAccess::writeConfig(getConfig(), toZ(lastRunConfigName())); } catch (const xmlAccess::FfsXmlError&) {} } void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison) { showSyncAction_ = false; @@ -470,6 +458,10 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, m_directoryLeft ->init(folderHistoryLeft); m_directoryRight->init(folderHistoryRight); + //setup sash: detach + reparent: + m_splitterMain->SetSizer(nullptr); //alas wxFormbuilder doesn't allow us to have child windows without a sizer, so we have to remove it here + m_splitterMain->setupWindows(m_gridMainL, m_gridMainC, m_gridMainR); + wxWindowUpdateLocker dummy(this); //avoid display distortion //---------------- support for dockable gui style -------------------------------- @@ -532,23 +524,30 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, //---------------------------------------------------------------------------------- //register context: quick variant selection - m_bpButtonCmpConfig ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnCompSettingsContext), nullptr, this); - m_bpButtonSyncConfig->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnSyncSettingsContext), nullptr, this); + m_bpButtonCmpConfig ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler (MainDialog::OnCompSettingsContext), nullptr, this); + m_bpButtonSyncConfig->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler (MainDialog::OnSyncSettingsContext), nullptr, this); m_bpButtonFilter ->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(MainDialog::OnGlobalFilterContext), nullptr, this); //sort grids - m_gridMain->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClick ), nullptr, this ); - m_gridMain->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContext), nullptr, this ); + m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickL ), nullptr, this ); + m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickC ), nullptr, this ); + m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickR ), nullptr, this ); + + m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextL ), nullptr, this ); + m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextC ), nullptr, this ); + m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextR ), nullptr, this ); //grid context menu - m_gridMain->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContext), nullptr, this); - m_gridNavi->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onNaviGridContext), nullptr, this); + m_gridMainL->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextL), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextC), nullptr, this); + m_gridMainR->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextR), nullptr, this); + m_gridNavi ->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onNaviGridContext ), nullptr, this); - m_gridMain->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClick), nullptr, this ); + m_gridMainL->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickL), nullptr, this ); + m_gridMainR->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickR), nullptr, this ); m_gridNavi->Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(MainDialog::onNaviSelection), nullptr, this); - globalSettings = &settings; gridDataView.reset(new zen::GridView); treeDataView.reset(new zen::TreeView); @@ -570,11 +569,11 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, initViewFilterButtons(); //init grid settings - gridview::init(*m_gridMain, gridDataView); + gridview::init(*m_gridMainL, *m_gridMainC, *m_gridMainR, gridDataView); treeview::init(*m_gridNavi, treeDataView); //initialize and load configuration - readGlobalSettings(); + setGlobalCfgOnInit(globalSettings); setConfig(guiCfg); //set icons for this dialog @@ -645,7 +644,10 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, }); //support for CTRL + C and DEL on grids - m_gridMain->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEvent), nullptr, this); + m_gridMainL->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventL), nullptr, this); + m_gridMainC->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventC), nullptr, this); + m_gridMainR->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventR), nullptr, this); + m_gridNavi->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onTreeButtonEvent), nullptr, this); //register global hotkeys (without explicit menu entry) @@ -674,8 +676,8 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, OnResizeStatisticsPanel(dummy3); // //event handler for manual (un-)checking of rows and setting of sync direction - m_gridMain->Connect(EVENT_GRID_CHECK_ROWS, CheckRowsEventHandler (MainDialog::onCheckRows), nullptr, this); - m_gridMain->Connect(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEventHandler(MainDialog::onSetSyncDirection), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_CHECK_ROWS, CheckRowsEventHandler (MainDialog::onCheckRows), nullptr, this); + m_gridMainC->Connect(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEventHandler(MainDialog::onSetSyncDirection), nullptr, this); //mainly to update row label sizes... updateGui(); @@ -740,41 +742,46 @@ void MainDialog::init(const xmlAccess::XmlGuiConfig& guiCfg, } -void MainDialog::readGlobalSettings() +void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings) { + globalCfg = globalSettings; + + setLanguage(globalSettings.programLanguage); + //set dialog size and position: test ALL parameters at once, since width/height are invalid if the window is minimized (eg x,y == -32000; height = 28, width = 160) //note: negative values for x and y are possible when using multiple monitors! - if (globalSettings->gui.dlgSize.GetWidth () > 0 && - globalSettings->gui.dlgSize.GetHeight() > 0 && - globalSettings->gui.dlgPos.x >= -3360 && - globalSettings->gui.dlgPos.y >= -200) - //wxDisplay::GetFromPoint(globalSettings->gui.dlgPos) != wxNOT_FOUND) //make sure upper left corner is in visible view -> not required - SetSize(wxRect(globalSettings->gui.dlgPos, globalSettings->gui.dlgSize)); + if (globalSettings.gui.dlgSize.GetWidth () > 0 && + globalSettings.gui.dlgSize.GetHeight() > 0 && + globalSettings.gui.dlgPos.x >= -3360 && + globalSettings.gui.dlgPos.y >= -200) + //wxDisplay::GetFromPoint(globalSettings.gui.dlgPos) != wxNOT_FOUND) //make sure upper left corner is in visible view -> not required + SetSize(wxRect(globalSettings.gui.dlgPos, globalSettings.gui.dlgSize)); else Centre(); - Maximize(globalSettings->gui.isMaximized); + Maximize(globalSettings.gui.isMaximized); //set column attributes - m_gridMain->setColumnConfig(gridview::convertConfig(globalSettings->gui.columnAttribLeft), gridview::COMP_LEFT); - m_gridMain->setColumnConfig(gridview::convertConfig(globalSettings->gui.columnAttribRight), gridview::COMP_RIGHT); + m_gridMainL ->setColumnConfig(gridview::convertConfig(globalSettings.gui.columnAttribLeft)); + m_gridMainR ->setColumnConfig(gridview::convertConfig(globalSettings.gui.columnAttribRight)); + m_splitterMain->setSashOffset(globalSettings.gui.sashOffset); - m_gridNavi->setColumnConfig(treeview::convertConfig(globalSettings->gui.columnAttribNavi)); - treeview::setShowPercentage(*m_gridNavi, globalSettings->gui.showPercentBar); + m_gridNavi->setColumnConfig(treeview::convertConfig(globalSettings.gui.columnAttribNavi)); + treeview::setShowPercentage(*m_gridNavi, globalSettings.gui.showPercentBar); - treeDataView->setSortDirection(globalSettings->gui.naviLastSortColumn, globalSettings->gui.naviLastSortAscending); + treeDataView->setSortDirection(globalSettings.gui.naviLastSortColumn, globalSettings.gui.naviLastSortAscending); //load list of last used configuration files - std::vector<wxString> cfgFileNames = globalSettings->gui.cfgFileHistory; + std::vector<wxString> cfgFileNames = globalSettings.gui.cfgFileHistory; cfgFileNames.push_back(lastRunConfigName()); //make sure <Last session> is always part of history list addFileToCfgHistory(cfgFileNames); //load list of last used folders - *folderHistoryLeft = FolderHistory(globalSettings->gui.folderHistoryLeft, globalSettings->gui.folderHistMax); - *folderHistoryRight = FolderHistory(globalSettings->gui.folderHistoryRight, globalSettings->gui.folderHistMax); + *folderHistoryLeft = FolderHistory(globalSettings.gui.folderHistoryLeft, globalSettings.gui.folderHistMax); + *folderHistoryRight = FolderHistory(globalSettings.gui.folderHistoryRight, globalSettings.gui.folderHistMax); //show/hide file icons - gridview::setupIcons(*m_gridMain, globalSettings->gui.showIcons, convert(globalSettings->gui.iconSize)); + gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalSettings.gui.showIcons, convert(globalSettings.gui.iconSize)); //------------------------------------------------------------------------------------------------ //wxAuiManager erroneously loads panel captions, we don't want that @@ -784,7 +791,7 @@ void MainDialog::readGlobalSettings() for (size_t i = 0; i < paneArray.size(); ++i) captionNameMap.push_back(std::make_pair(paneArray[i].caption, paneArray[i].name)); - auiMgr.LoadPerspective(globalSettings->gui.guiPerspectiveLast); + auiMgr.LoadPerspective(globalSettings.gui.guiPerspectiveLast); //restore original captions for (CaptionNameMapping::const_iterator i = captionNameMap.begin(); i != captionNameMap.end(); ++i) @@ -793,30 +800,38 @@ void MainDialog::readGlobalSettings() } -void MainDialog::writeGlobalSettings() +xmlAccess::XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit() { + Freeze(); //no need to Thaw() again!! + // wxWindowUpdateLocker dummy(this); + + xmlAccess::XmlGlobalSettings globalSettings = globalCfg; + + globalSettings.programLanguage = getLanguage(); + //write global settings to (global) variable stored in application instance if (IsIconized()) //we need to (reliably) retrieve non-iconized, non-maximized size and position Iconize(false); - globalSettings->gui.isMaximized = IsMaximized(); //evaluate AFTER uniconizing! + globalSettings.gui.isMaximized = IsMaximized(); //evaluate AFTER uniconizing! if (IsMaximized()) Maximize(false); - globalSettings->gui.dlgSize = GetSize(); - globalSettings->gui.dlgPos = GetPosition(); + globalSettings.gui.dlgSize = GetSize(); + globalSettings.gui.dlgPos = GetPosition(); //retrieve column attributes - globalSettings->gui.columnAttribLeft = gridview::convertConfig(m_gridMain->getColumnConfig(gridview::COMP_LEFT)); - globalSettings->gui.columnAttribRight = gridview::convertConfig(m_gridMain->getColumnConfig(gridview::COMP_RIGHT)); + globalSettings.gui.columnAttribLeft = gridview::convertConfig(m_gridMainL->getColumnConfig()); + globalSettings.gui.columnAttribRight = gridview::convertConfig(m_gridMainR->getColumnConfig()); + globalSettings.gui.sashOffset = m_splitterMain->getSashOffset(); - globalSettings->gui.columnAttribNavi = treeview::convertConfig(m_gridNavi->getColumnConfig()); - globalSettings->gui.showPercentBar = treeview::getShowPercentage(*m_gridNavi); + globalSettings.gui.columnAttribNavi = treeview::convertConfig(m_gridNavi->getColumnConfig()); + globalSettings.gui.showPercentBar = treeview::getShowPercentage(*m_gridNavi); - const auto sortInfo = treeDataView->getSortDirection(); - globalSettings->gui.naviLastSortColumn = sortInfo.first; - globalSettings->gui.naviLastSortAscending = sortInfo.second; + const std::pair<ColumnTypeNavi, bool> sortInfo = treeDataView->getSortDirection(); + globalSettings.gui.naviLastSortColumn = sortInfo.first; + globalSettings.gui.naviLastSortAscending = sortInfo.second; //write list of last used configuration files std::vector<wxString> cfgFileHistory; @@ -824,14 +839,16 @@ void MainDialog::writeGlobalSettings() if (auto clientString = dynamic_cast<wxClientDataString*>(m_listBoxHistory->GetClientObject(i))) cfgFileHistory.push_back(clientString->name_); - globalSettings->gui.cfgFileHistory = cfgFileHistory; - globalSettings->gui.lastUsedConfigFiles = activeConfigFiles; + globalSettings.gui.cfgFileHistory = cfgFileHistory; + globalSettings.gui.lastUsedConfigFiles = activeConfigFiles; //write list of last used folders - globalSettings->gui.folderHistoryLeft = folderHistoryLeft ->getList(); - globalSettings->gui.folderHistoryRight = folderHistoryRight->getList(); + globalSettings.gui.folderHistoryLeft = folderHistoryLeft ->getList(); + globalSettings.gui.folderHistoryRight = folderHistoryRight->getList(); + + globalSettings.gui.guiPerspectiveLast = auiMgr.SavePerspective(); - globalSettings->gui.guiPerspectiveLast = auiMgr.SavePerspective(); + return globalSettings; } @@ -877,16 +894,15 @@ void MainDialog::copySelectionToClipboard() { zxString clipboardString; //perf: wxString doesn't model exponential growth and so is out - auto addSelection = [&](size_t compPos) + auto addSelection = [&](const Grid& grid) { - auto prov = m_gridMain->getDataProvider(compPos); - if (prov) + if (auto prov = grid.getDataProvider()) { - std::vector<Grid::ColumnAttribute> colAttr = m_gridMain->getColumnConfig(compPos); + std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); if (!colAttr.empty()) { - const std::vector<size_t> selection = m_gridMain->getSelectedRows(compPos); + const std::vector<size_t> selection = grid.getSelectedRows(); std::for_each(selection.begin(), selection.end(), [&](size_t row) { @@ -903,8 +919,8 @@ void MainDialog::copySelectionToClipboard() } }; - addSelection(gridview::COMP_LEFT); - addSelection(gridview::COMP_RIGHT); + addSelection(*m_gridMainL); + addSelection(*m_gridMainR); //finally write to clipboard if (!clipboardString.empty()) @@ -920,17 +936,17 @@ std::vector<FileSystemObject*> MainDialog::getGridSelection(bool fromLeft, bool { std::set<size_t> selectedRows; - auto addSelection = [&](size_t compPos) + auto addSelection = [&](const Grid& grid) { - const std::vector<size_t>& sel = m_gridMain->getSelectedRows(compPos); + const std::vector<size_t>& sel = grid.getSelectedRows(); selectedRows.insert(sel.begin(), sel.end()); }; if (fromLeft) - addSelection(gridview::COMP_LEFT); + addSelection(*m_gridMainL); if (fromRight) - addSelection(gridview::COMP_RIGHT); + addSelection(*m_gridMainR); return gridDataView->getAllFileRef(selectedRows); } @@ -997,18 +1013,16 @@ public: mainDlg->enableAllElements(); } - virtual Response reportError(const std::wstring& errorMessage) + virtual Response reportError(const std::wstring& msg) { - if (abortRequested) - throw AbortDeleteProcess(); - if (ignoreErrors) return DeleteFilesHandler::IGNORE_ERROR; + updateGUI(); bool ignoreNextErrors = false; switch (showErrorDlg(mainDlg, ReturnErrorDlg::BUTTON_IGNORE | ReturnErrorDlg::BUTTON_RETRY | ReturnErrorDlg::BUTTON_CANCEL, - errorMessage, &ignoreNextErrors)) + msg, &ignoreNextErrors)) { case ReturnErrorDlg::BUTTON_IGNORE: ignoreErrors = ignoreNextErrors; @@ -1023,10 +1037,35 @@ public: return DeleteFilesHandler::IGNORE_ERROR; //dummy return value } + virtual void reportWarning(const std::wstring& msg, bool& warningActive) + { + if (!warningActive || ignoreErrors) + return; + + updateGUI(); + bool dontWarnAgain = false; + switch (showWarningDlg(mainDlg, ReturnWarningDlg::BUTTON_IGNORE | ReturnWarningDlg::BUTTON_CANCEL, msg, dontWarnAgain)) + { + case ReturnWarningDlg::BUTTON_SWITCH: + assert(false); + case ReturnWarningDlg::BUTTON_CANCEL: + throw AbortDeleteProcess(); + + case ReturnWarningDlg::BUTTON_IGNORE: + warningActive = !dontWarnAgain; + break; + } + } + virtual void notifyDeletion(const Zstring& currentObject) //called for each file/folder that has been deleted { ++deletionCount; + updateGUI(); + } +private: + void updateGUI() + { if (updateUiIsAllowed()) //test if specific time span between ui updates is over { mainDlg->setStatusInformation(replaceCpy(_P("Object deleted successfully!", "%x objects deleted successfully!", deletionCount), @@ -1038,7 +1077,7 @@ public: throw AbortDeleteProcess(); } -private: + //context: C callstack message loop => throw()! void OnAbortDeletion(wxCommandEvent& event) //handle abort button click { abortRequested = true; //don't throw exceptions in a GUI-Callback!!! (throw zen::AbortThisProcess()) @@ -1056,7 +1095,6 @@ private: event.Skip(); } - MainDialog* const mainDlg; bool abortRequested; @@ -1076,8 +1114,8 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec if (zen::showDeleteDialog(this, selectionLeft, selectionRight, - globalSettings->gui.deleteOnBothSides, - globalSettings->gui.useRecyclerForManualDeletion) == ReturnSmallDlg::BUTTON_OKAY) + globalCfg.gui.deleteOnBothSides, + globalCfg.gui.useRecyclerForManualDeletion) == ReturnSmallDlg::BUTTON_OKAY) { try { @@ -1088,9 +1126,12 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec selectionRight, folderCmp, extractDirectionCfg(getConfig().mainCfg), - globalSettings->gui.deleteOnBothSides, - globalSettings->gui.useRecyclerForManualDeletion, - statusHandler); + globalCfg.gui.deleteOnBothSides, + globalCfg.gui.useRecyclerForManualDeletion, + statusHandler, + globalCfg.optDialogs.warningRecyclerMissing); + + gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR); //do not clear, if aborted! } catch (AbortDeleteProcess&) {} @@ -1099,8 +1140,6 @@ void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selec //redraw grid neccessary to update new dimensions and for UI-Backend data linkage updateGui(); //call immediately after deleteFromGridAndHD!!! - - gridview::clearSelection(*m_gridMain); } } } @@ -1118,26 +1157,11 @@ wxString extractLastValidDir(const FileSystemObject& fsObj) } -void MainDialog::openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, size_t compPos) //fsObj may be nullptr +void MainDialog::openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, bool leftSide) //fsObj may be nullptr { if (commandline.empty()) return; - bool leftSide = true; - switch (compPos) - { - case gridview::COMP_LEFT: - break; - case gridview::COMP_MIDDLE: - return; //we don't want to start external apps when double-clicking here! - case gridview::COMP_RIGHT: - leftSide = false; - break; - default: //area to the right of main grid - leftSide = false; - fsObj = nullptr; //do not evaluate row when outside grid! - } - wxString name; wxString nameCo; wxString dir; @@ -1275,8 +1299,9 @@ void MainDialog::disableAllElements(bool enableAbort) m_panelConfig ->Disable(); m_bpButtonSyncConfig ->Disable(); m_buttonStartSync ->Disable(); - m_gridMain ->Disable(); - m_gridMain ->Disable(); + m_gridMainL ->Disable(); + m_gridMainC ->Disable(); + m_gridMainR ->Disable(); m_panelStatistics ->Disable(); m_gridNavi ->Disable(); m_panelDirectoryPairs->Disable(); @@ -1311,8 +1336,9 @@ void MainDialog::enableAllElements() m_panelConfig ->Enable(); m_bpButtonSyncConfig ->Enable(); m_buttonStartSync ->Enable(); - m_gridMain ->Enable(); - m_gridMain ->Enable(); + m_gridMainL ->Enable(); + m_gridMainC ->Enable(); + m_gridMainR ->Enable(); m_panelStatistics ->Enable(); m_gridNavi ->Enable(); m_panelDirectoryPairs->Enable(); @@ -1402,7 +1428,7 @@ void MainDialog::OnResizeFolderPairs(wxEvent& event) void MainDialog::onTreeButtonEvent(wxKeyEvent& event) { int keyCode = event.GetKeyCode(); - if (m_gridMain->GetLayoutDirection() == wxLayout_RightToLeft) + if (m_gridNavi->GetLayoutDirection() == wxLayout_RightToLeft) { if (keyCode == WXK_LEFT) keyCode = WXK_RIGHT; @@ -1459,10 +1485,23 @@ void MainDialog::onTreeButtonEvent(wxKeyEvent& event) } -void MainDialog::onGridButtonEvent(wxKeyEvent& event) +void MainDialog::onGridButtonEventL(wxKeyEvent& event) +{ + onGridButtonEvent(event, *m_gridMainL, true); +} +void MainDialog::onGridButtonEventC(wxKeyEvent& event) +{ + onGridButtonEvent(event, *m_gridMainC, true); +} +void MainDialog::onGridButtonEventR(wxKeyEvent& event) +{ + onGridButtonEvent(event, *m_gridMainR, false); +} + +void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide) { int keyCode = event.GetKeyCode(); - if (m_gridMain->GetLayoutDirection() == wxLayout_RightToLeft) + if (grid.GetLayoutDirection() == wxLayout_RightToLeft) { if (keyCode == WXK_LEFT) keyCode = WXK_RIGHT; @@ -1488,19 +1527,19 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event) { case WXK_NUMPAD_LEFT: case WXK_LEFT: //ALT + <- - setSyncDirManually(getGridSelection(), zen::SYNC_DIR_LEFT); + setSyncDirManually(getGridSelection(), SYNC_DIR_LEFT); return; case WXK_NUMPAD_RIGHT: case WXK_RIGHT: //ALT + -> - setSyncDirManually(getGridSelection(), zen::SYNC_DIR_RIGHT); + setSyncDirManually(getGridSelection(), SYNC_DIR_RIGHT); return; case WXK_NUMPAD_UP: case WXK_NUMPAD_DOWN: case WXK_UP: /* ALT + /|\ */ case WXK_DOWN: /* ALT + \|/ */ - setSyncDirManually(getGridSelection(), zen::SYNC_DIR_NONE); + setSyncDirManually(getGridSelection(), SYNC_DIR_NONE); return; } @@ -1524,13 +1563,12 @@ void MainDialog::onGridButtonEvent(wxKeyEvent& event) case WXK_RETURN: case WXK_NUMPAD_ENTER: - if (!globalSettings->gui.externelApplications.empty()) + if (!globalCfg.gui.externelApplications.empty()) { - const wxString commandline = globalSettings->gui.externelApplications[0].second; //open with first external application - auto cursorPos = m_gridMain->getGridCursor(); - const size_t row = cursorPos.first; - const size_t compPos = cursorPos.second; - openExternalApplication(commandline, gridDataView->getObject(row), compPos); + const wxString commandline = globalCfg.gui.externelApplications[0].second; //open with first external application + auto cursorPos = grid.getGridCursor(); + const size_t row = cursorPos.first; + openExternalApplication(commandline, gridDataView->getObject(row), leftSide); } return; } @@ -1552,11 +1590,13 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou { //avoid recursion!!! -> this ugly construct seems to be the only (portable) way to avoid re-entrancy //recursion may happen in multiple situations: e.g. modal dialogs, wxGrid::ProcessEvent()! - if (processingGlobalKeyEvent || - !IsShown() || - !IsActive() || - !IsEnabled() || //only handle if main window is in use - !m_gridMain->IsEnabled()) // + if (processingGlobalKeyEvent || + !IsShown() || + !IsActive() || + !IsEnabled() || + !m_gridMainL->IsEnabled() || // + !m_gridMainC->IsEnabled() || //only handle if main window is in use + !m_gridMainR->IsEnabled()) // { event.Skip(); return; @@ -1573,7 +1613,7 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou switch (keyCode) { case 'F': //CTRL + F - zen::startFind(this, *m_gridMain, gridview::COMP_LEFT, gridview::COMP_RIGHT, globalSettings->gui.textSearchRespectCase); + zen::startFind(this, *m_gridMainL, *m_gridMainR, globalCfg.gui.textSearchRespectCase); return; //-> swallow event! } @@ -1581,7 +1621,7 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou { case WXK_F3: //F3 case WXK_NUMPAD_F3: // - zen::findNext(this, *m_gridMain, gridview::COMP_LEFT, gridview::COMP_RIGHT, globalSettings->gui.textSearchRespectCase); + zen::findNext(this, *m_gridMainL, *m_gridMainR, globalCfg.gui.textSearchRespectCase); return; //-> swallow event! case WXK_F8: //F8 @@ -1608,15 +1648,17 @@ void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events withou case WXK_NUMPAD_END: { const wxWindow* focus = wxWindow::FindFocus(); - if (!isPartOf(focus, m_gridMain ) && //don't propagate keyboard commands if grid is already in focus + if (!isPartOf(focus, m_gridMainL ) && // + !isPartOf(focus, m_gridMainC ) && //don't propagate keyboard commands if grid is already in focus + !isPartOf(focus, m_gridMainR ) && // !isPartOf(focus, m_listBoxHistory) && //don't propagate if selecting config !isPartOf(focus, m_directoryLeft) && //don't propagate if changing directory field !isPartOf(focus, m_directoryRight) && !isPartOf(focus, m_gridNavi ) && !isPartOf(focus, m_scrolledWindowFolderPairs)) - if (wxEvtHandler* evtHandler = m_gridMain->GetEventHandler()) + if (wxEvtHandler* evtHandler = m_gridMainL->GetEventHandler()) { - m_gridMain->SetFocus(); + m_gridMainL->SetFocus(); evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it... event.Skip(false); //definitively handled now! return; @@ -1648,7 +1690,16 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) } if (leadRow >= 0) - m_gridMain->scrollTo(std::max(0, leadRow - 1)); //scroll one more row + { + leadRow =std::max(0, leadRow - 1); //scroll one more row + + m_gridMainL->scrollTo(leadRow); + m_gridMainC->scrollTo(leadRow); + m_gridMainR->scrollTo(leadRow); + + m_gridNavi->getMainWin().Update(); //draw cursor immediately rather than on next idle event (required for slow CPUs, netbook) + + } //get selection on navigation tree and set corresponding markers on main grid std::vector<const HierarchyObject*> markedFiles; //mark files/symlinks directly within a container @@ -1669,7 +1720,7 @@ void MainDialog::onNaviSelection(GridRangeSelectEvent& event) } }); - gridview::setNavigationMarker(*m_gridMain, std::move(markedFiles), std::move(markedContainer)); + gridview::setNavigationMarker(*m_gridMainL, std::move(markedFiles), std::move(markedContainer)); event.Skip(); } @@ -1740,138 +1791,145 @@ void MainDialog::onNaviGridContext(GridClickEvent& event) } -void MainDialog::onMainGridContext(GridClickEvent& event) +void MainDialog::onMainGridContextC(GridClickEvent& event) { - const auto& selection = getGridSelection(); //referenced by lambdas! ContextMenu menu; - switch (event.compPos_) + menu.addItem(_("Include all"), [&] { - case gridview::COMP_MIDDLE: - menu.addItem(_("Include all"), [&] - { - zen::setActiveStatus(true, folderCmp); - updateGui(); - }, nullptr, gridDataView->rowsTotal() > 0); + zen::setActiveStatus(true, folderCmp); + updateGui(); + }, nullptr, gridDataView->rowsTotal() > 0); - menu.addItem(_("Exclude all"), [&] - { - zen::setActiveStatus(false, folderCmp); - updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true - }, nullptr, gridDataView->rowsTotal() > 0); - break; - - case gridview::COMP_LEFT: - case gridview::COMP_RIGHT: - default: //area to the right of main grid - //---------------------------------------------------------------------------------------------------- - if (showSyncAction_ && !selection.empty()) - { - auto getImage = [&](SyncDirection dir, SyncOperation soDefault) - { - return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ? - selection[0]->testSyncOperation(dir) : soDefault)); - }; - const wxBitmap opRight = getImage(SYNC_DIR_RIGHT, SO_OVERWRITE_RIGHT); - const wxBitmap opNone = getImage(SYNC_DIR_NONE, SO_DO_NOTHING ); - const wxBitmap opLeft = getImage(SYNC_DIR_LEFT, SO_OVERWRITE_LEFT ); - - wxString shortCutLeft = L"\tAlt+Left"; - wxString shortCutRight = L"\tAlt+Right"; - if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) - std::swap(shortCutLeft, shortCutRight); - - menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_RIGHT); }, &opRight); - menu.addItem(_("Set direction:") + L" -" L"\tAlt+Up", [this, &selection] { setSyncDirManually(selection, SYNC_DIR_NONE); }, &opNone); - menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_LEFT); }, &opLeft); - //Gtk needs a direction, "<-", because it has no context menu icons! - //Gtk requires "no spaces" for shortcut identifiers! - menu.addSeparator(); - } - //---------------------------------------------------------------------------------------------------- - if (!selection.empty()) - { - if (selection[0]->isActive()) - menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); - else - menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); - } - else - menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); + menu.addItem(_("Exclude all"), [&] + { + zen::setActiveStatus(false, folderCmp); + updateGuiAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true + }, nullptr, gridDataView->rowsTotal() > 0); - //---------------------------------------------------------------------------------------------------- - //EXCLUDE FILTER - if (selection.size() == 1) - { - ContextMenu submenu; + menu.popup(*this); +} - //by extension - if (dynamic_cast<const DirMapping*>(selection[0]) == nullptr) //non empty && no directory - { - const Zstring filename = afterLast(selection[0]->getObjRelativeName(), FILE_NAME_SEPARATOR); - if (contains(filename, Zchar('.'))) //be careful: AfterLast would return the whole string if '.' were not found! - { - const Zstring extension = afterLast(filename, Zchar('.')); +void MainDialog::onMainGridContextL(GridClickEvent& event) +{ + onMainGridContextRim(true); +} - submenu.addItem(L"*." + utfCvrtTo<wxString>(extension), - [this, extension] { excludeExtension(extension); }); - } - } +void MainDialog::onMainGridContextR(GridClickEvent& event) +{ + onMainGridContextRim(false); +} - //by short name - submenu.addItem(utfCvrtTo<wxString>(Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getObjShortName()), - [this, &selection] { excludeShortname(*selection[0]); }); +void MainDialog::onMainGridContextRim(bool leftSide) +{ + const auto& selection = getGridSelection(); //referenced by lambdas! + ContextMenu menu; - //by relative path - submenu.addItem(utfCvrtTo<wxString>(FILE_NAME_SEPARATOR + selection[0]->getObjRelativeName()), - [this, &selection] { excludeItems(selection); }); + if (showSyncAction_ && !selection.empty()) + { + auto getImage = [&](SyncDirection dir, SyncOperation soDefault) + { + return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ? + selection[0]->testSyncOperation(dir) : soDefault)); + }; + const wxBitmap opRight = getImage(SYNC_DIR_RIGHT, SO_OVERWRITE_RIGHT); + const wxBitmap opNone = getImage(SYNC_DIR_NONE, SO_DO_NOTHING ); + const wxBitmap opLeft = getImage(SYNC_DIR_LEFT, SO_OVERWRITE_LEFT ); - menu.addSubmenu(_("Exclude via filter:"), submenu, &GlobalResources::getImage(L"filterSmall")); - } - else if (selection.size() > 1) + wxString shortCutLeft = L"\tAlt+Left"; + wxString shortCutRight = L"\tAlt+Right"; + if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft) + std::swap(shortCutLeft, shortCutRight); + + menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_RIGHT); }, &opRight); + menu.addItem(_("Set direction:") + L" -" L"\tAlt+Up", [this, &selection] { setSyncDirManually(selection, SYNC_DIR_NONE); }, &opNone); + menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SYNC_DIR_LEFT); }, &opLeft); + //Gtk needs a direction, "<-", because it has no context menu icons! + //Gtk requires "no spaces" for shortcut identifiers! + menu.addSeparator(); + } + //---------------------------------------------------------------------------------------------------- + if (!selection.empty()) + { + if (selection[0]->isActive()) + menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, false); }, &GlobalResources::getImage(L"checkboxFalse")); + else + menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setManualFilter(selection, true); }, &GlobalResources::getImage(L"checkboxTrue")); + } + else + menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false); + + //---------------------------------------------------------------------------------------------------- + //EXCLUDE FILTER + if (selection.size() == 1) + { + ContextMenu submenu; + + //by extension + if (dynamic_cast<const DirMapping*>(selection[0]) == nullptr) //non empty && no directory + { + const Zstring filename = afterLast(selection[0]->getObjRelativeName(), FILE_NAME_SEPARATOR); + if (contains(filename, Zchar('.'))) //be careful: AfterLast would return the whole string if '.' were not found! { - //by relative path - menu.addItem(_("Exclude via filter:") + L" " + _("<multiple selection>"), - [this, &selection] { excludeItems(selection); }, &GlobalResources::getImage(L"filterSmall")); + const Zstring extension = afterLast(filename, Zchar('.')); + + submenu.addItem(L"*." + utfCvrtTo<wxString>(extension), + [this, extension] { excludeExtension(extension); }); } + } - //---------------------------------------------------------------------------------------------------- - //CONTEXT_EXTERNAL_APP - if (!globalSettings->gui.externelApplications.empty()) - { - menu.addSeparator(); + //by short name + submenu.addItem(utfCvrtTo<wxString>(Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getObjShortName()), + [this, &selection] { excludeShortname(*selection[0]); }); - for (auto iter = globalSettings->gui.externelApplications.begin(); - iter != globalSettings->gui.externelApplications.end(); - ++iter) - { - //some trick to translate default external apps on the fly: 1. "open in explorer" 2. "start directly" - wxString description = zen::implementation::translate(iter->first); - if (description.empty()) - description = L" "; //wxWidgets doesn't like empty items + //by relative path + submenu.addItem(utfCvrtTo<wxString>(FILE_NAME_SEPARATOR + selection[0]->getObjRelativeName()), + [this, &selection] { excludeItems(selection); }); - const wxString command = iter->second; + menu.addSubmenu(_("Exclude via filter:"), submenu, &GlobalResources::getImage(L"filterSmall")); + } + else if (selection.size() > 1) + { + //by relative path + menu.addItem(_("Exclude via filter:") + L" " + _("<multiple selection>"), + [this, &selection] { excludeItems(selection); }, &GlobalResources::getImage(L"filterSmall")); + } - auto openApp = [this, &selection, command, event] { openExternalApplication(command, selection.empty() ? nullptr : selection[0], event.compPos_); }; + //---------------------------------------------------------------------------------------------------- + //CONTEXT_EXTERNAL_APP + if (!globalCfg.gui.externelApplications.empty()) + { + menu.addSeparator(); - if (iter == globalSettings->gui.externelApplications.begin()) - menu.addItem(description + L"\tEnter", openApp); - else - menu.addItem(description, openApp, nullptr, !selection.empty()); - } - } - //---------------------------------------------------------------------------------------------------- - //CONTEXT_DELETE_FILES - menu.addSeparator(); + for (auto iter = globalCfg.gui.externelApplications.begin(); + iter != globalCfg.gui.externelApplications.end(); + ++iter) + { + //some trick to translate default external apps on the fly: 1. "open in explorer" 2. "start directly" + wxString description = zen::implementation::translate(iter->first); + if (description.empty()) + description = L" "; //wxWidgets doesn't like empty items - menu.addItem(_("Delete") + L"\tDel", [this] - { - deleteSelectedFiles( - getGridSelection(true, false), - getGridSelection(false, true)); - }, nullptr, !selection.empty()); - break; + const wxString command = iter->second; + + auto openApp = [this, &selection, leftSide, command] { openExternalApplication(command, selection.empty() ? nullptr : selection[0], leftSide); }; + + if (iter == globalCfg.gui.externelApplications.begin()) + menu.addItem(description + L"\tEnter", openApp); + else + menu.addItem(description, openApp, nullptr, !selection.empty()); + } } + //---------------------------------------------------------------------------------------------------- + //CONTEXT_DELETE_FILES + menu.addSeparator(); + + menu.addItem(_("Delete") + L"\tDel", [this] + { + deleteSelectedFiles( + getGridSelection(true, false), + getGridSelection(false, true)); + }, nullptr, !selection.empty()); menu.popup(*this); } @@ -1951,92 +2009,99 @@ void MainDialog::excludeItems(const std::vector<FileSystemObject*>& selection) } -void MainDialog::onGridLabelContext(GridClickEvent& event) +void MainDialog::onGridLabelContextC(GridClickEvent& event) { ContextMenu menu; + menu.addItem(_("Category") + L"\tF8", [&] { showSyncAction(false); }, showSyncAction_ ? nullptr : &GlobalResources::getImage(L"compareSmall")); + menu.addItem(_("Action"), [&] { showSyncAction(true ); }, showSyncAction_ ? &GlobalResources::getImage(L"syncSmall") : nullptr); + menu.popup(*this); +} - if (event.compPos_ == gridview::COMP_LEFT || - event.compPos_ == gridview::COMP_RIGHT) - { - auto toggleColumn = [&](const Grid::ColumnAttribute& ca, size_t compPos) - { - auto colAttr = m_gridMain->getColumnConfig(compPos); - for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) - if (iter->type_ == ca.type_) - { - iter->visible_ = !ca.visible_; - m_gridMain->setColumnConfig(colAttr, compPos); - return; - } - }; +void MainDialog::onGridLabelContextL(GridClickEvent& event) +{ + onGridLabelContext(*m_gridMainL, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesLeft()); +} +void MainDialog::onGridLabelContextR(GridClickEvent& event) +{ + onGridLabelContext(*m_gridMainR, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesRight()); +} - if (auto prov = m_gridMain->getDataProvider(event.compPos_)) - { - const auto& colAttr = m_gridMain->getColumnConfig(event.compPos_); - for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) - { - const Grid::ColumnAttribute& ca = *iter; - const size_t compPos = event.compPos_; - menu.addCheckBox(prov->getColumnLabel(ca.type_), [ca, compPos, toggleColumn] { toggleColumn(ca, compPos); }, - ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_FILENAME)); //do not allow user to hide file name column! - } - } - //---------------------------------------------------------------------------------------------- - menu.addSeparator(); +void MainDialog::onGridLabelContext(Grid& grid, ColumnTypeRim type, const std::vector<ColumnAttributeRim>& defaultColumnAttributes) +{ + ContextMenu menu; - auto setDefault = [&] - { - m_gridMain->setColumnConfig(gridview::convertConfig(event.compPos_ == gridview::COMP_LEFT ? - getDefaultColumnAttributesLeft() : - getDefaultColumnAttributesRight()), event.compPos_); - }; - menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere - //---------------------------------------------------------------------------------------------- - menu.addSeparator(); - menu.addCheckBox(_("Show icons:"), [&] - { - globalSettings->gui.showIcons = !globalSettings->gui.showIcons; - gridview::setupIcons(*m_gridMain, globalSettings->gui.showIcons, convert(globalSettings->gui.iconSize)); + auto toggleColumn = [&](const Grid::ColumnAttribute& ca) + { + auto colAttr = grid.getColumnConfig(); - }, globalSettings->gui.showIcons); + for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) + if (iter->type_ == ca.type_) + { + iter->visible_ = !ca.visible_; + grid.setColumnConfig(colAttr); + return; + } + }; - auto setIconSize = [&](xmlAccess::FileIconSize sz) - { - globalSettings->gui.iconSize = sz; - gridview::setupIcons(*m_gridMain, globalSettings->gui.showIcons, convert(sz)); - }; - auto addSizeEntry = [&](const wxString& label, xmlAccess::FileIconSize sz) - { - auto setIconSize2 = setIconSize; //bring into scope - menu.addRadio(label, [sz, setIconSize2] { setIconSize2(sz); }, globalSettings->gui.iconSize == sz, globalSettings->gui.showIcons); - }; - addSizeEntry(L" " + _("Small" ), xmlAccess::ICON_SIZE_SMALL ); - addSizeEntry(L" " + _("Medium"), xmlAccess::ICON_SIZE_MEDIUM); - addSizeEntry(L" " + _("Large" ), xmlAccess::ICON_SIZE_LARGE ); - //---------------------------------------------------------------------------------------------- - if (static_cast<ColumnTypeRim>(event.colType_) == COL_TYPE_DATE) + if (auto prov = grid.getDataProvider()) + { + const auto& colAttr = grid.getColumnConfig(); + for (auto iter = colAttr.begin(); iter != colAttr.end(); ++iter) { - menu.addSeparator(); + const Grid::ColumnAttribute& ca = *iter; - auto selectTimeSpan = [&] - { - if (showSelectTimespanDlg(this, manualTimeSpanFrom, manualTimeSpanTo) == ReturnSmallDlg::BUTTON_OKAY) - { - applyTimeSpanFilter(folderCmp, manualTimeSpanFrom, manualTimeSpanTo); //overwrite current active/inactive settings - updateGuiAfterFilterChange(400); - } - }; - menu.addItem(_("Select time span..."), selectTimeSpan); + menu.addCheckBox(prov->getColumnLabel(ca.type_), [ca, toggleColumn] { toggleColumn(ca); }, + ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_FILENAME)); //do not allow user to hide file name column! } } + //---------------------------------------------------------------------------------------------- + menu.addSeparator(); - else if (event.compPos_ == gridview::COMP_MIDDLE) + auto setDefault = [&] { - menu.addItem(_("Category") + L"\tF8", [&] { showSyncAction(false); }, showSyncAction_ ? nullptr : &GlobalResources::getImage(L"compareSmall")); - menu.addItem(_("Action"), [&] { showSyncAction(true ); }, showSyncAction_ ? &GlobalResources::getImage(L"syncSmall") : nullptr); + grid.setColumnConfig(gridview::convertConfig(defaultColumnAttributes)); + }; + menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere + //---------------------------------------------------------------------------------------------- + menu.addSeparator(); + menu.addCheckBox(_("Show icons:"), [&] + { + globalCfg.gui.showIcons = !globalCfg.gui.showIcons; + gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg.gui.showIcons, convert(globalCfg.gui.iconSize)); + + }, globalCfg.gui.showIcons); + + auto setIconSize = [&](xmlAccess::FileIconSize sz) + { + globalCfg.gui.iconSize = sz; + gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg.gui.showIcons, convert(sz)); + }; + auto addSizeEntry = [&](const wxString& label, xmlAccess::FileIconSize sz) + { + auto setIconSize2 = setIconSize; //bring into scope + menu.addRadio(label, [sz, setIconSize2] { setIconSize2(sz); }, globalCfg.gui.iconSize == sz, globalCfg.gui.showIcons); + }; + addSizeEntry(L" " + _("Small" ), xmlAccess::ICON_SIZE_SMALL ); + addSizeEntry(L" " + _("Medium"), xmlAccess::ICON_SIZE_MEDIUM); + addSizeEntry(L" " + _("Large" ), xmlAccess::ICON_SIZE_LARGE ); + //---------------------------------------------------------------------------------------------- + if (type == COL_TYPE_DATE) + { + menu.addSeparator(); + + auto selectTimeSpan = [&] + { + if (showSelectTimespanDlg(this, manualTimeSpanFrom, manualTimeSpanTo) == ReturnSmallDlg::BUTTON_OKAY) + { + applyTimeSpanFilter(folderCmp, manualTimeSpanFrom, manualTimeSpanTo); //overwrite current active/inactive settings + updateGuiAfterFilterChange(400); + } + }; + menu.addItem(_("Select time span..."), selectTimeSpan); } + menu.popup(*this); } @@ -2048,6 +2113,8 @@ void MainDialog::OnContextSetLayout(wxMouseEvent& event) menu.addItem(_("Default view"), [&] { auiMgr.LoadPerspective(defaultPerspective); + + m_splitterMain->setSashOffset(0); updateGuiForFolderPair(); }); //---------------------------------------------------------------------------------------- @@ -2296,13 +2363,13 @@ bool MainDialog::saveOldConfig() //return false on user abort if (lastConfigurationSaved != getConfig()) { //notify user about changed settings - if (globalSettings->optDialogs.popupOnConfigChange) + if (globalCfg.optDialogs.popupOnConfigChange) if (activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName()) //only if check is active and non-default config file loaded { const wxString filename = activeConfigFiles[0]; - bool neverSave = !globalSettings->optDialogs.popupOnConfigChange; + bool neverSave = !globalCfg.optDialogs.popupOnConfigChange; CheckBox cb(_("Never save changes"), neverSave); switch (showQuestionDlg(this, @@ -2320,7 +2387,7 @@ bool MainDialog::saveOldConfig() //return false on user abort nullptr); case ReturnQuestionDlg::BUTTON_NO: - globalSettings->optDialogs.popupOnConfigChange = !neverSave; + globalCfg.optDialogs.popupOnConfigChange = !neverSave; break; case ReturnQuestionDlg::BUTTON_CANCEL: @@ -2645,8 +2712,11 @@ void MainDialog::updateGuiAfterFilterChange(int delay) if (currentCfg.hideFilteredElements) { - m_gridMain->Refresh(); - m_gridMain->Update(); + gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); + m_gridMainL->Update(); + m_gridMainC->Update(); + m_gridMainR->Update(); + wxMilliSleep(delay); //some delay to show user the rows he has filtered out before they are removed } @@ -2941,9 +3011,16 @@ void MainDialog::OnCompare(wxCommandEvent& event) wxBusyCursor dummy; //show hourglass cursor + wxWindow* oldFocus = wxWindow::FindFocus(); + ZEN_ON_SCOPE_EXIT( if (oldFocus) oldFocus->SetFocus(); ); //e.g. keep focus on main grid after pressing F5 + int scrollPosX = 0; int scrollPosY = 0; - m_gridMain->GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position + m_gridMainL->GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position + ZEN_ON_SCOPE_EXIT( + m_gridMainL->Scroll(scrollPosX, scrollPosY); // + m_gridMainR->Scroll(scrollPosX, scrollPosY); //restore + m_gridMainC->Scroll(-1, scrollPosY); ) // clearGrid(false); //avoid memory peak by clearing old data @@ -2956,7 +3033,7 @@ void MainDialog::OnCompare(wxCommandEvent& event) //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization std::unique_ptr<LockHolder> dummy2; - if (globalSettings->createLockFile) + if (globalCfg.createLockFile) { dummy2.reset(new LockHolder(true)); //allow pw prompt for (auto iter = cmpConfig.begin(); iter != cmpConfig.end(); ++iter) @@ -2967,23 +3044,18 @@ void MainDialog::OnCompare(wxCommandEvent& event) } //begin comparison - zen::CompareProcess compProc(globalSettings->fileTimeTolerance, - globalSettings->optDialogs, + zen::CompareProcess compProc(globalCfg.fileTimeTolerance, + globalCfg.optDialogs, true, //allow pw prompt - globalSettings->runWithBackgroundPriority, + globalCfg.runWithBackgroundPriority, statusHandler); //technical representation of comparison data - compProc.startCompareProcess(cmpConfig, folderCmp); //throw - - //play (optional) sound notification after sync has completed (GUI and batch mode) - const wxString soundFile = toWx(zen::getResourceDir()) + wxT("Compare_Complete.wav"); - if (fileExists(toZ(soundFile))) - wxSound::Play(soundFile, wxSOUND_ASYNC); + compProc.startCompareProcess(cmpConfig, folderCmp); //throw GuiAbortProcess } catch (GuiAbortProcess&) { - if (m_buttonCompare->IsShownOnScreen()) m_buttonCompare->SetFocus(); + // if (m_buttonCompare->IsShownOnScreen()) m_buttonCompare->SetFocus(); updateGui(); //refresh grid in ANY case! (also on abort) return; } @@ -2991,15 +3063,19 @@ void MainDialog::OnCompare(wxCommandEvent& event) gridDataView->setData(folderCmp); //update view on data treeDataView->setData(folderCmp); // updateGui(); - m_gridMain->Scroll(scrollPosX, scrollPosY); //restore + updateSyncEnabledStatus(); //enable the sync button - if (m_buttonStartSync->IsShownOnScreen()) - m_buttonStartSync->SetFocus(); + // if (m_buttonStartSync->IsShownOnScreen()) m_buttonStartSync->SetFocus(); - gridview::clearSelection(*m_gridMain); + gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR); m_gridNavi->clearSelection(); + //play (optional) sound notification after sync has completed (GUI and batch mode) + const wxString soundFile = toWx(zen::getResourceDir()) + L"Compare_Complete.wav"; + if (fileExists(toZ(soundFile))) + wxSound::Play(soundFile, wxSOUND_ASYNC); + //add to folder history after successful comparison only folderHistoryLeft ->addItem(toZ(m_directoryLeft->GetValue())); folderHistoryRight->addItem(toZ(m_directoryRight->GetValue())); @@ -3070,8 +3146,8 @@ void MainDialog::updateStatistics() void MainDialog::OnSyncSettings(wxCommandEvent& event) { ExecWhenFinishedCfg ewfCfg = { ¤tCfg.mainCfg.onCompletion, - &globalSettings->gui.onCompletionHistory, - globalSettings->gui.onCompletionHistoryMax + &globalCfg.gui.onCompletionHistory, + globalCfg.gui.onCompletionHistoryMax }; if (showSyncConfigDlg(this, @@ -3145,7 +3221,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) } //show sync preview screen - if (globalSettings->optDialogs.showSummaryBeforeSync) + if (globalCfg.optDialogs.showSummaryBeforeSync) { bool dontShowAgain = false; @@ -3155,7 +3231,7 @@ void MainDialog::OnStartSync(wxCommandEvent& event) dontShowAgain) != ReturnSmallDlg::BUTTON_OKAY) return; - globalSettings->optDialogs.showSummaryBeforeSync = !dontShowAgain; + globalCfg.optDialogs.showSummaryBeforeSync = !dontShowAgain; } wxBusyCursor dummy; //show hourglass cursor @@ -3175,11 +3251,11 @@ void MainDialog::OnStartSync(wxCommandEvent& event) currentCfg.handleError, xmlAccess::extractJobName(activeFileName), guiCfg.mainCfg.onCompletion, - globalSettings->gui.onCompletionHistory); + globalCfg.gui.onCompletionHistory); //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization std::unique_ptr<LockHolder> dummy2; - if (globalSettings->createLockFile) + if (globalCfg.createLockFile) { dummy2.reset(new LockHolder(true)); //allow pw prompt for (auto iter = begin(folderCmp); iter != end(folderCmp); ++iter) @@ -3192,12 +3268,12 @@ void MainDialog::OnStartSync(wxCommandEvent& event) //start synchronization and mark all elements processed zen::SyncProcess syncProc(xmlAccess::extractJobName(activeFileName), formatTime<std::wstring>(L"%Y-%m-%d %H%M%S"), - globalSettings->optDialogs, - globalSettings->verifyFileCopy, - globalSettings->copyLockedFiles, - globalSettings->copyFilePermissions, - globalSettings->transactionalFileCopy, - globalSettings->runWithBackgroundPriority, + globalCfg.optDialogs, + globalCfg.verifyFileCopy, + globalCfg.copyLockedFiles, + globalCfg.copyFilePermissions, + globalCfg.transactionalFileCopy, + globalCfg.runWithBackgroundPriority, statusHandler); const std::vector<zen::FolderPairSyncCfg> syncProcessCfg = zen::extractSyncCfg(guiCfg.mainCfg); @@ -3225,48 +3301,52 @@ void MainDialog::OnStartSync(wxCommandEvent& event) } -void MainDialog::onGridDoubleClick(GridClickEvent& event) +void MainDialog::onGridDoubleClickL(GridClickEvent& event) { - if (!globalSettings->gui.externelApplications.empty()) - openExternalApplication(globalSettings->gui.externelApplications[0].second, - gridDataView->getObject(event.row_), //optional - event.compPos_); + onGridDoubleClickRim(event.row_, true); +} +void MainDialog::onGridDoubleClickR(GridClickEvent& event) +{ + onGridDoubleClickRim(event.row_, false); } - -void MainDialog::onGridLabelLeftClick(GridClickEvent& event) +void MainDialog::onGridDoubleClickRim(int row, bool leftSide) { - auto sortSide = [&](bool onLeft) - { - auto sortInfo = gridDataView->getSortInfo(); + if (!globalCfg.gui.externelApplications.empty()) + openExternalApplication(globalCfg.gui.externelApplications[0].second, + gridDataView->getObject(row), //optional + leftSide); +} - ColumnTypeRim type = static_cast<ColumnTypeRim>(event.colType_); - bool sortAscending = GridView::getDefaultSortDirection(type); - if (sortInfo && sortInfo->onLeft_ == onLeft && sortInfo->type_ == type) - sortAscending = !sortInfo->ascending_; +void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim type) +{ + auto sortInfo = gridDataView->getSortInfo(); - gridDataView->sortView(type, onLeft, sortAscending); + bool sortAscending = GridView::getDefaultSortDirection(type); + if (sortInfo && sortInfo->onLeft_ == onLeft && sortInfo->type_ == type) + sortAscending = !sortInfo->ascending_; - gridview::clearSelection(*m_gridMain); - updateGui(); //refresh gridDataView - }; + gridDataView->sortView(type, onLeft, sortAscending); - switch (event.compPos_) - { - case gridview::COMP_LEFT: - sortSide(true); - break; + gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR); + updateGui(); //refresh gridDataView +} + +void MainDialog::onGridLabelLeftClickL(GridClickEvent& event) +{ + onGridLabelLeftClick(true, static_cast<ColumnTypeRim>(event.colType_)); +} +void MainDialog::onGridLabelLeftClickR(GridClickEvent& event) +{ + onGridLabelLeftClick(false, static_cast<ColumnTypeRim>(event.colType_)); +} - case gridview::COMP_MIDDLE: - //sorting middle grid is more or less useless: therefore let's toggle view instead! - showSyncAction(!showSyncAction_); //toggle view - break; - case gridview::COMP_RIGHT: - sortSide(false); - break; - } +void MainDialog::onGridLabelLeftClickC(GridClickEvent& event) +{ + //sorting middle grid is more or less useless: therefore let's toggle view instead! + showSyncAction(!showSyncAction_); //toggle view } @@ -3434,7 +3514,7 @@ void MainDialog::updateGridViewData() m_panelViewFilter->Hide(); } //all three grids retrieve their data directly via gridDataView - m_gridMain->Refresh(); + gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR); //navigation tree if (showSyncAction_) @@ -3510,7 +3590,7 @@ void MainDialog::applySyncConfig() zen::redetermineSyncDirection(getConfig().mainCfg, folderCmp, [&](const std::wstring& warning) { - bool& warningActive = globalSettings->optDialogs.warningSyncDatabase; + bool& warningActive = globalCfg.optDialogs.warningSyncDatabase; if (warningActive) { bool dontWarnAgain = false; @@ -3618,7 +3698,7 @@ void MainDialog::updateGuiForFolderPair() { int pairHeight = additionalFolderPairs[0]->GetSize().GetHeight(); addPairMinimalHeight = std::min<double>(1.5, additionalFolderPairs.size()) * pairHeight; //have 0.5 * height indicate that more folders are there - addPairOptimalHeight = std::min<double>(globalSettings->gui.maxFolderPairsVisible - 1 + 0.5, //subtract first/main folder pair and add 0.5 to indicate additional folders + addPairOptimalHeight = std::min<double>(globalCfg.gui.maxFolderPairsVisible - 1 + 0.5, //subtract first/main folder pair and add 0.5 to indicate additional folders additionalFolderPairs.size()) * pairHeight; addPairOptimalHeight = std::max(addPairOptimalHeight, addPairMinimalHeight); //implicitly handle corrupted values for "maxFolderPairsVisible" @@ -3719,7 +3799,7 @@ void MainDialog::removeAddFolderPair(size_t pos) //set size of scrolled window //const size_t additionalRows = additionalFolderPairs.size(); - //if (additionalRows <= globalSettings->gui.addFolderPairCountMax) //up to "addFolderPairCountMax" additional pairs shall be shown + //if (additionalRows <= globalCfg.gui.addFolderPairCountMax) //up to "addFolderPairCountMax" additional pairs shall be shown // m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, pairHeight * static_cast<int>(additionalRows))); //update controls @@ -3756,7 +3836,7 @@ void MainDialog::clearAddFolderPairs() //menu events void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event) { - zen::showGlobalSettingsDlg(this, *globalSettings); + zen::showGlobalSettingsDlg(this, globalCfg); } @@ -3816,13 +3896,13 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) buffer += '\n'; //write header - auto provLeft = m_gridMain->getDataProvider(gridview::COMP_LEFT); - auto provMiddle = m_gridMain->getDataProvider(gridview::COMP_MIDDLE); - auto provRight = m_gridMain->getDataProvider(gridview::COMP_RIGHT); + auto provLeft = m_gridMainL->getDataProvider(); + auto provMiddle = m_gridMainC->getDataProvider(); + auto provRight = m_gridMainR->getDataProvider(); - auto colAttrLeft = m_gridMain->getColumnConfig(gridview::COMP_LEFT); - auto colAttrMiddle = m_gridMain->getColumnConfig(gridview::COMP_MIDDLE); - auto colAttrRight = m_gridMain->getColumnConfig(gridview::COMP_RIGHT); + auto colAttrLeft = m_gridMainL->getColumnConfig(); + auto colAttrMiddle = m_gridMainC->getColumnConfig(); + auto colAttrRight = m_gridMainR->getColumnConfig(); vector_remove_if(colAttrLeft , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); vector_remove_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); @@ -3863,7 +3943,7 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) buffer += '\n'; //main grid - const size_t rowCount = m_gridMain->getRowCount(); + const size_t rowCount = m_gridMainL->getRowCount(); for (size_t row = 0; row < rowCount; ++row) { std::for_each(colAttrLeft.begin(), colAttrLeft.end(), @@ -3921,8 +4001,8 @@ void MainDialog::OnMenuBatchJob(wxCommandEvent& event) referenceFile, batchCfg, folderHistoryLeft, folderHistoryRight, - globalSettings->gui.onCompletionHistory, - globalSettings->gui.onCompletionHistoryMax); + globalCfg.gui.onCompletionHistory, + globalCfg.gui.onCompletionHistoryMax); } @@ -3937,7 +4017,7 @@ void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) //execute just once per startup! Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this); - zen::checkForUpdatePeriodically(this, globalSettings->gui.lastUpdateCheck); + zen::checkForUpdatePeriodically(this, globalCfg.gui.lastUpdateCheck); } @@ -3978,18 +4058,13 @@ void MainDialog::OnMenuQuit(wxCommandEvent& event) //######################################################################################################### //language selection -void MainDialog::switchProgramLanguage(const int langID) +void MainDialog::switchProgramLanguage(int langID) { //create new dialog with respect to new language zen::setLanguage(langID); //language is a global attribute - const xmlAccess::XmlGuiConfig currentGuiCfg = getConfig(); - auto activeFiles = activeConfigFiles; - - writeGlobalSettings(); //updating global settings before creating new dialog - //create new main window and delete old one - MainDialog* frame = new MainDialog(activeFiles, currentGuiCfg, *globalSettings, false); + MainDialog* frame = new MainDialog(activeConfigFiles, getConfig(), getGlobalCfgBeforeExit(), false); frame->Show(); Destroy(); @@ -4011,7 +4086,7 @@ void MainDialog::showSyncAction(bool value) showSyncAction_ = value; //toggle display of sync preview in middle grid - gridview::showSyncAction(*m_gridMain, value); + gridview::showSyncAction(*m_gridMainC, value); updateGui(); } diff --git a/ui/main_dlg.h b/ui/main_dlg.h index 0e9a7830..63eef25a 100644 --- a/ui/main_dlg.h +++ b/ui/main_dlg.h @@ -29,11 +29,11 @@ class MainDialog : public MainDialogGenerated { public: MainDialog(const std::vector<wxString>& cfgFileNames, //default behavior, application start - xmlAccess::XmlGlobalSettings& settings); + const xmlAccess::XmlGlobalSettings& globalSettings); //take over ownership => save on exit MainDialog(const std::vector<wxString>& referenceFiles, const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, //take over ownership => save on exit bool startComparison); ~MainDialog(); @@ -41,7 +41,7 @@ public: void disableAllElements(bool enableAbort); //dis-/enables all elements (except abort button) that might receive user input void enableAllElements(); //during long-running processes: comparison, deletion - void onQueryEndSession(); //last chance to do something before getting killed! + void onQueryEndSession(); //last chance to do something useful before killing the application! private: friend class CompareStatusHandler; @@ -56,7 +56,7 @@ private: MainDialog(); void init(const xmlAccess::XmlGuiConfig& guiCfg, - xmlAccess::XmlGlobalSettings& settings, + const xmlAccess::XmlGlobalSettings& globalSettings, bool startComparison); //configuration load/save @@ -66,6 +66,9 @@ private: xmlAccess::XmlGuiConfig getConfig() const; void setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg); + void setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings); //messes with Maximize(), window sizes, so call just once! + xmlAccess::XmlGlobalSettings getGlobalCfgBeforeExit(); //destructive "get" thanks to "Iconize(false), Maximize(false)" + void loadConfiguration(const wxString& filename); void loadConfiguration(const std::vector<wxString>& filenames); @@ -78,9 +81,6 @@ private: //used when saving configuration std::vector<wxString> activeConfigFiles; //name of currently loaded config file (may be more than 1) - void readGlobalSettings(); - void writeGlobalSettings(); - void initViewFilterButtons(); void updateFilterButtons(); @@ -106,7 +106,7 @@ private: void deleteSelectedFiles(const std::vector<zen::FileSystemObject*>& selectionLeft, const std::vector<zen::FileSystemObject*>& selectionRight); - void openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, size_t compPos); //fsObj may be nullptr + void openExternalApplication(const wxString& commandline, const zen::FileSystemObject* fsObj, bool leftSide); //fsObj may be nullptr //work to be done in idle time void OnIdleEvent(wxEvent& event); @@ -116,7 +116,11 @@ private: void flashStatusInformation(const wxString& msg); //temporarily show different status //events - void onGridButtonEvent (wxKeyEvent& event); + void onGridButtonEventL(wxKeyEvent& event); + void onGridButtonEventC(wxKeyEvent& event); + void onGridButtonEventR(wxKeyEvent& event); + void onGridButtonEvent(wxKeyEvent& event, zen::Grid& grid, bool leftSide); + void onTreeButtonEvent (wxKeyEvent& event); void OnContextSetLayout (wxMouseEvent& event); void OnGlobalKeyEvent (wxKeyEvent& event); @@ -128,7 +132,11 @@ private: void applyCompareConfig(bool changePreviewStatus = true); //context menu handler methods - void onMainGridContext(zen::GridClickEvent& event); + void onMainGridContextL(zen::GridClickEvent& event); + void onMainGridContextC(zen::GridClickEvent& event); + void onMainGridContextR(zen::GridClickEvent& event); + void onMainGridContextRim(bool leftSide); + void onNaviGridContext(zen::GridClickEvent& event); void onNaviSelection(zen::GridRangeSelectEvent& event); @@ -139,9 +147,19 @@ private: void onCheckRows (zen::CheckRowsEvent& event); void onSetSyncDirection(zen::SyncDirectionEvent& event); - void onGridDoubleClick (zen::GridClickEvent& event); - void onGridLabelLeftClick (zen::GridClickEvent& event); - void onGridLabelContext(zen::GridClickEvent& event); + void onGridDoubleClickL(zen::GridClickEvent& event); + void onGridDoubleClickR(zen::GridClickEvent& event); + void onGridDoubleClickRim(int row, bool leftSide); + + void onGridLabelLeftClickC (zen::GridClickEvent& event); + void onGridLabelLeftClickL (zen::GridClickEvent& event); + void onGridLabelLeftClickR (zen::GridClickEvent& event); + void onGridLabelLeftClick(bool onLeft, zen::ColumnTypeRim type); + + void onGridLabelContextL(zen::GridClickEvent& event); + void onGridLabelContextC(zen::GridClickEvent& event); + void onGridLabelContextR(zen::GridClickEvent& event); + void onGridLabelContext(zen::Grid& grid, zen::ColumnTypeRim type, const std::vector<zen::ColumnAttributeRim>& defaultColumnAttributes); void OnLeftOnlyFiles( wxCommandEvent& event); void OnRightOnlyFiles( wxCommandEvent& event); @@ -209,7 +227,7 @@ private: void OnMenuQuit( wxCommandEvent& event); void OnMenuLanguageSwitch( wxCommandEvent& event); - void switchProgramLanguage(const int langID); + void switchProgramLanguage(int langID); typedef int MenuItemID; typedef int LanguageID; @@ -218,8 +236,8 @@ private: //*********************************************** //application variables are stored here: - //global settings used by GUI and batch mode - xmlAccess::XmlGlobalSettings* globalSettings; //always bound! + //global settings shared by GUI and batch mode + xmlAccess::XmlGlobalSettings globalCfg; //UI view of FolderComparison structure (partially owns folderCmp) std::shared_ptr<zen::GridView> gridDataView; //always bound! diff --git a/ui/search.cpp b/ui/search.cpp index 80e4aa26..2a9aabfb 100644 --- a/ui/search.cpp +++ b/ui/search.cpp @@ -107,13 +107,12 @@ private: template <bool respectCase> ptrdiff_t findRow(const Grid& grid, //return -1 if no matching row found - size_t compPos, const wxString& searchString, size_t rowFirst, //specify area to search: size_t rowLast) // [rowFirst, rowLast) { - auto prov = grid.getDataProvider(compPos); - std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(compPos); + auto prov = grid.getDataProvider(); + std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig(); vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; }); if (!colAttr.empty() && prov) { @@ -129,16 +128,15 @@ ptrdiff_t findRow(const Grid& grid, //return -1 if no matching row found //syntactic sugar... -ptrdiff_t findRow(const Grid& grid, - size_t compPos, +ptrdiff_t findRow(Grid& grid, bool respectCase, const wxString& searchString, size_t rowFirst, //specify area to search: size_t rowLast) // [rowFirst, rowLast) { return respectCase ? - findRow<true>( grid, compPos, searchString, rowFirst, rowLast) : - findRow<false>(grid, compPos, searchString, rowFirst, rowLast); + findRow<true>( grid, searchString, rowFirst, rowLast) : + findRow<false>(grid, searchString, rowFirst, rowLast); } @@ -148,8 +146,7 @@ wxString lastSearchString; //this variable really is conceptionally global... void executeSearch(bool forceShowDialog, bool& respectCase, wxWindow* parent, - Grid& grid, - size_t compPosLeft, size_t compPosRight) + Grid* gridL, Grid* gridR) { bool searchDialogWasShown = false; @@ -162,36 +159,35 @@ void executeSearch(bool forceShowDialog, searchDialogWasShown = true; } - const size_t rowCount = grid.getRowCount(); - auto cursorPos = grid.getGridCursor(); //(row, component pos) + if (wxWindow::FindFocus() == &gridR->getMainWin()) + std::swap(gridL, gridR); //select side to start with - size_t cursorRow = cursorPos.first; - if (cursorRow >= rowCount) - cursorRow = 0; - - if (cursorPos.second == compPosRight) - std::swap(compPosLeft, compPosRight); //select side to start with - else if (cursorPos.second != compPosLeft) - cursorRow = 0; + const size_t rowCountL = gridL->getRowCount(); + const size_t rowCountR = gridR->getRowCount(); + auto cursorPos = gridL->getGridCursor(); //(row, component pos) + size_t cursorRowL = cursorPos.first; + if (cursorRowL >= rowCountL) + cursorRowL = 0; { wxBusyCursor showHourGlass; - auto finishSearch = [&](size_t compPos, size_t rowFirst, size_t rowLast) -> bool + auto finishSearch = [&](Grid& grid, size_t rowFirst, size_t rowLast) -> bool { - const ptrdiff_t targetRow = findRow(grid, compPos, respectCase, lastSearchString, rowFirst, rowLast); + const ptrdiff_t targetRow = findRow(grid, respectCase, lastSearchString, rowFirst, rowLast); if (targetRow >= 0) { - grid.setGridCursor(targetRow, compPos); + //gridOther.clearSelection(); -> not needed other grids are automatically cleared after selection + grid.setGridCursor(targetRow); grid.SetFocus(); return true; } return false; }; - if (finishSearch(compPosLeft , cursorRow + 1, rowCount) || - finishSearch(compPosRight, 0, rowCount) || - finishSearch(compPosLeft , 0, cursorRow + 1)) + if (finishSearch(*gridL, cursorRowL + 1, rowCountL) || + finishSearch(*gridR, 0, rowCountR) || + finishSearch(*gridL, 0, cursorRowL + 1)) return; } @@ -199,18 +195,18 @@ void executeSearch(bool forceShowDialog, //show search dialog again if (searchDialogWasShown) - executeSearch(true, respectCase, parent, grid, compPosLeft, compPosRight); + executeSearch(true, respectCase, parent, gridL, gridR); } //########################################################################################### -void zen::startFind(wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase) //Strg + F +void zen::startFind(wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase) //Strg + F { - executeSearch(true, respectCase, parent, grid, compPosLeft, compPosRight); + executeSearch(true, respectCase, parent, &gridL, &gridR); } -void zen::findNext(wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase) //F3 +void zen::findNext(wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase) //F3 { - executeSearch(false, respectCase, parent, grid, compPosLeft, compPosRight); + executeSearch(false, respectCase, parent, &gridL, &gridR); } diff --git a/ui/search.h b/ui/search.h index 6120562e..95811244 100644 --- a/ui/search.h +++ b/ui/search.h @@ -11,8 +11,8 @@ namespace zen { -void startFind(wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase); //Strg + F -void findNext( wxWindow* parent, Grid& grid, size_t compPosLeft, size_t compPosRight, bool& respectCase); //F3 +void startFind(wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase); //Strg + F +void findNext( wxWindow* parent, Grid& gridL, Grid& gridR, bool& respectCase); //F3 } #endif // SEARCH_H_INCLUDED diff --git a/ui/tree_view.cpp b/ui/tree_view.cpp index ea73d979..e4dc022e 100644 --- a/ui/tree_view.cpp +++ b/ui/tree_view.cpp @@ -478,9 +478,9 @@ void TreeView::updateCmpResult(bool hideFiltered, case FILE_DIFFERENT: return differentFilesActive; case FILE_EQUAL: + case FILE_DIFFERENT_METADATA: //= sub-category of equal return equalFilesActive; case FILE_CONFLICT: - case FILE_DIFFERENT_METADATA: return conflictFilesActive; } assert(false); @@ -612,8 +612,8 @@ const wxColour COLOR_LEVEL11(0xff, 0xcc, 0x99); const wxColour COLOR_PERCENTAGE_BORDER(198, 198, 198); const wxColour COLOR_PERCENTAGE_BACKGROUND(0xf8, 0xf8, 0xf8); -//const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = wxColor( 89, 255, 99); //green: H:88 S:255 V:172 -//const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = wxColor(225, 255, 227); // H:88 S:255 V:240 +//const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = wxColor( 89, 255, 99); //green: HSV: 88, 255, 172 +//const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = wxColor(225, 255, 227); // HSV: 88, 255, 240 const wxColor COLOR_TREE_SELECTION_GRADIENT_FROM = getColorSelectionGradientFrom(); const wxColor COLOR_TREE_SELECTION_GRADIENT_TO = getColorSelectionGradientTo (); @@ -1182,7 +1182,7 @@ std::vector<Grid::ColumnAttribute> treeview::convertConfig(const std::vector<Col std::vector<Grid::ColumnAttribute> output; std::transform(attribClean.begin(), attribClean.end(), std::back_inserter(output), - [&](const ColumnAttributeNavi& a) { return Grid::ColumnAttribute(static_cast<ColumnType>(a.type_), a.width_, a.visible_); }); + [&](const ColumnAttributeNavi& ca) { return Grid::ColumnAttribute(static_cast<ColumnType>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return output; } @@ -1193,7 +1193,7 @@ std::vector<ColumnAttributeNavi> treeview::convertConfig(const std::vector<Grid: std::vector<ColumnAttributeNavi> output; std::transform(attribs.begin(), attribs.end(), std::back_inserter(output), - [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeNavi(static_cast<ColumnTypeNavi>(ca.type_), ca.width_, ca.visible_); }); + [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeNavi(static_cast<ColumnTypeNavi>(ca.type_), ca.offset_, ca.stretch_, ca.visible_); }); return makeConsistent(output); } diff --git a/ui/tree_view.h b/ui/tree_view.h index 01c737bc..331e1f30 100644 --- a/ui/tree_view.h +++ b/ui/tree_view.h @@ -147,7 +147,7 @@ private: template <bool ascending> static void sortSingleLevel(std::vector<TreeLine>& items, ColumnTypeNavi columnType); template <bool ascending> struct LessShortName; - std::vector<TreeLine> flatTree; //collapsable/expandable sub-tree of partial view -> always sorted! + std::vector<TreeLine> flatTree; //collapsable/expandable sub-tree of folderCmpView -> always sorted! /* /|\ | (update...) | */ diff --git a/ui/triple_splitter.cpp b/ui/triple_splitter.cpp new file mode 100644 index 00000000..5783bc4f --- /dev/null +++ b/ui/triple_splitter.cpp @@ -0,0 +1,230 @@ +// ************************************************************************** +// * 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 "triple_splitter.h" +#include <algorithm> + +using namespace zen; + + +namespace +{ +//------------ Grid Constants ------------------------------- +const int SASH_HIT_TOLERANCE = 5; //currently onla a placebo! +const int SASH_SIZE = 10; +const double SASH_GRAVITY = 0.5; //value within [0, 1]; 1 := resize left only, 0 := resize right only +const int CHILD_WINDOW_MIN_SIZE = 50; //min. size of managed windows + +const wxColor COLOR_SASH_GRADIENT_FROM = wxColour(192, 192, 192); //light grey +const wxColor COLOR_SASH_GRADIENT_TO = *wxWHITE; +} + +TripleSplitter::TripleSplitter(wxWindow* parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style) : wxWindow(parent, id, pos, size, style | wxTAB_TRAVERSAL), //tab between windows + centerOffset(0), + windowL(nullptr), + windowC(nullptr), + windowR(nullptr) +{ + Connect(wxEVT_PAINT, wxPaintEventHandler(TripleSplitter::onPaintEvent ), nullptr, this); + Connect(wxEVT_SIZE, wxSizeEventHandler (TripleSplitter::onSizeEvent ), nullptr, this); + //http://wiki.wxwidgets.org/Flicker-Free_Drawing + Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(TripleSplitter::onEraseBackGround), nullptr, this); +#if wxCHECK_VERSION(2, 9, 1) + SetBackgroundStyle(wxBG_STYLE_PAINT); +#else + SetBackgroundStyle(wxBG_STYLE_CUSTOM); +#endif + Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(TripleSplitter::onMouseLeftDown ), nullptr, this); + Connect(wxEVT_LEFT_UP, wxMouseEventHandler(TripleSplitter::onMouseLeftUp ), nullptr, this); + Connect(wxEVT_MOTION, wxMouseEventHandler(TripleSplitter::onMouseMovement ), nullptr, this); + Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(TripleSplitter::onLeaveWindow ), nullptr, this); + Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(TripleSplitter::onMouseCaptureLost), nullptr, this); + Connect(wxEVT_LEFT_DCLICK, wxMouseEventHandler(TripleSplitter::onMouseLeftDouble), nullptr, this); +} + + +void TripleSplitter::updateWindowSizes() +{ + if (windowL && windowC && windowR) + { + const int centerPosX = getCenterPosX(); + const int centerWidth = getCenterWidth(); + + const wxRect clientRect = GetClientRect(); + + const int widthL = centerPosX; + const int windowRposX = widthL + centerWidth; + const int widthR = clientRect.width - windowRposX; + + windowL->SetSize(0, 0, widthL, clientRect.height); + windowC->SetSize(widthL + SASH_SIZE, 0, windowC->GetSize().GetWidth(), clientRect.height); + windowR->SetSize(windowRposX, 0, widthR, clientRect.height); + + wxClientDC dc(this); + drawSash(dc); + } +} + + +class TripleSplitter::SashMove +{ +public: + SashMove(wxWindow& wnd, int mousePosX, int centerPosX) : wnd_(wnd), mousePosX_(mousePosX), centerPosX_(centerPosX) + { + wnd_.SetCursor(wxCURSOR_SIZEWE); + wnd_.CaptureMouse(); + } + ~SashMove() + { + wnd_.SetCursor(*wxSTANDARD_CURSOR); + if (wnd_.HasCapture()) + wnd_.ReleaseMouse(); + } + int getMousePosXStart () { return mousePosX_; } + int getCenterPosXStart() { return centerPosX_; } + +private: + wxWindow& wnd_; + const int mousePosX_; + const int centerPosX_; +}; + + +inline +int TripleSplitter::getCenterWidth() const +{ + return 2 * SASH_SIZE + (windowC ? windowC->GetSize().GetWidth() : 0); +} + + +int TripleSplitter::getCenterPosXOptimal() const +{ + const wxRect clientRect = GetClientRect(); + const int centerWidth = getCenterWidth(); + return (clientRect.width - centerWidth) * SASH_GRAVITY; //allowed to be negative for extreme client widths! +} + +int TripleSplitter::getCenterPosX() const +{ + const wxRect clientRect = GetClientRect(); + const int centerWidth = getCenterWidth(); + const int centerPosXOptimal = getCenterPosXOptimal(); + + //normalize "centerPosXOptimal + centerOffset" + if (clientRect.width < 2 * CHILD_WINDOW_MIN_SIZE + centerWidth) //continuous transition between conditional branches! + //use fixed "centeroffset" when "clientRect.width == 2 * CHILD_WINDOW_MIN_SIZE + centerWidth" + return centerPosXOptimal + CHILD_WINDOW_MIN_SIZE - static_cast<int>(2 * CHILD_WINDOW_MIN_SIZE * SASH_GRAVITY); //avoid rounding error + else + return std::max(CHILD_WINDOW_MIN_SIZE, //make sure centerPosXOptimal + offset is within bounds + std::min(centerPosXOptimal + centerOffset, clientRect.width - CHILD_WINDOW_MIN_SIZE - centerWidth)); +} + + +void TripleSplitter::drawSash(wxDC& dc) +{ + const int centerPosX = getCenterPosX(); + const int centerWidth = getCenterWidth(); + + auto draw = [&](wxRect rect) + { + const int sash2ndHalf = 3; + rect.width -= sash2ndHalf; + dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxEAST); + + rect.x += rect.width; + rect.width = sash2ndHalf; + dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxWEST); + + static_assert(SASH_SIZE > sash2ndHalf, ""); + }; + + const wxRect rectSashL(centerPosX, 0, SASH_SIZE, GetClientRect().height); + const wxRect rectSashR(centerPosX + centerWidth - SASH_SIZE, 0, SASH_SIZE, GetClientRect().height); + + draw(rectSashL); + draw(rectSashR); +} + + +bool TripleSplitter::hitOnSashLine(int posX) const +{ + const int centerPosX = getCenterPosX(); + const int centerWidth = getCenterWidth(); + + //we don't get events outside of sash, so SASH_HIT_TOLERANCE is currently *useless* + auto hitSash = [&](int sashX) { return sashX - SASH_HIT_TOLERANCE <= posX && posX < sashX + SASH_SIZE + SASH_HIT_TOLERANCE; }; + + return hitSash(centerPosX) || hitSash(centerPosX + centerWidth - SASH_SIZE); //hit one of the two sash lines +} + + +void TripleSplitter::onMouseLeftDown(wxMouseEvent& event) +{ + activeMove.reset(); + + const int posX = event.GetPosition().x; + if (hitOnSashLine(posX)) + activeMove.reset(new SashMove(*this, posX, getCenterPosX())); + event.Skip(); +} + + +void TripleSplitter::onMouseLeftUp(wxMouseEvent& event) +{ + activeMove.reset(); //nothing else to do, actual work done by onMouseMovement() + event.Skip(); +} + + +void TripleSplitter::onMouseMovement(wxMouseEvent& event) +{ + if (activeMove) + { + centerOffset = activeMove->getCenterPosXStart() - getCenterPosXOptimal() + event.GetPosition().x - activeMove->getMousePosXStart(); + + updateWindowSizes(); + Update(); //no time to wait until idle event! + } + else + { + //we receive those only while above the sash, not the managed windows! + SetCursor(wxCURSOR_SIZEWE); //set window-local only! + } + event.Skip(); +} + + +void TripleSplitter::onLeaveWindow(wxMouseEvent& event) +{ + //even called when moving from sash over to managed windows! + if (!activeMove) + SetCursor(*wxSTANDARD_CURSOR); + event.Skip(); +} + + +void TripleSplitter::onMouseCaptureLost(wxMouseCaptureLostEvent& event) +{ + activeMove.reset(); + updateWindowSizes(); + //event.Skip(); -> we DID handle it! +} + + +void TripleSplitter::onMouseLeftDouble(wxMouseEvent& event) +{ + const int posX = event.GetPosition().x; + if (hitOnSashLine(posX)) + { + centerOffset = 0; //reset sash according to gravity + updateWindowSizes(); + } + event.Skip(); +} diff --git a/ui/triple_splitter.h b/ui/triple_splitter.h new file mode 100644 index 00000000..e95671c7 --- /dev/null +++ b/ui/triple_splitter.h @@ -0,0 +1,87 @@ +// ************************************************************************** +// * 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 TRIPPLE_SPLIT_HEADER_8257804292846842573942534254 +#define TRIPPLE_SPLIT_HEADER_8257804292846842573942534254 + +#include <cassert> +#include <memory> +#include <wx/window.h> +#include <wx/dcclient.h> + +//a not-so-crappy splitter window + +/* manage three contained windows: + 1. left and right window are stretched + 2. middle window is fixed size + 3. middle window position can be changed via mouse with two sash lines + ----------------- + | | | | + | | | | + | | | | + ----------------- +*/ + +namespace zen +{ +class TripleSplitter : public wxWindow +{ +public: + TripleSplitter(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = 0); + + void setupWindows(wxWindow* winL, wxWindow* winC, wxWindow* winR) + { + assert(winL->GetParent() == this && winC->GetParent() == this && winR->GetParent() == this && !GetSizer()); + windowL = winL; + windowC = winC; + windowR = winR; + updateWindowSizes(); + } + + int getSashOffset() const { return centerOffset; } + void setSashOffset(int off) { centerOffset = off; updateWindowSizes(); } + +private: + void onEraseBackGround(wxEraseEvent& event) {} + void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } + + void onPaintEvent(wxPaintEvent& event) + { + wxPaintDC dc(this); + drawSash(dc); + } + + void updateWindowSizes(); + int getCenterWidth() const; + int getCenterPosX() const; //return normalized posX + int getCenterPosXOptimal() const; + + void drawSash(wxDC& dc); + bool hitOnSashLine(int posX) const; + + void onMouseLeftDown(wxMouseEvent& event); + void onMouseLeftUp(wxMouseEvent& event); + void onMouseMovement(wxMouseEvent& event); + void onLeaveWindow(wxMouseEvent& event); + void onMouseCaptureLost(wxMouseCaptureLostEvent& event); + void onMouseLeftDouble(wxMouseEvent& event); + + class SashMove; + std::unique_ptr<SashMove> activeMove; + + int centerOffset; //offset to add after "gravity" stretching + + wxWindow* windowL; + wxWindow* windowC; + wxWindow* windowR; +}; +} + +#endif //TRIPPLE_SPLIT_HEADER_8257804292846842573942534254 |