summaryrefslogtreecommitdiff
path: root/wx+
diff options
context:
space:
mode:
Diffstat (limited to 'wx+')
-rw-r--r--wx+/app_main.h49
-rw-r--r--wx+/button.cpp318
-rw-r--r--wx+/button.h47
-rw-r--r--wx+/choice_enum.h120
-rw-r--r--wx+/dir_picker.h31
-rw-r--r--wx+/file_drop.h113
-rw-r--r--wx+/format_unit.cpp232
-rw-r--r--wx+/format_unit.h64
-rw-r--r--wx+/graph.cpp540
-rw-r--r--wx+/graph.h325
-rw-r--r--wx+/image_tools.h157
-rw-r--r--wx+/mouse_move_dlg.cpp72
-rw-r--r--wx+/mouse_move_dlg.h33
-rw-r--r--wx+/pch.h106
-rw-r--r--wx+/serialize.h274
-rw-r--r--wx+/shell_execute.h115
-rw-r--r--wx+/string_conv.h25
-rw-r--r--wx+/timespan.h166
-rw-r--r--wx+/toggle_button.h94
-rw-r--r--wx+/tooltip.cpp95
-rw-r--r--wx+/tooltip.h30
21 files changed, 3006 insertions, 0 deletions
diff --git a/wx+/app_main.h b/wx+/app_main.h
new file mode 100644
index 00000000..6b375592
--- /dev/null
+++ b/wx+/app_main.h
@@ -0,0 +1,49 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef APPMAIN_H_INCLUDED
+#define APPMAIN_H_INCLUDED
+
+#include <wx/window.h>
+#include <wx/app.h>
+
+namespace zen
+{
+//just some wrapper around a global variable representing the (logical) main application window
+void setMainWindow(wxWindow* window); //set main window and enable "exit on frame delete"
+bool mainWindowWasSet();
+
+
+
+
+
+
+
+
+//######################## implementation ########################
+inline
+bool& getMainWndStatus()
+{
+ static bool status = false; //external linkage!
+ return status;
+}
+
+
+inline
+void setMainWindow(wxWindow* window)
+{
+ wxTheApp->SetTopWindow(window);
+ wxTheApp->SetExitOnFrameDelete(true);
+
+ getMainWndStatus() = true;
+}
+
+
+inline
+bool mainWindowWasSet() { return getMainWndStatus(); }
+}
+
+#endif // APPMAIN_H_INCLUDED
diff --git a/wx+/button.cpp b/wx+/button.cpp
new file mode 100644
index 00000000..7edfdea8
--- /dev/null
+++ b/wx+/button.cpp
@@ -0,0 +1,318 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "button.h"
+#include <algorithm>
+#include <limits>
+#include <cmath>
+#include <wx/dcmemory.h>
+#include <wx/image.h>
+#include "image_tools.h"
+
+using namespace zen;
+
+
+void zen::setBitmapLabel(wxBitmapButton& button, const wxBitmap& bmp)
+{
+ if (!isEqual(button.GetBitmapLabel(), bmp))
+ button.SetBitmapLabel(bmp);
+}
+
+
+BitmapButton::BitmapButton(wxWindow* parent,
+ wxWindowID id,
+ const wxString& label,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxValidator& validator,
+ const wxString& name) :
+ wxBitmapButton(parent, id, wxNullBitmap, pos, size, style | wxBU_AUTODRAW, validator, name),
+ m_spaceAfter(0),
+ m_spaceBefore(0)
+{
+ setTextLabel(label);
+}
+
+
+void BitmapButton::setBitmapFront(const wxBitmap& bitmap, unsigned spaceAfter)
+{
+ if (!isEqual(bitmap, bitmapFront) || spaceAfter != m_spaceAfter) //avoid flicker
+ {
+ bitmapFront = bitmap;
+ m_spaceAfter = spaceAfter;
+ refreshButtonLabel();
+ }
+}
+
+
+void BitmapButton::setTextLabel(const wxString& text)
+{
+ if (text != textLabel) //avoid flicker
+ {
+ textLabel = text;
+ wxBitmapButton::SetLabel(text);
+ refreshButtonLabel();
+ }
+}
+
+
+void BitmapButton::setBitmapBack(const wxBitmap& bitmap, unsigned spaceBefore)
+{
+ if (!isEqual(bitmap, bitmapBack) || spaceBefore != m_spaceBefore) //avoid flicker
+ {
+ bitmapBack = bitmap;
+ m_spaceBefore = spaceBefore;
+ refreshButtonLabel();
+ }
+}
+
+
+void makeWhiteTransparent(wxImage& image) //assume black text on white background
+{
+ unsigned char* alphaFirst = image.GetAlpha();
+ if (alphaFirst)
+ {
+ unsigned char* alphaLast = alphaFirst + image.GetWidth() * image.GetHeight();
+
+ //dist(black, white)
+ double distBlackWhite = std::sqrt(3.0 * 255 * 255);
+
+ const unsigned char* bytePos = image.GetData();
+
+ for (unsigned char* j = alphaFirst; j != alphaLast; ++j)
+ {
+ unsigned char r = *bytePos++; //
+ unsigned char g = *bytePos++; //each pixel consists of three chars
+ unsigned char b = *bytePos++; //
+
+ //dist((r,g,b), white)
+ double distColWhite = std::sqrt((255.0 - r) * (255.0 - r) + (255.0 - g) * (255.0 - g) + (255.0 - b) * (255.0 - b));
+
+ //black(0,0,0) becomes fully opaque(255), while white(255,255,255) becomes transparent(0)
+ *j = distColWhite / distBlackWhite * wxIMAGE_ALPHA_OPAQUE;
+ }
+ }
+}
+
+
+wxSize getSizeNeeded(const wxString& text, wxFont& font)
+{
+ wxCoord width, height;
+ wxMemoryDC dc;
+
+ wxString textFormatted = text;
+ textFormatted.Replace(wxT("&"), wxT(""), false); //remove accelerator
+ dc.GetMultiLineTextExtent(textFormatted, &width, &height , NULL, &font);
+ return wxSize(width, height);
+}
+
+
+wxBitmap BitmapButton::createBitmapFromText(const wxString& text)
+{
+ //wxDC::DrawLabel() doesn't respect alpha channel at all => apply workaround to calculate alpha values manually:
+
+ if (text.empty())
+ return wxBitmap();
+
+ wxFont currentFont = wxBitmapButton::GetFont();
+ wxColor textColor = wxBitmapButton::GetForegroundColour();
+
+ wxSize sizeNeeded = getSizeNeeded(text, currentFont);
+ wxBitmap newBitmap(sizeNeeded.GetWidth(), sizeNeeded.GetHeight());
+
+ {
+ wxMemoryDC dc;
+ dc.SelectObject(newBitmap);
+
+ //set up white background
+ dc.SetBackground(*wxWHITE_BRUSH);
+ dc.Clear();
+
+ //find position of accelerator
+ int indexAccel = -1;
+ size_t accelPos;
+ wxString textLabelFormatted = text;
+ if ((accelPos = text.find(wxT("&"))) != wxString::npos)
+ {
+ textLabelFormatted.Replace(wxT("&"), wxT(""), false); //remove accelerator
+ indexAccel = static_cast<int>(accelPos);
+ }
+
+ dc.SetTextForeground(*wxBLACK); //for use in makeWhiteTransparent
+ dc.SetTextBackground(*wxWHITE); //
+ dc.SetFont(currentFont);
+
+ dc.DrawLabel(textLabelFormatted, wxNullBitmap, wxRect(0, 0, newBitmap.GetWidth(), newBitmap.GetHeight()), wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, indexAccel);
+
+ dc.SelectObject(wxNullBitmap);
+ }
+
+ //add alpha channel to image
+ wxImage finalImage(newBitmap.ConvertToImage());
+ finalImage.SetAlpha();
+
+ //linearInterpolation(finalImage);
+
+ //calculate values for alpha channel
+ makeWhiteTransparent(finalImage);
+
+ //now apply real text color
+ unsigned char* bytePos = finalImage.GetData();
+ const int pixelCount = finalImage.GetWidth() * finalImage.GetHeight();
+ for (int i = 0; i < pixelCount; ++ i)
+ {
+ *bytePos++ = textColor.Red();
+ *bytePos++ = textColor.Green();
+ *bytePos++ = textColor.Blue();
+ }
+
+ return wxBitmap(finalImage);
+}
+
+
+//copy one image into another, allowing arbitrary overlapping! (pos may contain negative numbers)
+void writeToImage(const wxImage& source, const wxPoint pos, wxImage& target)
+{
+ //determine startpositions in source and target image, as well as width and height to be copied
+ wxPoint posSrc, posTrg;
+ int width, height;
+
+ //X-axis
+ if (pos.x < 0)
+ {
+ posSrc.x = -pos.x;
+ posTrg.x = 0;
+ width = std::min(pos.x + source.GetWidth(), target.GetWidth());
+ }
+ else
+ {
+ posSrc.x = 0;
+ posTrg.x = pos.x;
+ width = std::min(target.GetWidth() - pos.x, source.GetWidth());
+ }
+
+ //Y-axis
+ if (pos.y < 0)
+ {
+ posSrc.y = -pos.y;
+ posTrg.y = 0;
+ height = std::min(pos.y + source.GetHeight(), target.GetHeight());
+ }
+ else
+ {
+ posSrc.y = 0;
+ posTrg.y = pos.y;
+ height = std::min(target.GetHeight() - pos.y, source.GetHeight());
+ }
+
+
+ if (width > 0 && height > 0)
+ {
+ {
+ //copy source to target respecting overlapping parts
+ const unsigned char* sourcePtr = source.GetData() + 3 * (posSrc.x + posSrc.y * source.GetWidth());
+ const unsigned char* const sourcePtrEnd = source.GetData() + 3 * (posSrc.x + (posSrc.y + height) * source.GetWidth());
+ unsigned char* targetPtr = target.GetData() + 3 * (posTrg.x + posTrg.y * target.GetWidth());
+
+ while (sourcePtr < sourcePtrEnd)
+ {
+ memcpy(targetPtr, sourcePtr, 3 * width);
+ sourcePtr += 3 * source.GetWidth();
+ targetPtr += 3 * target.GetWidth();
+ }
+ }
+
+ //handle different cases concerning alpha channel
+ if (source.HasAlpha())
+ {
+ if (!target.HasAlpha())
+ {
+ target.SetAlpha();
+ unsigned char* alpha = target.GetAlpha();
+ memset(alpha, wxIMAGE_ALPHA_OPAQUE, target.GetWidth() * target.GetHeight());
+ }
+
+ //copy alpha channel
+ const unsigned char* sourcePtr = source.GetAlpha() + (posSrc.x + posSrc.y * source.GetWidth());
+ const unsigned char* const sourcePtrEnd = source.GetAlpha() + (posSrc.x + (posSrc.y + height) * source.GetWidth());
+ unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth());
+
+ while (sourcePtr < sourcePtrEnd)
+ {
+ memcpy(targetPtr, sourcePtr, width);
+ sourcePtr += source.GetWidth();
+ targetPtr += target.GetWidth();
+ }
+ }
+ else if (target.HasAlpha())
+ {
+ unsigned char* targetPtr = target.GetAlpha() + (posTrg.x + posTrg.y * target.GetWidth());
+ const unsigned char* const targetPtrEnd = target.GetAlpha() + (posTrg.x + (posTrg.y + height) * target.GetWidth());
+
+ while (targetPtr < targetPtrEnd)
+ {
+ memset(targetPtr, wxIMAGE_ALPHA_OPAQUE, width);
+ targetPtr += target.GetWidth();
+ }
+ }
+ }
+}
+
+
+namespace
+{
+inline
+wxSize getSize(const wxBitmap& bmp)
+{
+ return bmp.IsOk() ? wxSize(bmp.GetWidth(), bmp.GetHeight()) : wxSize(0, 0);
+}
+}
+
+
+void BitmapButton::refreshButtonLabel()
+{
+ wxBitmap bitmapText = createBitmapFromText(textLabel);
+
+ wxSize szFront = getSize(bitmapFront); //
+ wxSize szText = getSize(bitmapText); //make sure to NOT access null-bitmaps!
+ wxSize szBack = getSize(bitmapBack); //
+
+ //calculate dimensions of new button
+ const int height = std::max(std::max(szFront.GetHeight(), szText.GetHeight()), szBack.GetHeight());
+ const int width = szFront.GetWidth() + m_spaceAfter + szText.GetWidth() + m_spaceBefore + szBack.GetWidth();
+
+ //create a transparent image
+ wxImage transparentImage(width, height, false);
+ transparentImage.SetAlpha();
+ unsigned char* alpha = transparentImage.GetAlpha();
+ ::memset(alpha, wxIMAGE_ALPHA_TRANSPARENT, width * height);
+
+ //wxDC::DrawLabel() unfortunately isn't working for transparent images on Linux, so we need to use custom image-concatenation
+ if (bitmapFront.IsOk())
+ writeToImage(bitmapFront.ConvertToImage(),
+ wxPoint(0, (transparentImage.GetHeight() - bitmapFront.GetHeight()) / 2),
+ transparentImage);
+
+ if (bitmapText.IsOk())
+ writeToImage(bitmapText.ConvertToImage(),
+ wxPoint(szFront.GetWidth() + m_spaceAfter, (transparentImage.GetHeight() - bitmapText.GetHeight()) / 2),
+ transparentImage);
+
+ if (bitmapBack.IsOk())
+ writeToImage(bitmapBack.ConvertToImage(),
+ wxPoint(szFront.GetWidth() + m_spaceAfter + szText.GetWidth() + m_spaceBefore, (transparentImage.GetHeight() - bitmapBack.GetHeight()) / 2),
+ transparentImage);
+
+ //adjust button size
+ wxSize minSize = GetMinSize();
+
+ //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly
+ wxBitmapButton::SetMinSize(wxSize(std::max(width + 10, minSize.GetWidth()), std::max(height + 5, minSize.GetHeight())));
+
+ //finally set bitmap
+ wxBitmapButton::SetBitmapLabel(wxBitmap(transparentImage));
+}
diff --git a/wx+/button.h b/wx+/button.h
new file mode 100644
index 00000000..e5d63a19
--- /dev/null
+++ b/wx+/button.h
@@ -0,0 +1,47 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef CUSTOMBUTTON_H_INCLUDED
+#define CUSTOMBUTTON_H_INCLUDED
+
+#include <wx/bmpbuttn.h>
+
+namespace zen
+{
+//zen::BitmapButton behaves like wxButton but optionally adds bitmap labels
+class BitmapButton : public wxBitmapButton
+{
+public:
+ BitmapButton(wxWindow* parent,
+ wxWindowID id,
+ const wxString& label,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxButtonNameStr);
+
+ void setBitmapFront(const wxBitmap& bitmap, unsigned spaceAfter = 0);
+ void setTextLabel( const wxString& text);
+ void setBitmapBack( const wxBitmap& bitmap, unsigned spaceBefore = 0);
+
+private:
+ wxBitmap createBitmapFromText(const wxString& text);
+ void refreshButtonLabel();
+
+ wxBitmap bitmapFront;
+ unsigned m_spaceAfter;
+ wxString textLabel;
+ unsigned m_spaceBefore;
+ wxBitmap bitmapBack;
+};
+
+//set bitmap label flicker free!
+void setBitmapLabel(wxBitmapButton& button, const wxBitmap& bmp);
+}
+
+
+#endif // CUSTOMBUTTON_H_INCLUDED
diff --git a/wx+/choice_enum.h b/wx+/choice_enum.h
new file mode 100644
index 00000000..4565bf81
--- /dev/null
+++ b/wx+/choice_enum.h
@@ -0,0 +1,120 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef WX_CHOICE_ENUM_H_INCLUDED
+#define WX_CHOICE_ENUM_H_INCLUDED
+
+#include <vector>
+#include <wx/choice.h>
+
+//handle mapping of enum values to wxChoice controls
+/*
+Example:
+
+Member variable:
+ zen::EnumDescrList<EnumOnError> enumDescrMap;
+
+Constructor code:
+ enumDescrMap.
+ add(ON_ERROR_POPUP , _("Show pop-up") , _("Show pop-up on errors or warnings")).
+ add(ON_ERROR_IGNORE, _("Ignore errors") , _("Hide all error and warning messages")).
+ add(ON_ERROR_EXIT , _("Exit instantly"), _("Abort synchronization immediately"));
+
+Set enum value:
+ setEnumVal(enumDescrMap, *m_choiceHandleError, value);
+
+Get enum value:
+ value = getEnumVal(enumDescrMap, *m_choiceHandleError)
+
+Update enum tooltips (after user changed selection):
+ updateTooltipEnumVal(enumDescrMap, *m_choiceHandleError);
+*/
+
+
+namespace zen
+{
+template <class Enum>
+struct EnumDescrList
+{
+ EnumDescrList& add(Enum value, const wxString& text, const wxString& tooltip = wxEmptyString)
+ {
+ descrList.push_back(std::make_pair(value, std::make_pair(text, tooltip)));
+ return *this;
+ }
+ typedef std::vector<std::pair<Enum, std::pair<wxString, wxString> > > DescrList;
+ DescrList descrList;
+};
+template <class Enum> void setEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl, Enum value);
+template <class Enum> Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl);
+template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//--------------- inline impelementation -------------------------------------------
+template <class Enum>
+void setEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl, Enum value)
+{
+ ctrl.Clear();
+
+ int selectedPos = 0;
+ for (typename EnumDescrList<Enum>::DescrList::const_iterator i = mapping.descrList.begin(); i != mapping.descrList.end(); ++i)
+ {
+ ctrl.Append(i->second.first);
+ if (i->first == value)
+ {
+ selectedPos = i - mapping.descrList.begin();
+
+ if (!i->second.second.empty())
+ ctrl.SetToolTip(i->second.second);
+ }
+ }
+
+ ctrl.SetSelection(selectedPos);
+}
+
+template <class Enum>
+Enum getEnumVal(const EnumDescrList<Enum>& mapping, const wxChoice& ctrl)
+{
+ const int selectedPos = ctrl.GetSelection();
+
+ if (0 <= selectedPos && selectedPos < static_cast<int>(mapping.descrList.size()))
+ return mapping.descrList[selectedPos].first;
+ else
+ {
+ assert(false);
+ return Enum(0);
+ }
+}
+
+template <class Enum> void updateTooltipEnumVal(const EnumDescrList<Enum>& mapping, wxChoice& ctrl)
+{
+ const Enum value = getEnumVal(mapping, ctrl);
+
+ for (typename EnumDescrList<Enum>::DescrList::const_iterator i = mapping.descrList.begin(); i != mapping.descrList.end(); ++i)
+ if (i->first == value)
+ ctrl.SetToolTip(i->second.second);
+}
+
+}
+
+
+#endif //WX_CHOICE_ENUM_H_INCLUDED
diff --git a/wx+/dir_picker.h b/wx+/dir_picker.h
new file mode 100644
index 00000000..5f18b6fb
--- /dev/null
+++ b/wx+/dir_picker.h
@@ -0,0 +1,31 @@
+#ifndef DIR_PICKER_I18N_H_INCLUDED
+#define DIR_PICKER_I18N_H_INCLUDED
+
+#include <wx/filepicker.h>
+#include <zen/i18n.h>
+
+namespace zen
+{
+class DirPickerCtrl : public wxDirPickerCtrl
+{
+public:
+ DirPickerCtrl(wxWindow* parent, wxWindowID id,
+ const wxString& path = wxEmptyString,
+ const wxString& message = wxDirSelectorPromptStr,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxDIRP_DEFAULT_STYLE,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxDirPickerCtrlNameStr) :
+ wxDirPickerCtrl(parent, id, path, message, pos, size, style, validator, name)
+ {
+#ifdef FFS_WIN
+ //fix wxWidgets localization gap:
+ wxButton* button = dynamic_cast<wxButton*>(m_pickerIface);
+ if (button) button->SetLabel(_("Browse"));
+#endif
+ }
+};
+}
+
+#endif // DIR_PICKER_I18N_H_INCLUDED
diff --git a/wx+/file_drop.h b/wx+/file_drop.h
new file mode 100644
index 00000000..1eaeede0
--- /dev/null
+++ b/wx+/file_drop.h
@@ -0,0 +1,113 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FILE_DROP_H_INCLUDED
+#define FILE_DROP_H_INCLUDED
+
+#include <wx/event.h>
+#include <wx/dnd.h>
+
+namespace zen
+{
+//register simple file drop event (without issue of freezing dialogs and without wxFileDropTarget overdesign)
+
+//1. setup a window to emit FFS_DROP_FILE_EVENT
+void setupFileDrop(wxWindow& wnd);
+
+//2. register events:
+//wnd.Connect (FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(MyDlg::OnFilesDropped), NULL, this);
+//wnd.Disconnect(FFS_DROP_FILE_EVENT, FFSFileDropEventHandler(MyDlg::OnFilesDropped), NULL, this);
+
+//3. do something:
+//void MyDlg::OnFilesDropped(FFSFileDropEvent& event);
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+inline
+wxEventType createNewEventType()
+{
+ //inline functions have external linkage by default => this static is also extern, i.e. program wide unique! but defined in a header... ;)
+ static wxEventType dummy = wxNewEventType();
+ return dummy;
+}
+
+//define new event type
+const wxEventType FFS_DROP_FILE_EVENT = createNewEventType();
+
+class FFSFileDropEvent : public wxCommandEvent
+{
+public:
+ FFSFileDropEvent(const std::vector<wxString>& filesDropped, const wxWindow& dropWindow, wxPoint dropPos) :
+ wxCommandEvent(FFS_DROP_FILE_EVENT),
+ filesDropped_(filesDropped),
+ dropWindow_(dropWindow),
+ dropPos_(dropPos) {}
+
+ virtual wxEvent* Clone() const
+ {
+ return new FFSFileDropEvent(filesDropped_, dropWindow_, dropPos_);
+ }
+
+ const std::vector<wxString>& getFiles() const { return filesDropped_; }
+ const wxWindow& getDropWindow() const { return dropWindow_; }
+ wxPoint getDropPosition() const { return dropPos_; } //position relative to drop window
+
+private:
+ const std::vector<wxString> filesDropped_;
+ const wxWindow& dropWindow_;
+ wxPoint dropPos_;
+};
+
+typedef void (wxEvtHandler::*FFSFileDropEventFunction)(FFSFileDropEvent&);
+
+#define FFSFileDropEventHandler(func) \
+ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(FFSFileDropEventFunction, &func)
+
+
+class WindowDropTarget : public wxFileDropTarget
+{
+public:
+ WindowDropTarget(wxWindow& dropWindow) : dropWindow_(dropWindow) {}
+
+ virtual bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& fileArray)
+ {
+ std::vector<wxString> filenames(fileArray.begin(), fileArray.end());
+ if (!filenames.empty())
+ {
+ //create a custom event on drop window: execute event after file dropping is completed! (after mouse is released)
+ FFSFileDropEvent evt(filenames, dropWindow_, wxPoint(x, y));
+ dropWindow_.GetEventHandler()->AddPendingEvent(evt);
+ }
+ return true;
+ }
+
+private:
+ wxWindow& dropWindow_;
+};
+
+
+inline
+void setupFileDrop(wxWindow& wnd)
+{
+ wnd.SetDropTarget(new WindowDropTarget(wnd)); //takes ownership
+}
+}
+
+#endif // FILE_DROP_H_INCLUDED
diff --git a/wx+/format_unit.cpp b/wx+/format_unit.cpp
new file mode 100644
index 00000000..771778aa
--- /dev/null
+++ b/wx+/format_unit.cpp
@@ -0,0 +1,232 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "format_unit.h"
+#include <zen/basic_math.h>
+#include <zen/i18n.h>
+#include <cwchar> //swprintf
+#include <ctime>
+#include <cstdio>
+
+#ifdef FFS_WIN
+#include <zen/win.h> //includes "windows.h"
+#include <zen/win_ver.h>
+#endif
+
+
+namespace
+{
+inline
+size_t getDigitCount(size_t number)
+{
+ return number == 0 ? 1 : static_cast<size_t>(std::log10(static_cast<double>(number))) + 1;
+} //count number of digits
+}
+
+
+std::wstring zen::filesizeToShortString(UInt64 size)
+{
+ if (to<Int64>(size) < 0) return _("Error");
+
+ if (size <= 999U)
+ return replaceCpy(_P("1 Byte", "%x Bytes", to<int>(size)),
+ L"%x",
+ toString<std::wstring>(size));
+ else
+ {
+ double filesize = to<double>(size);
+
+ filesize /= 1024;
+ std::wstring output = _("%x KB");
+ if (filesize > 999)
+ {
+ filesize /= 1024;
+ output = _("%x MB");
+ if (filesize > 999)
+ {
+ filesize /= 1024;
+ output = _("%x GB");
+ if (filesize > 999)
+ {
+ filesize /= 1024;
+ output = _("%x TB");
+ if (filesize > 999)
+ {
+ filesize /= 1024;
+ output = _("%x PB");
+ }
+ }
+ }
+ }
+ //print just three significant digits: 0,01 | 0,11 | 1,11 | 11,1 | 111
+ const size_t leadDigitCount = getDigitCount(static_cast<size_t>(filesize)); //number of digits before decimal point
+ if (leadDigitCount == 0 || leadDigitCount > 3)
+ return _("Error");
+
+ wchar_t buffer[50];
+#ifdef __MINGW32__
+ int charsWritten = ::snwprintf(buffer, 50, L"%.*f", static_cast<int>(3 - leadDigitCount), filesize); //MinGW does not comply to the C standard here
+#else
+ int charsWritten = std::swprintf(buffer, 50, L"%.*f", static_cast<int>(3 - leadDigitCount), filesize);
+#endif
+ return charsWritten > 0 ? replaceCpy(output, L"%x", std::wstring(buffer, charsWritten)) : _("Error");
+ }
+}
+
+
+enum UnitRemTime
+{
+ URT_SEC,
+ URT_MIN,
+ URT_HOUR,
+ URT_DAY
+};
+
+std::wstring zen::remainingTimeToShortString(double timeInSec)
+{
+ double remainingTime = timeInSec;
+
+ //determine preferred unit
+ UnitRemTime unit = URT_SEC;
+ if (remainingTime > 59)
+ {
+ unit = URT_MIN;
+ remainingTime /= 60;
+ if (remainingTime > 59)
+ {
+ unit = URT_HOUR;
+ remainingTime /= 60;
+ if (remainingTime > 23)
+ {
+ unit = URT_DAY;
+ remainingTime /= 24;
+ }
+ }
+ }
+
+ int formattedTime = numeric::round(remainingTime);
+
+ //reduce precision to 5 seconds
+ if (unit == URT_SEC)
+ formattedTime = static_cast<int>(std::ceil(formattedTime / 5.0) * 5);
+
+ //generate output message
+ std::wstring output;
+ switch (unit)
+ {
+ case URT_SEC:
+ output = _P("1 sec", "%x sec", formattedTime);
+ break;
+ case URT_MIN:
+ output = _P("1 min", "%x min", formattedTime);
+ break;
+ case URT_HOUR:
+ output = _P("1 hour", "%x hours", formattedTime);
+ break;
+ case URT_DAY:
+ output = _P("1 day", "%x days", formattedTime);
+ break;
+ }
+ return replaceCpy(output, L"%x", zen::toString<std::wstring>(formattedTime));
+}
+
+
+std::wstring zen::percentageToShortString(double fraction)
+{
+ return replaceCpy(_("%x%"), L"%x", printNumber<std::wstring>(L"%3.2f", fraction * 100.0), false);
+}
+
+
+std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number)
+{
+ std::wstring output(number);
+ for (size_t i = output.size(); i > 3; i -= 3)
+ output.insert(i - 3, zen::getThousandsSeparator());
+
+ return output;
+}
+
+/*
+#include <wx/scrolwin.h>
+
+void zen::scrollToBottom(wxScrolledWindow* scrWindow)
+{
+ int height = 0;
+ scrWindow->GetClientSize(NULL, &height);
+
+ int pixelPerLine = 0;
+ scrWindow->GetScrollPixelsPerUnit(NULL, &pixelPerLine);
+
+ if (height > 0 && pixelPerLine > 0)
+ {
+ const int scrollLinesTotal = scrWindow->GetScrollLines(wxVERTICAL);
+ const int scrollLinesOnScreen = height / pixelPerLine;
+ const int scrollPosBottom = scrollLinesTotal - scrollLinesOnScreen;
+
+ if (0 <= scrollPosBottom)
+ scrWindow->Scroll(0, scrollPosBottom);
+ }
+}
+*/
+
+#ifdef FFS_WIN
+namespace
+{
+const bool useNewLocalTimeCalculation = zen::vistaOrLater();
+}
+#endif
+
+
+std::wstring zen::utcToLocalTimeString(Int64 utcTime)
+{
+#ifdef FFS_WIN
+ FILETIME lastWriteTimeUtc = tofiletime(utcTime); //convert ansi C time to FILETIME
+
+ SYSTEMTIME systemTimeLocal = {};
+
+ if (useNewLocalTimeCalculation) //use DST setting from source date (like in Windows 7, see http://msdn.microsoft.com/en-us/library/ms724277(VS.85).aspx)
+ {
+ SYSTEMTIME systemTimeUtc = {};
+ if (!::FileTimeToSystemTime(&lastWriteTimeUtc, //__in const FILETIME *lpFileTime,
+ &systemTimeUtc)) //__out LPSYSTEMTIME lpSystemTime
+ return _("Error");
+
+ if (!::SystemTimeToTzSpecificLocalTime(NULL, //__in_opt LPTIME_ZONE_INFORMATION lpTimeZone,
+ &systemTimeUtc, //__in LPSYSTEMTIME lpUniversalTime,
+ &systemTimeLocal)) //__out LPSYSTEMTIME lpLocalTime
+ return _("Error");
+ }
+ else //use DST setting (like in Windows 2000 and XP)
+ {
+ FILETIME fileTimeLocal = {};
+ if (!::FileTimeToLocalFileTime(&lastWriteTimeUtc, //pointer to UTC file time to convert
+ &fileTimeLocal)) //pointer to converted file time
+ return _("Error");
+
+ if (!::FileTimeToSystemTime(&fileTimeLocal, //pointer to file time to convert
+ &systemTimeLocal)) //pointer to structure to receive system time
+ return _("Error");
+ }
+
+ struct tm loc = {};
+ loc.tm_year = systemTimeLocal.wYear - 1900;
+ loc.tm_mon = systemTimeLocal.wMonth - 1;
+ loc.tm_mday = systemTimeLocal.wDay;
+ loc.tm_hour = systemTimeLocal.wHour;
+ loc.tm_min = systemTimeLocal.wMinute;
+ loc.tm_sec = systemTimeLocal.wSecond;
+ const struct tm* timePtr = &loc;
+
+#elif defined FFS_LINUX
+ const time_t fileTime = to<time_t>(utcTime);
+ const struct tm* timePtr = ::localtime(&fileTime); //convert to local time
+#endif
+
+ wchar_t buffer[1000];
+ size_t charsWritten = std::wcsftime(buffer, 1000, L"%x %X", timePtr);
+
+ return std::wstring(buffer, charsWritten);
+}
diff --git a/wx+/format_unit.h b/wx+/format_unit.h
new file mode 100644
index 00000000..4adf74f8
--- /dev/null
+++ b/wx+/format_unit.h
@@ -0,0 +1,64 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef UTIL_H_INCLUDED
+#define UTIL_H_INCLUDED
+
+#include <string>
+#include <zen/string_tools.h>
+#include <zen/int64.h>
+
+namespace zen
+{
+std::wstring filesizeToShortString(UInt64 filesize);
+std::wstring remainingTimeToShortString(double timeInSec);
+std::wstring percentageToShortString(double fraction); //within [0, 1]
+
+template <class NumberType>
+std::wstring toStringSep(NumberType number); //convert number to std::wstring including thousands separator
+
+std::wstring utcToLocalTimeString(Int64 utcTime); //throw std::runtime_error
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//--------------- inline impelementation -------------------------------------------
+namespace ffs_Impl
+{
+std::wstring includeNumberSeparator(const std::wstring& number);
+}
+
+template <class NumberType> inline
+std::wstring toStringSep(NumberType number)
+{
+ return ffs_Impl::includeNumberSeparator(zen::toString<std::wstring>(number));
+}
+}
+
+#endif // UTIL_H_INCLUDED
diff --git a/wx+/graph.cpp b/wx+/graph.cpp
new file mode 100644
index 00000000..584ef0ea
--- /dev/null
+++ b/wx+/graph.cpp
@@ -0,0 +1,540 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "graph.h"
+#include <cassert>
+#include <algorithm>
+#include <numeric>
+#include <zen/basic_math.h>
+#include <wx/settings.h>
+
+using namespace zen;
+using namespace numeric;
+
+
+//todo: support zoom via mouse wheel
+
+
+const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType();
+
+namespace
+{
+inline
+double bestFit(double val, double low, double high) { return val < (high + low) / 2 ? low : high; }
+}
+
+
+double zen::nextNiceNumber(double blockSize) //round to next number which is a convenient to read block size
+{
+ if (blockSize <= 0)
+ return 0;
+
+ const double k = std::floor(std::log10(blockSize));
+ const double e = std::pow(10, k);
+ const double a = blockSize / e; //blockSize = a * 10^k with a in (1, 10)
+
+ //have a look at leading two digits: "nice" numbers start with 1, 2, 2.5 and 5
+ if (a <= 2)
+ return bestFit(a, 1, 2) * e;
+ else if (a <= 2.5)
+ return bestFit(a, 2, 2.5) * e;
+ else if (a <= 5)
+ return bestFit(a, 2.5, 5) * e;
+ else if (a < 10)
+ return bestFit(a, 5, 10) * e;
+ else
+ {
+ assert(false);
+ return 10 * e;
+ }
+}
+
+
+namespace
+{
+wxColor getDefaultColor(size_t pos)
+{
+ pos %= 10;
+ switch (pos)
+ {
+ case 0:
+ return wxColor(0, 69, 134); //blue
+ case 1:
+ return wxColor(255, 66, 14); //red
+ case 2:
+ return wxColor(255, 211, 32); //yellow
+ case 3:
+ return wxColor(87, 157, 28); //green
+ case 4:
+ return wxColor(126, 0, 33); //royal
+ case 5:
+ return wxColor(131, 202, 255); //light blue
+ case 6:
+ return wxColor(49, 64, 4); //dark green
+ case 7:
+ return wxColor(174, 207, 0); //light green
+ case 8:
+ return wxColor(75, 31, 111); //purple
+ case 9:
+ return wxColor(255, 149, 14); //orange
+ default:
+ return *wxBLACK;
+ }
+}
+
+
+void drawYLabel(wxDC& dc, double& yMin, double& yMax, const wxRect& clientArea, int labelWidth, bool drawLeft, const LabelFormatter& labelFmt) //clientArea := y-label + data window
+{
+ //note: DON'T use wxDC::GetSize()! DC may be larger than visible area!
+ if (clientArea.GetHeight() <= 0 || clientArea.GetWidth() <= 0) return;
+
+ int optimalBlockHeight = 3 * dc.GetMultiLineTextExtent(wxT("1")).GetHeight();;
+
+ double valRangePerBlock = (yMax - yMin) * optimalBlockHeight / clientArea.GetHeight();
+ valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock);
+ if (numeric::isNull(valRangePerBlock)) return;
+
+ double yMinNew = std::floor(yMin / valRangePerBlock) * valRangePerBlock;
+ double yMaxNew = std::ceil (yMax / valRangePerBlock) * valRangePerBlock;
+ int blockCount = numeric::round((yMaxNew - yMinNew) / valRangePerBlock);
+ if (blockCount == 0) return;
+
+ yMin = yMinNew; //inform about adjusted y value range
+ yMax = yMaxNew;
+
+ //draw labels
+ {
+ wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey
+ dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("Arial") ));
+
+ const int posLabel = drawLeft ? 0 : clientArea.GetWidth() - labelWidth;
+ const int posDataArea = drawLeft ? labelWidth : 0;
+ const int widthDataArea = clientArea.GetWidth() - labelWidth;
+
+ const wxPoint origin = clientArea.GetTopLeft();
+
+ for (int i = 1; i < blockCount; ++i)
+ {
+ //draw grey horizontal lines
+ const int y = i * static_cast<double>(clientArea.GetHeight()) / blockCount;
+ if (widthDataArea > 0)
+ dc.DrawLine(wxPoint(posDataArea, y) + origin, wxPoint(posDataArea + widthDataArea - 1, y) + origin);
+
+ //draw y axis labels
+ const wxString label = labelFmt.formatText(yMaxNew - i * valRangePerBlock ,valRangePerBlock);
+ wxSize labelExtent = dc.GetMultiLineTextExtent(label);
+
+ labelExtent.x = std::max(labelExtent.x, labelWidth); //enlarge if possible to center horizontally
+
+ dc.DrawLabel(label, wxRect(wxPoint(posLabel, y - labelExtent.GetHeight() / 2) + origin, labelExtent), wxALIGN_CENTRE);
+ }
+ }
+}
+
+
+void drawXLabel(wxDC& dc, double& xMin, double& xMax, const wxRect& clientArea, int labelHeight, bool drawBottom, const LabelFormatter& labelFmt) //clientArea := x-label + data window
+{
+ //note: DON'T use wxDC::GetSize()! DC may be larger than visible area!
+ if (clientArea.GetHeight() <= 0 || clientArea.GetWidth() <= 0) return;
+
+ int optimalBlockWidth = dc.GetMultiLineTextExtent(wxT("100000000000000")).GetWidth();
+
+ double valRangePerBlock = (xMax - xMin) * optimalBlockWidth / clientArea.GetWidth();
+ valRangePerBlock = labelFmt.getOptimalBlockSize(valRangePerBlock);
+ if (numeric::isNull(valRangePerBlock)) return;
+
+ double xMinNew = std::floor(xMin / valRangePerBlock) * valRangePerBlock;
+ double xMaxNew = std::ceil (xMax / valRangePerBlock) * valRangePerBlock;
+ int blockCount = numeric::round((xMaxNew - xMinNew) / valRangePerBlock);
+ if (blockCount == 0) return;
+
+ xMin = xMinNew; //inform about adjusted x value range
+ xMax = xMaxNew;
+
+ //draw labels
+ {
+ wxDCPenChanger dummy(dc, wxPen(wxColor(192, 192, 192))); //light grey
+ dc.SetFont(wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxT("Arial") ));
+
+ const int posLabel = drawBottom ? clientArea.GetHeight() - labelHeight : 0;
+ const int posDataArea = drawBottom ? 0 : labelHeight;
+ const int heightDataArea = clientArea.GetHeight() - labelHeight;
+
+ const wxPoint origin = clientArea.GetTopLeft();
+
+ for (int i = 1; i < blockCount; ++i)
+ {
+ //draw grey vertical lines
+ const int x = i * static_cast<double>(clientArea.GetWidth()) / blockCount;
+ if (heightDataArea > 0)
+ dc.DrawLine(wxPoint(x, posDataArea) + origin, wxPoint(x, posDataArea + heightDataArea - 1) + origin);
+
+ //draw x axis labels
+ const wxString label = labelFmt.formatText(xMin + i * valRangePerBlock ,valRangePerBlock);
+ wxSize labelExtent = dc.GetMultiLineTextExtent(label);
+
+ labelExtent.y = std::max(labelExtent.y, labelHeight); //enlarge if possible to center vertically
+
+ dc.DrawLabel(label, wxRect(wxPoint(x - labelExtent.GetWidth() / 2, posLabel) + origin, labelExtent), wxALIGN_CENTRE);
+ }
+ }
+}
+
+
+class ConvertCoord //convert between screen and actual coordinates
+{
+public:
+ ConvertCoord(double valMin, double valMax, size_t screenSize) :
+ min_(valMin),
+ scaleToReal(screenSize == 0 ? 0 : (valMax - valMin) / screenSize),
+ scaleToScr(numeric::isNull(valMax - valMin) ? 0 : screenSize / (valMax - valMin)) {}
+
+ double screenToReal(double screenPos) const //input value: [0, screenSize - 1]
+ {
+ return screenPos * scaleToReal + min_; //come close to valMax, but NEVER reach it!
+ }
+ double realToScreen(double realPos) const //return screen position in pixel (but with double precision!)
+ {
+ return (realPos - min_) * scaleToScr;
+ }
+
+private:
+ const double min_;
+ const double scaleToReal;
+ const double scaleToScr;
+};
+
+
+template <class StdCont>
+void subsample(StdCont& cont, size_t factor)
+{
+ if (factor <= 1) return;
+
+ typedef typename StdCont::iterator IterType;
+
+ IterType posWrite = cont.begin();
+ for (IterType posRead = cont.begin(); cont.end() - posRead >= static_cast<int>(factor); posRead += factor) //don't even let iterator point out of range!
+ *posWrite++ = std::accumulate(posRead, posRead + factor, 0.0) / static_cast<double>(factor);
+
+ cont.erase(posWrite, cont.end());
+}
+}
+
+
+Graph2D::Graph2D(wxWindow* parent,
+ wxWindowID winid,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ wxPanel(parent, winid, pos, size, style, name)
+{
+ Connect(wxEVT_PAINT, wxPaintEventHandler(Graph2D::onPaintEvent), NULL, this);
+ Connect(wxEVT_SIZE, wxEventHandler(Graph2D::onRefreshRequired), NULL, this);
+ //http://wiki.wxwidgets.org/Flicker-Free_Drawing
+ Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(Graph2D::onEraseBackGround), NULL, this);
+
+#if wxCHECK_VERSION(2, 9, 1)
+ SetBackgroundStyle(wxBG_STYLE_PAINT);
+#else
+ SetBackgroundStyle(wxBG_STYLE_CUSTOM);
+#endif
+
+ Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(Graph2D::OnMouseLeftDown), NULL, this);
+ Connect(wxEVT_MOTION, wxMouseEventHandler(Graph2D::OnMouseMovement), NULL, this);
+ Connect(wxEVT_LEFT_UP, wxMouseEventHandler(Graph2D::OnMouseLeftUp), NULL, this);
+ Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(Graph2D::OnMouseCaptureLost), NULL, this);
+}
+
+
+void Graph2D::OnMouseLeftDown(wxMouseEvent& event)
+{
+ activeSel.reset(new MouseSelection(*this, event.GetPosition()));
+
+ if (!event.ControlDown())
+ oldSel.clear();
+
+ Refresh();
+}
+
+
+void Graph2D::OnMouseMovement(wxMouseEvent& event)
+{
+ if (activeSel.get())
+ {
+ activeSel->refCurrentPos() = event.GetPosition();
+ Refresh();
+ }
+}
+
+
+void Graph2D::OnMouseLeftUp(wxMouseEvent& event)
+{
+ if (activeSel.get())
+ {
+ if (activeSel->getStartPos() != activeSel->refCurrentPos()) //if it's just a single mouse click: discard selection
+ {
+ //fire off GraphSelectEvent
+ GraphSelectEvent evt(activeSel->refSelection());
+ GetEventHandler()->AddPendingEvent(evt);
+
+ oldSel.push_back(activeSel->refSelection());
+ }
+
+ activeSel.reset();
+ Refresh();
+ }
+}
+
+
+void Graph2D::OnMouseCaptureLost(wxMouseCaptureLostEvent& event)
+{
+ activeSel.reset();
+ Refresh();
+}
+
+
+void Graph2D::setData(const std::shared_ptr<GraphData>& data, const LineAttributes& la)
+{
+ curves_.clear();
+ addData(data, la);
+}
+
+
+void Graph2D::addData(const std::shared_ptr<GraphData>& data, const LineAttributes& la)
+{
+ LineAttributes newAttr = la;
+ if (newAttr.autoColor)
+ newAttr.setColor(getDefaultColor(curves_.size()));
+ curves_.push_back(std::make_pair(data, newAttr));
+ Refresh();
+}
+
+
+namespace
+{
+class DcBackgroundChanger
+{
+public:
+ DcBackgroundChanger(wxDC& dc, const wxBrush& brush) : dc_(dc), old(dc.GetBackground()) { dc.SetBackground(brush); }
+ ~DcBackgroundChanger() { if (old.Ok()) dc_.SetBackground(old); }
+private:
+ wxDC& dc_;
+ const wxBrush old;
+};
+}
+
+
+void Graph2D::render(wxDC& dc) const
+{
+ {
+ //have everything including label background in natural window color by default (overwriting current background color)
+ DcBackgroundChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); //sigh, who *invents* this stuff??? -> workaround for issue with wxBufferedPaintDC
+ //wxDCBrushChanger dummy(dc, *wxTRANSPARENT_BRUSH);
+ dc.Clear();
+ }
+
+ //note: DON'T use wxDC::GetSize()! DC may be larger than visible area!
+ /*
+ -----------------------
+ |y-label |data window |
+ |----------------------
+ | | x-label |
+ -----------------------
+ */
+ wxRect dataArea = GetClientSize(); //data window only
+ wxRect yLabelArea = GetClientSize(); //y-label + data window
+ wxRect xLabelArea = GetClientSize(); //x-label + data window
+
+ switch (attr.labelposX)
+ {
+ case X_LABEL_TOP:
+ dataArea.y += attr.labelHeightX;
+ dataArea.height -= attr.labelHeightX;
+ yLabelArea = dataArea;
+ break;
+ case X_LABEL_BOTTOM:
+ dataArea.height -= attr.labelHeightX;
+ yLabelArea = dataArea;
+ break;
+ case X_LABEL_NONE:
+ break;
+ }
+
+ switch (attr.labelposY)
+ {
+ case Y_LABEL_LEFT:
+ dataArea .x += attr.labelWidthY;
+ xLabelArea.x += attr.labelWidthY;
+ dataArea .width -= attr.labelWidthY;
+ xLabelArea.width -= attr.labelWidthY;
+ break;
+ case Y_LABEL_RIGHT:
+ dataArea .width -= attr.labelWidthY;
+ xLabelArea.width -= attr.labelWidthY;
+ break;
+ case Y_LABEL_NONE:
+ break;
+ }
+
+ {
+ //paint actual graph background (without labels) using window background color
+ DcBackgroundChanger dummy(dc, GetBackgroundColour());
+ wxDCPenChanger dummy2(dc, wxColour(130, 135, 144)); //medium grey, the same Win7 uses for other frame borders
+ //dc.DrawRectangle(static_cast<const wxRect&>(dataArea).Inflate(1, 1)); //correct wxWidgets design mistakes
+ dc.DrawRectangle(dataArea);
+ dataArea.Deflate(1, 1); //do not draw on border
+ }
+
+ //detect x value range
+ double minWndX = attr.minXauto ? HUGE_VAL : attr.minX; //automatic: ensure values are initialized by first curve
+ double maxWndX = attr.maxXauto ? -HUGE_VAL : attr.maxX; //
+ if (!curves_.empty())
+ {
+ for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j)
+ {
+ if (!j->first.get()) continue;
+ const GraphData& graph = *j->first;
+ assert(graph.getXBegin() <= graph.getXEnd());
+
+ if (attr.minXauto)
+ minWndX = std::min(minWndX, graph.getXBegin());
+ if (attr.maxXauto)
+ maxWndX = std::max(maxWndX, graph.getXEnd());
+ }
+ if (attr.labelposX != X_LABEL_NONE && //minWndX, maxWndX are just a suggestion, drawXLabel may enlarge them!
+ attr.labelFmtX.get())
+ drawXLabel(dc, minWndX, maxWndX, xLabelArea, attr.labelHeightX, attr.labelposX == X_LABEL_BOTTOM, *attr.labelFmtX);
+ }
+ if (minWndX < maxWndX) //valid x-range
+ {
+ //detect y value range
+ std::vector<std::pair<std::vector<double>, int>> yValuesList(curves_.size());
+ double minWndY = attr.minYauto ? HUGE_VAL : attr.minY; //automatic: ensure values are initialized by first curve
+ double maxWndY = attr.maxYauto ? -HUGE_VAL : attr.maxY; //
+ if (!curves_.empty())
+ {
+ const int avgFactor = 2; //some averaging of edgy input data to smoothen behavior on window resize
+ const ConvertCoord cvrtX(minWndX, maxWndX, dataArea.width * avgFactor);
+
+ for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j)
+ {
+ if (!j->first.get()) continue;
+ const GraphData& graph = *j->first;
+
+ std::vector<double>& yValues = yValuesList[j - curves_.begin()].first; //actual y-values
+ int& offset = yValuesList[j - curves_.begin()].second; //x-value offset in pixel
+ {
+ const int posFirst = std::max<int>(std::ceil(cvrtX.realToScreen(graph.getXBegin())), 0); //evaluate visible area only and make sure to not step one pixel before xbegin()!
+ const int postLast = std::min<int>(std::floor(cvrtX.realToScreen(graph.getXEnd())), dataArea.width * avgFactor); //
+
+ for (int i = posFirst; i < postLast; ++i)
+ yValues.push_back(graph.getValue(cvrtX.screenToReal(i)));
+
+ subsample(yValues, avgFactor);
+ offset = posFirst / avgFactor;
+ }
+
+ if (!yValues.empty())
+ {
+ if (attr.minYauto)
+ minWndY = std::min(minWndY, *std::min_element(yValues.begin(), yValues.end()));
+ if (attr.maxYauto)
+ maxWndY = std::max(maxWndY, *std::max_element(yValues.begin(), yValues.end()));
+ }
+ }
+ }
+ if (minWndY < maxWndY) //valid y-range
+ {
+ if (attr.labelposY != Y_LABEL_NONE && //minWnd, maxWndY are just a suggestion, drawYLabel may enlarge them!
+ attr.labelFmtY.get())
+ drawYLabel(dc, minWndY, maxWndY, yLabelArea, attr.labelWidthY, attr.labelposY == Y_LABEL_LEFT, *attr.labelFmtY);
+
+ const ConvertCoord cvrtY(minWndY, maxWndY, dataArea.height <= 0 ? 0 : dataArea.height - 1); //both minY/maxY values will be actually evaluated in contrast to maxX => - 1
+ const ConvertCoord cvrtX(minWndX, maxWndX, dataArea.width);
+
+ const wxPoint dataOrigin = dataArea.GetTopLeft();
+
+ //update active mouse selection
+ if (activeSel.get() &&
+ dataArea.width > 0 &&
+ dataArea.height > 0)
+ {
+ wxPoint startPos = activeSel->getStartPos() - dataOrigin; //pos relative to dataArea
+ wxPoint currentPos = activeSel->refCurrentPos() - dataOrigin;
+
+ //normalize positions
+ confine(startPos .x, 0, dataArea.width); //allow for one past the end(!) to enable "full range selections"
+ confine(currentPos.x, 0, dataArea.width); //
+
+ confine(startPos .y, 0, dataArea.height); //
+ confine(currentPos.y, 0, dataArea.height); //
+
+ //save current selection as double coordinates
+ activeSel->refSelection().from = SelectionBlock::Point(cvrtX.screenToReal(startPos.x + 0.5), //+0.5 start selection in the middle of a pixel
+ cvrtY.screenToReal(startPos.y + 0.5));
+ activeSel->refSelection().to = SelectionBlock::Point(cvrtX.screenToReal(currentPos.x + 0.5),
+ cvrtY.screenToReal(currentPos.y + 0.5));
+ }
+ //draw all currently set mouse selections (including active selection)
+ std::vector<SelectionBlock> allSelections = oldSel;
+ if (activeSel)
+ allSelections.push_back(activeSel->refSelection());
+ {
+ wxColor colSelect(168, 202, 236); //light blue
+ //wxDCBrushChanger dummy(dc, *wxTRANSPARENT_BRUSH);
+ wxDCBrushChanger dummy(dc, colSelect); //alpha channel (not yet) supported on wxMSW, so draw selection before graphs
+
+ wxPen selPen(colSelect);
+ //wxPen selPen(*wxBLACK);
+ //selPen.SetStyle(wxSHORT_DASH);
+ wxDCPenChanger dummy2(dc, selPen);
+
+ for (auto i = allSelections.begin(); i != allSelections.end(); ++i)
+ {
+ const wxPoint pixelFrom = wxPoint(cvrtX.realToScreen(i->from.x),
+ cvrtY.realToScreen(i->from.y)) + dataOrigin;
+ const wxPoint pixelTo = wxPoint(cvrtX.realToScreen(i->to.x),
+ cvrtY.realToScreen(i->to.y)) + dataOrigin;
+
+ switch (attr.mouseSelMode)
+ {
+ case SELECT_NONE:
+ break;
+ case SELECT_RECTANGLE:
+ dc.DrawRectangle(wxRect(pixelFrom, pixelTo));
+ break;
+ case SELECT_X_AXIS:
+ dc.DrawRectangle(wxRect(wxPoint(pixelFrom.x, dataArea.y), wxPoint(pixelTo.x, dataArea.y + dataArea.height - 1)));
+ break;
+ case SELECT_Y_AXIS:
+ dc.DrawRectangle(wxRect(wxPoint(dataArea.x, pixelFrom.y), wxPoint(dataArea.x + dataArea.width - 1, pixelTo.y)));
+ break;
+ }
+ }
+ }
+
+ //finally draw curves
+ for (GraphList::const_iterator j = curves_.begin(); j != curves_.end(); ++j)
+ {
+ std::vector<double>& yValues = yValuesList[j - curves_.begin()].first; //actual y-values
+ int offset = yValuesList[j - curves_.begin()].second; //x-value offset in pixel
+
+ std::vector<wxPoint> curve;
+ for (std::vector<double>::const_iterator i = yValues.begin(); i != yValues.end(); ++i)
+ curve.push_back(wxPoint(i - yValues.begin() + offset,
+ dataArea.height - 1 - cvrtY.realToScreen(*i)) + dataOrigin); //screen y axis starts upper left
+
+ if (!curve.empty())
+ {
+ dc.SetPen(wxPen(j->second.color, j->second.lineWidth));
+ dc.DrawLines(curve.size(), &curve[0]);
+ }
+ }
+ }
+ }
+}
diff --git a/wx+/graph.h b/wx+/graph.h
new file mode 100644
index 00000000..61a90ca1
--- /dev/null
+++ b/wx+/graph.h
@@ -0,0 +1,325 @@
+// **************************************************************************
+// * This file is part of the zenXML project. It is distributed under the *
+// * Boost Software License, Version 1.0. See accompanying file *
+// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. *
+// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef WX_PLOT_HEADER_2344252459
+#define WX_PLOT_HEADER_2344252459
+
+#include <vector>
+#include <memory>
+#include <wx/panel.h>
+#include <wx/dcbuffer.h>
+#include <zen/string_tools.h>
+
+//simple 2D graph as wxPanel specialization
+
+namespace zen
+{
+/*
+Example:
+ //init graph (optional)
+ m_panelGraph->setAttributes(Graph2D::GraphAttributes().
+ setLabelX(Graph2D::POSLX_BOTTOM, 20, std::make_shared<LabelFormatterTimeElapsed>()).
+ setLabelY(Graph2D::POSLY_RIGHT, 60, std::make_shared<LabelFormatterBytes>()));
+ //set graph data
+ std::shared_ptr<GraphData>() graphDataBytes = ...
+ m_panelGraph->setData(graphDataBytes, Graph2D::LineAttributes().setLineWidth(2).setColor(wxColor(0, 192, 0)));
+*/
+
+//------------------------------------------------------------------------------------------------------------
+struct GraphData
+{
+ virtual ~GraphData() {}
+ virtual double getValue(double x) const = 0;
+ virtual double getXBegin() const = 0;
+ virtual double getXEnd() const = 0; //upper bound for x, getValue() is NOT evaluated at this position! Similar to std::vector::end()
+};
+
+
+//reference data implementation
+
+class RangeData : public GraphData
+{
+public:
+ std::vector<double>& refData() { return data; }
+
+private:
+ virtual double getValue(double x) const
+ {
+ const size_t pos = static_cast<size_t>(x);
+ return pos < data.size() ? data[pos] : 0;
+ }
+ virtual double getXBegin() const { return 0; }
+ virtual double getXEnd() const { return data.size(); } //example: two-element range is accessible within [0, 2)
+
+ std::vector<double> data;
+};
+
+
+//reference data implementation
+class VectorData : public GraphData
+{
+public:
+ operator std::vector<double>& () { return data; }
+
+private:
+ virtual double getValue(double x) const
+ {
+ const size_t pos = static_cast<size_t>(x);
+ return pos < data.size() ? data[pos] : 0;
+ }
+ virtual double getXBegin() const { return 0; }
+ virtual double getXEnd() const { return data.size(); } //example: two-element range is accessible within [0, 2)
+
+ std::vector<double> data;
+};
+
+//------------------------------------------------------------------------------------------------------------
+struct LabelFormatter
+{
+ virtual ~LabelFormatter() {}
+
+ //determine convenient graph label block size in unit of data: usually some small deviation on "sizeProposed"
+ virtual double getOptimalBlockSize(double sizeProposed) const = 0;
+
+ //create human-readable text for x or y-axis position
+ virtual wxString formatText(double value, double optimalBlockSize) const = 0;
+};
+
+double nextNiceNumber(double blockSize); //round to next number which is convenient to read, e.g. 2.13 -> 2; 2.7 -> 2.5; 7 -> 5
+
+struct DecimalNumberFormatter : public LabelFormatter
+{
+ virtual double getOptimalBlockSize(double sizeProposed) const { return nextNiceNumber(sizeProposed); }
+ virtual wxString formatText(double value, double optimalBlockSize) const { return zen::toString<wxString>(value); }
+};
+
+//------------------------------------------------------------------------------------------------------------
+//emit data selection event
+//usage: wnd.Connect(wxEVT_GRAPH_SELECTION, GraphSelectEventHandler(MyDlg::OnGraphSelection), NULL, this);
+// void MyDlg::OnGraphSelection(GraphSelectEvent& event);
+
+
+extern const wxEventType wxEVT_GRAPH_SELECTION;
+
+struct SelectionBlock
+{
+ struct Point
+ {
+ Point() : x(0), y(0) {}
+ Point(double xVal, double yVal) : x(xVal), y(yVal) {}
+ double x;
+ double y;
+ };
+
+ Point from;
+ Point to;
+};
+
+class GraphSelectEvent : public wxCommandEvent
+{
+public:
+ GraphSelectEvent(const SelectionBlock& selBlock) : wxCommandEvent(wxEVT_GRAPH_SELECTION), selBlock_(selBlock_) {}
+
+ virtual wxEvent* Clone() const { return new GraphSelectEvent(selBlock_); }
+
+ SelectionBlock getSelection() { return selBlock_; }
+
+private:
+ SelectionBlock selBlock_;
+};
+
+typedef void (wxEvtHandler::*GraphSelectEventFunction)(GraphSelectEvent&);
+
+#define GraphSelectEventHandler(func) \
+ (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GraphSelectEventFunction, &func)
+
+
+//------------------------------------------------------------------------------------------------------------
+class Graph2D : public wxPanel
+{
+public:
+ Graph2D(wxWindow* parent,
+ wxWindowID winid = wxID_ANY,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = wxTAB_TRAVERSAL | wxNO_BORDER,
+ const wxString& name = wxPanelNameStr);
+
+ class LineAttributes
+ {
+ public:
+ LineAttributes() : autoColor(true), lineWidth(2) {}
+
+ LineAttributes& setColor(const wxColour& col) { color = col; autoColor = false; return *this; }
+ LineAttributes& setLineWidth(size_t width) { lineWidth = width; return *this; }
+
+ private:
+ friend class Graph2D;
+
+ bool autoColor;
+ wxColour color;
+ size_t lineWidth;
+ };
+
+ void setData(const std::shared_ptr<GraphData>& data, const LineAttributes& attr = LineAttributes());
+ void addData(const std::shared_ptr<GraphData>& data, const LineAttributes& attr = LineAttributes());
+
+ enum PosLabelY
+ {
+ Y_LABEL_LEFT,
+ Y_LABEL_RIGHT,
+ Y_LABEL_NONE
+ };
+
+ enum PosLabelX
+ {
+ X_LABEL_TOP,
+ X_LABEL_BOTTOM,
+ X_LABEL_NONE
+ };
+
+ enum SelMode
+ {
+ SELECT_NONE,
+ SELECT_RECTANGLE,
+ SELECT_X_AXIS,
+ SELECT_Y_AXIS,
+ };
+
+ class GraphAttributes
+ {
+ public:
+ GraphAttributes() :
+ minXauto(true),
+ maxXauto(true),
+ minX(0),
+ maxX(0),
+ minYauto(true),
+ maxYauto(true),
+ minY(0),
+ maxY(0),
+ labelposX(X_LABEL_BOTTOM),
+ labelHeightX(25),
+ labelFmtX(new DecimalNumberFormatter()),
+ labelposY(Y_LABEL_LEFT),
+ labelWidthY(60),
+ labelFmtY(new DecimalNumberFormatter()),
+ mouseSelMode(SELECT_RECTANGLE) {}
+
+
+ GraphAttributes& setMinX(double newMinX) { minX = newMinX; minXauto = false; return *this; }
+ GraphAttributes& setMaxX(double newMaxX) { maxX = newMaxX; maxXauto = false; return *this; }
+
+ GraphAttributes& setMinY(double newMinY) { minY = newMinY; minYauto = false; return *this; }
+ GraphAttributes& setMaxY(double newMaxY) { maxY = newMaxY; maxYauto = false; return *this; }
+
+ GraphAttributes& setAutoSize() { minXauto = true; maxXauto = true; minYauto = true; maxYauto = true; return *this; }
+
+ GraphAttributes& setLabelX(PosLabelX posX, size_t height = 25, const std::shared_ptr<LabelFormatter>& newLabelFmt = std::shared_ptr<LabelFormatter>(new DecimalNumberFormatter()))
+ {
+ labelposX = posX;
+ labelHeightX = height;
+ labelFmtX = newLabelFmt;
+ return *this;
+ }
+ GraphAttributes& setLabelY(PosLabelY posY, size_t width = 60, const std::shared_ptr<LabelFormatter>& newLabelFmt = std::shared_ptr<LabelFormatter>(new DecimalNumberFormatter()))
+ {
+ labelposY = posY;
+ labelWidthY = width;
+ labelFmtY = newLabelFmt;
+ return *this;
+ }
+
+ GraphAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; }
+
+ private:
+ friend class Graph2D;
+
+ bool minXauto; //autodetect range for X value
+ bool maxXauto;
+ double minX; //x-range to visualize
+ double maxX;
+
+ bool minYauto; //autodetect range for Y value
+ bool maxYauto;
+ double minY; //y-range to visualize
+ double maxY;
+
+ PosLabelX labelposX;
+ size_t labelHeightX;
+ std::shared_ptr<LabelFormatter> labelFmtX;
+
+ PosLabelY labelposY;
+ size_t labelWidthY;
+ std::shared_ptr<LabelFormatter> labelFmtY;
+
+ SelMode mouseSelMode;
+ };
+ void setAttributes(const GraphAttributes& newAttr) { attr = newAttr; Refresh(); }
+ GraphAttributes getAttributes() const { return attr; }
+
+
+ std::vector<SelectionBlock> getSelections() const { return oldSel; }
+ void setSelections(const std::vector<SelectionBlock>& sel)
+ {
+ oldSel = sel;
+ activeSel.reset();
+ Refresh();
+ }
+ void clearSelection() { oldSel.clear(); Refresh(); }
+
+private:
+ void OnMouseLeftDown(wxMouseEvent& event);
+ void OnMouseMovement(wxMouseEvent& event);
+ void OnMouseLeftUp (wxMouseEvent& event);
+ void OnMouseCaptureLost(wxMouseCaptureLostEvent& event);
+
+ void onPaintEvent(wxPaintEvent& evt)
+ {
+ wxAutoBufferedPaintDC dc(this); //double-buffer only on systems that require it
+ render(dc);
+ }
+
+ void onRefreshRequired(wxEvent& evt)
+ {
+ Refresh();
+ evt.Skip();
+ }
+
+ void onEraseBackGround(wxEraseEvent& evt) {}
+
+ void render(wxDC& dc) const;
+
+ class MouseSelection
+ {
+ public:
+ MouseSelection(wxWindow& wnd, const wxPoint& posDragStart) : wnd_(wnd), posDragStart_(posDragStart), posDragCurrent(posDragStart) { wnd_.CaptureMouse(); }
+ ~MouseSelection() { if (wnd_.HasCapture()) wnd_.ReleaseMouse(); }
+
+ wxPoint getStartPos() const { return posDragStart_; }
+ wxPoint& refCurrentPos() { return posDragCurrent; }
+
+ SelectionBlock& refSelection() { return selBlock; } //set when selection is drawn: this is fine, 'cause only what's shown should be selected!
+
+ private:
+ wxWindow& wnd_;
+ const wxPoint posDragStart_;
+ wxPoint posDragCurrent;
+ SelectionBlock selBlock;
+ };
+ std::vector<SelectionBlock> oldSel; //applied selections
+ std::shared_ptr<MouseSelection> activeSel; //set during mouse selection
+
+ GraphAttributes attr; //global attributes
+
+ typedef std::vector<std::pair<std::shared_ptr<GraphData>, LineAttributes>> GraphList;
+ GraphList curves_;
+};
+}
+
+
+#endif //WX_PLOT_HEADER_2344252459
diff --git a/wx+/image_tools.h b/wx+/image_tools.h
new file mode 100644
index 00000000..e78e7ced
--- /dev/null
+++ b/wx+/image_tools.h
@@ -0,0 +1,157 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef IMAGE_TOOLS_HEADER_45782456427634254
+#define IMAGE_TOOLS_HEADER_45782456427634254
+
+#include <numeric>
+#include <wx/bitmap.h>
+#include <wx/dcmemory.h>
+
+
+namespace zen
+{
+wxBitmap greyScale(const wxBitmap& bmp); //greyscale + brightness adaption
+wxBitmap layOver(const wxBitmap& foreground, const wxBitmap& background); //merge
+
+void move(wxImage& img, int up, int left = 0);
+void adjustBrightness(wxImage& img, int targetLevel);
+double getAvgBrightness(const wxImage& img); //in [0, 255]
+void brighten(wxImage& img, int level); //level: delta per channel in points
+
+bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs); //pixel-wise equality (respecting alpha channel)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//################################### implementation ###################################
+inline
+void move(wxImage& img, int up, int left)
+{
+ img = img.GetSubImage(wxRect(std::max(0, left), std::max(0, up), img.GetWidth() - abs(left), img.GetHeight() - abs(up)));
+ img.Resize(wxSize(img.GetWidth() + abs(left), img.GetHeight() + abs(up)), wxPoint(-std::min(0, left), -std::min(0, up)));
+}
+
+
+inline
+wxBitmap greyScale(const wxBitmap& bmp)
+{
+ wxImage output = bmp.ConvertToImage().ConvertToGreyscale(1.0/3, 1.0/3, 1.0/3); //treat all channels equally!
+ //wxImage output = bmp.ConvertToImage().ConvertToGreyscale();
+ adjustBrightness(output, 170);
+ return output;
+}
+
+
+inline
+double getAvgBrightness(const wxImage& img)
+{
+ const int pixelCount = img.GetWidth() * img.GetHeight();
+ auto pixBegin = img.GetData();
+ if (pixBegin)
+ {
+ auto pixEnd = pixBegin + 3 * pixelCount; //RGB
+
+ if (img.HasAlpha())
+ {
+ const unsigned char* alphaFirst = img.GetAlpha();
+
+ //calculate average weighted by alpha channel
+ double dividend = 0;
+ for (auto iter = pixBegin; iter != pixEnd; ++iter)
+ dividend += *iter * static_cast<double>(alphaFirst[(iter - pixBegin) / 3]);
+
+ const int divisor = 3.0 * std::accumulate(alphaFirst, alphaFirst + pixelCount, 0.0);
+
+ return dividend / divisor;
+ }
+ else
+ return std::accumulate(pixBegin, pixEnd, 0.0) / (3.0 * pixelCount);
+ }
+ return 0;
+}
+
+
+inline
+void brighten(wxImage& img, int level)
+{
+ const int pixelCount = img.GetWidth() * img.GetHeight();
+ auto pixBegin = img.GetData();
+ if (pixBegin)
+ {
+ auto pixEnd = pixBegin + 3 * pixelCount; //RGB
+ if (level > 0)
+ std::for_each(pixBegin, pixEnd, [&](unsigned char& c) { c = std::min(255, c + level); });
+ else
+ std::for_each(pixBegin, pixEnd, [&](unsigned char& c) { c = std::max(0, c + level); });
+ }
+}
+
+
+inline
+void adjustBrightness(wxImage& img, int targetLevel)
+{
+ brighten(img, targetLevel - getAvgBrightness(img));
+}
+
+
+inline
+wxBitmap layOver(const wxBitmap& foreground, const wxBitmap& background)
+{
+ wxBitmap output = background;
+ {
+ wxMemoryDC dc;
+ dc.SelectObject(output);
+ dc.DrawBitmap(foreground, 0, 0, true);
+ dc.SelectObject(wxNullBitmap);
+ }
+ return output;
+}
+
+
+inline
+bool isEqual(const wxBitmap& lhs, const wxBitmap& rhs)
+{
+ if (lhs.IsOk() != rhs.IsOk())
+ return false;
+ if (!lhs.IsOk())
+ return true;
+
+ const int pixelCount = lhs.GetWidth() * lhs.GetHeight();
+ if (pixelCount != rhs.GetWidth() * rhs.GetHeight())
+ return false;
+
+ wxImage imLhs = lhs.ConvertToImage();
+ wxImage imRhs = rhs.ConvertToImage();
+
+ if (imLhs.HasAlpha() != imRhs.HasAlpha())
+ return false;
+
+ if (imLhs.HasAlpha())
+ {
+ if (!std::equal(imLhs.GetAlpha(), imLhs.GetAlpha() + pixelCount, imRhs.GetAlpha()))
+ return false;
+ }
+
+ return std::equal(imLhs.GetData(), imLhs.GetData() + pixelCount * 3, imRhs.GetData());
+}
+}
+
+
+#endif //IMAGE_TOOLS_HEADER_45782456427634254
diff --git a/wx+/mouse_move_dlg.cpp b/wx+/mouse_move_dlg.cpp
new file mode 100644
index 00000000..3f7ca755
--- /dev/null
+++ b/wx+/mouse_move_dlg.cpp
@@ -0,0 +1,72 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "mouse_move_dlg.h"
+#include <vector>
+#include <zen/win.h> //includes "windows.h"
+#include <wx/stattext.h>
+#include <wx/statbmp.h>
+#include <wx/statline.h>
+#include <wx/animate.h>
+#include <wx/panel.h>
+#include <wx/gauge.h>
+#include <wx/statusbr.h>
+
+using namespace zen;
+
+
+namespace
+{
+template <class Fun> inline
+void forEachChild(wxWindow& parent, Fun f)
+{
+ wxWindowList& wl = parent.GetChildren();
+ for (auto iter = wl.begin(); iter != wl.end(); ++iter) //yet another wxWidgets bug keeps us from using std::for_each
+ {
+ wxWindow& wnd = **iter;
+ f(wnd);
+ forEachChild(wnd, f);
+ }
+}
+}
+
+MouseMoveWindow::MouseMoveWindow(wxWindow& parent, bool includeParent) : wxWindow(&parent, wxID_ANY)
+{
+ wxObjectEventFunction memFunMouseDown = wxMouseEventHandler(MouseMoveWindow::LeftButtonDown);
+ auto connect = [&](wxWindow& wnd)
+ {
+ if (dynamic_cast<wxStaticText*> (&wnd) || //redirect clicks on these "dead" controls to move dialog instead
+ dynamic_cast<wxStaticBitmap*> (&wnd) ||
+ dynamic_cast<wxAnimationCtrl*>(&wnd) ||
+ dynamic_cast<wxGauge*> (&wnd) ||
+ dynamic_cast<wxStaticLine*> (&wnd) ||
+ dynamic_cast<wxStatusBar*> (&wnd) ||
+ dynamic_cast<wxPanel*> (&wnd))
+ wnd.Connect(wxEVT_LEFT_DOWN, memFunMouseDown, NULL, this); //wxWidgets macros are obviously not C++11 ready
+ };
+
+ if (includeParent)
+ connect(parent);
+ forEachChild(parent, connect);
+
+ Hide(); //this is just a dummy window so that its parent can have ownership
+ Disable();
+}
+
+
+void MouseMoveWindow::LeftButtonDown(wxMouseEvent& event)
+{
+ if (GetParent() && allowMove(event))
+ {
+ ::ReleaseCapture();
+ //::SendMessage(GetHwndOf(dialogToMove_), WM_NCLBUTTONDOWN, HTCAPTION, 0);
+ ::SendMessage(static_cast<HWND>(GetParent()->GetHWND()), WM_NCLBUTTONDOWN, HTCAPTION, 0);
+
+ return;
+ //event.Skip(); -> swallow event, to avoid other windows losing focus
+ }
+ event.Skip();
+}
diff --git a/wx+/mouse_move_dlg.h b/wx+/mouse_move_dlg.h
new file mode 100644
index 00000000..44988e3a
--- /dev/null
+++ b/wx+/mouse_move_dlg.h
@@ -0,0 +1,33 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef MOUSEMOVEWINDOW_H_INCLUDED
+#define MOUSEMOVEWINDOW_H_INCLUDED
+
+#include <wx/window.h>
+
+namespace zen
+{
+
+/*
+move dialog by mouse-dragging contained sub-windows: just attach to parent via new in constructor:
+
+Syntax:
+ new MouseMoveWindow(parent); //ownership passed to parent
+*/
+class MouseMoveWindow : public wxWindow //private wxEvtHandler
+{
+public:
+ MouseMoveWindow(wxWindow& parent, bool includeParent = true); //parent including all relevant child elements
+
+ virtual bool allowMove(const wxMouseEvent& event) { return true; }
+
+private:
+ void LeftButtonDown(wxMouseEvent& event);
+};
+}
+
+#endif // MOUSEMOVEWINDOW_H_INCLUDED
diff --git a/wx+/pch.h b/wx+/pch.h
new file mode 100644
index 00000000..acc03012
--- /dev/null
+++ b/wx+/pch.h
@@ -0,0 +1,106 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef FFS_PRECOMPILED_HEADER
+#define FFS_PRECOMPILED_HEADER
+
+//pay attention when using this file: might cause issues!
+#ifdef NDEBUG
+#error do NOT use in release build!
+#endif
+
+//#####################################################
+// basic wxWidgets headers
+#ifndef WX_PRECOMP
+#define WX_PRECOMP
+#endif
+
+#include <wx/wxprec.h> //#includes <wx/msw/wrapwin.h>
+
+//other wxWidgets headers
+#include <wx/log.h>
+#include <wx/grid.h>
+#include <wx/animate.h>
+#include <wx/app.h>
+#include <wx/arrstr.h>
+#include <wx/bitmap.h>
+#include <wx/bmpbuttn.h>
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/choice.h>
+#include <wx/clipbrd.h>
+#include <wx/cmdline.h>
+#include <wx/colour.h>
+#include <wx/config.h>
+#include <wx/dc.h>
+#include <wx/dialog.h>
+#include <wx/dir.h>
+#include <wx/dnd.h>
+#include <wx/file.h>
+#include <wx/filename.h>
+#include <wx/filepicker.h>
+#include <wx/font.h>
+#include <wx/frame.h>
+#include <wx/gauge.h>
+#include <wx/gdicmn.h>
+#include <wx/grid.h>
+#include <wx/hyperlink.h>
+#include <wx/icon.h>
+#include <wx/image.h>
+#include <wx/intl.h>
+#include <wx/log.h>
+#include <wx/menu.h>
+#include <wx/msgdlg.h>
+#include <wx/panel.h>
+#include <wx/radiobut.h>
+#include <wx/settings.h>
+#include <wx/sizer.h>
+#include <wx/statbmp.h>
+#include <wx/statbox.h>
+#include <wx/statline.h>
+#include <wx/stattext.h>
+#include <wx/stdpaths.h>
+#include <wx/stopwatch.h>
+#include <wx/stream.h>
+#include <wx/string.h>
+#include <wx/textctrl.h>
+#include <wx/thread.h>
+#include <wx/utils.h>
+#include <wx/wfstream.h>
+#include <wx/zipstrm.h>
+#include <wx/scrolwin.h>
+#include <wx/notebook.h>
+#include <wx/help.h>
+#include <wx/event.h>
+
+//#####################################################
+// #include other rarely changing headers here
+
+//STL headers
+#include <string>
+#include <vector>
+#include <set>
+#include <map>
+#include <queue>
+#include <deque>
+#include <stack>
+#include <list>
+#include <algorithm>
+#include <functional>
+#include <iterator>
+#include <numeric>
+#include <memory>
+#include <utility>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+#include <new>
+#include <stdexcept>
+
+//Boost
+#include <boost/scoped_array.hpp>
+
+#endif //FFS_PRECOMPILED_HEADER
diff --git a/wx+/serialize.h b/wx+/serialize.h
new file mode 100644
index 00000000..c15e963d
--- /dev/null
+++ b/wx+/serialize.h
@@ -0,0 +1,274 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef SERIALIZE_H_INCLUDED
+#define SERIALIZE_H_INCLUDED
+
+#include <vector>
+#include <cstdint>
+#include <memory>
+#include <wx/stream.h>
+#include <zen/file_error.h>
+#include <zen/file_io.h>
+
+
+namespace zen
+{
+//unchecked, unformatted serialization
+template <class T> T readPOD (wxInputStream& stream);
+template <class T> void writePOD(wxOutputStream& stream, const T& pod);
+
+template <class S> S readString (wxInputStream& stream);
+template <class S> void writeString(wxOutputStream& stream, const S& str);
+
+
+//############# wxWidgets stream adapter #############
+// can be used as base classes (have virtual destructors)
+class FileInputStream : public wxInputStream
+{
+public:
+ FileInputStream(const Zstring& filename) : //throw FileError
+ fileObj(filename) {}
+
+private:
+ virtual size_t OnSysRead(void* buffer, size_t bufsize)
+ {
+ return fileObj.read(buffer, bufsize); //throw FileError
+ }
+
+ zen::FileInput fileObj;
+};
+
+
+class FileOutputStream : public wxOutputStream
+{
+public:
+ FileOutputStream(const Zstring& filename) : //throw FileError
+ fileObj(filename, zen::FileOutput::ACC_OVERWRITE) {}
+
+private:
+ virtual size_t OnSysWrite(const void* buffer, size_t bufsize)
+ {
+ fileObj.write(buffer, bufsize); //throw FileError
+ return bufsize;
+ }
+
+ zen::FileOutput fileObj;
+};
+
+
+
+class ReadInputStream //throw FileError
+{
+protected:
+ ReadInputStream(wxInputStream& stream, const wxString& errorObjName) : stream_(stream), errorObjName_(errorObjName) {}
+
+ template <class T>
+ T readNumberC() const; //throw FileError, checked read operation
+
+ template <class S>
+ S readStringC() const; //throw FileError, checked read operation
+
+ typedef std::shared_ptr<std::vector<char> > CharArray; //there's no guarantee std::string has a ref-counted implementation... so use this "thing"
+ CharArray readArrayC() const; //throw FileError
+
+ void check() const;
+
+ wxInputStream& getStream() { return stream_; }
+
+private:
+ wxInputStream& stream_;
+ const wxString& errorObjName_; //used for error text only
+};
+
+
+class WriteOutputStream //throw FileError
+{
+protected:
+ WriteOutputStream(const wxString& errorObjName, wxOutputStream& stream) : stream_(stream), errorObjName_(errorObjName) {}
+
+ template <class T>
+ void writeNumberC(T number) const; //throw FileError, checked write operation
+
+ template <class S>
+ void writeStringC(const S& str) const; //throw FileError, checked write operation
+
+ void writeArrayC(const std::vector<char>& buffer) const; //throw FileError
+
+ void check() const;
+
+ wxOutputStream& getStream() { return stream_; }
+
+private:
+ wxOutputStream& stream_;
+ const wxString& errorObjName_; //used for error text only!
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+//-----------------------implementation-------------------------------
+template <class T> inline
+T readPOD(wxInputStream& stream)
+{
+ T pod = 0;
+ stream.Read(reinterpret_cast<char*>(&pod), sizeof(T));
+ return pod;
+}
+
+
+template <class T> inline
+void writePOD(wxOutputStream& stream, const T& pod)
+{
+ stream.Write(reinterpret_cast<const char*>(&pod), sizeof(T));
+}
+
+
+template <class S> inline
+S readString(wxInputStream& stream)
+{
+ typedef typename S::value_type CharType;
+
+ const auto strLength = readPOD<std::uint32_t>(stream);
+ if (strLength <= 1000)
+ {
+ CharType buffer[1000];
+ stream.Read(buffer, sizeof(CharType) * strLength);
+ return S(buffer, strLength);
+ }
+ else
+ {
+ std::vector<CharType> buffer(strLength); //throw std::bad_alloc
+ stream.Read(&buffer[0], sizeof(CharType) * strLength);
+ return S(&buffer[0], strLength);
+ }
+}
+
+
+template <class S> inline
+void writeString(wxOutputStream& stream, const S& str)
+{
+ writePOD(stream, static_cast<std::uint32_t>(str.length()));
+ stream.Write(str.c_str(), sizeof(typename S::value_type) * str.length());
+}
+
+
+inline
+void ReadInputStream::check() const
+{
+ if (stream_.GetLastError() != wxSTREAM_NO_ERROR)
+ throw zen::FileError(_("Error reading from synchronization database:") + " \n" + "\"" + errorObjName_.c_str() + "\"");
+}
+
+
+template <class T>
+inline
+T ReadInputStream::readNumberC() const //checked read operation
+{
+ T output = readPOD<T>(stream_);
+ check();
+ return output;
+}
+
+
+template <class S> inline
+S ReadInputStream::readStringC() const //checked read operation
+{
+ S output;
+ try
+ {
+ output = readString<S>(stream_); //throw (std::bad_alloc)
+ check();
+ }
+ catch (std::exception&)
+ {
+ throw FileError(_("Error reading from synchronization database:") + " \n" + "\"" + errorObjName_.c_str() + "\"");
+ }
+ return output;
+}
+
+
+inline
+ReadInputStream::CharArray ReadInputStream::readArrayC() const
+{
+ const std::uint32_t byteCount = readNumberC<std::uint32_t>();
+ CharArray buffer(new std::vector<char>(byteCount));
+ if (byteCount > 0)
+ {
+ stream_.Read(&(*buffer)[0], byteCount);
+ check();
+ if (stream_.LastRead() != byteCount) //some additional check
+ throw FileError(_("Error reading from synchronization database:") + " \n" + "\"" + errorObjName_.c_str() + "\"");
+ }
+ return buffer;
+}
+
+
+template <class T> inline
+void WriteOutputStream::writeNumberC(T number) const //checked write operation
+{
+ writePOD<T>(stream_, number);
+ check();
+}
+
+
+template <class S> inline
+void WriteOutputStream::writeStringC(const S& str) const //checked write operation
+{
+ writeString(stream_, str);
+ check();
+}
+
+
+inline
+void WriteOutputStream::writeArrayC(const std::vector<char>& buffer) const
+{
+ writeNumberC<std::uint32_t>(static_cast<std::uint32_t>(buffer.size()));
+ if (buffer.size() > 0)
+ {
+ stream_.Write(&buffer[0], buffer.size());
+ check();
+ if (stream_.LastWrite() != buffer.size()) //some additional check
+ throw FileError(_("Error writing to synchronization database:") + " \n" + "\"" + errorObjName_.c_str() + "\"");
+ }
+}
+
+
+inline
+void WriteOutputStream::check() const
+{
+ if (stream_.GetLastError() != wxSTREAM_NO_ERROR)
+ throw FileError(_("Error writing to synchronization database:") + " \n" + "\"" + errorObjName_.c_str() + "\"");
+}
+
+}
+
+#endif //SERIALIZE_H_INCLUDED
diff --git a/wx+/shell_execute.h b/wx+/shell_execute.h
new file mode 100644
index 00000000..00faf6ea
--- /dev/null
+++ b/wx+/shell_execute.h
@@ -0,0 +1,115 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef EXECUTE_HEADER_23482134578134134
+#define EXECUTE_HEADER_23482134578134134
+
+#include <wx/msgdlg.h>
+
+#ifdef FFS_WIN
+#include <zen/last_error.h>
+#include <zen/string_tools.h>
+#include <zen/i18n.h>
+#include <zen/win.h> //includes "windows.h"
+
+#elif defined FFS_LINUX
+#include <stdlib.h>
+#include <wx/utils.h>
+#endif
+
+
+namespace zen
+{
+//launch commandline and report errors via popup dialog
+//windows: COM needs to be initialized before calling this function!
+namespace
+{
+enum ExecutionType
+{
+ EXEC_TYPE_SYNC,
+ EXEC_TYPE_ASYNC
+};
+
+void shellExecute(const wxString& command, ExecutionType type = EXEC_TYPE_ASYNC)
+{
+#ifdef FFS_WIN
+ //parse commandline
+ std::vector<std::wstring> argv;
+ {
+ int argc = 0;
+ LPWSTR* tmp = ::CommandLineToArgvW(command.c_str(), &argc);
+ for (int i = 0; i < argc; ++i)
+ argv.push_back(tmp[i]);
+ ::LocalFree(tmp);
+ }
+
+ wxString filename;
+ wxString arguments;
+ if (!argv.empty())
+ {
+ filename = argv[0];
+ for (std::vector<std::wstring>::const_iterator i = argv.begin() + 1; i != argv.end(); ++i)
+ arguments += (i != argv.begin() ? L" " : L"") +
+ (i->empty() || std::find_if(i->begin(), i->end(), &cStringIsWhiteSpace<wchar_t>) != i->end() ? L"\"" + *i + L"\"" : *i);
+ }
+
+ SHELLEXECUTEINFO execInfo = {};
+ execInfo.cbSize = sizeof(execInfo);
+
+ //SEE_MASK_NOASYNC is equal to SEE_MASK_FLAG_DDEWAIT, but former is defined not before Win SDK 6.0
+ execInfo.fMask = type == EXEC_TYPE_SYNC ? (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT) : 0; //don't use SEE_MASK_ASYNCOK -> returns successful despite errors!
+ execInfo.fMask |= SEE_MASK_UNICODE | SEE_MASK_FLAG_NO_UI; //::ShellExecuteEx() shows a non-blocking pop-up dialog on errors -> we want a blocking one
+ execInfo.lpVerb = L"open";
+ execInfo.lpFile = filename.c_str();
+ execInfo.lpParameters = arguments.c_str();
+ execInfo.nShow = SW_SHOWNORMAL;
+
+ if (!::ShellExecuteEx(&execInfo)) //__inout LPSHELLEXECUTEINFO lpExecInfo
+ {
+ wxString errorMsg = _("Invalid command line: %x");
+ wxString cmdFmt = wxString(L"\nFile: ") + filename + L"\nArg: " + arguments;
+
+ errorMsg.Replace(L"%x", cmdFmt);
+ wxMessageBox(errorMsg + L"\n\n" + getLastErrorFormatted());
+ return;
+ }
+
+ if (type == EXEC_TYPE_SYNC)
+ {
+ if (execInfo.hProcess != 0)
+ {
+ ::WaitForSingleObject(execInfo.hProcess, INFINITE);
+ ::CloseHandle(execInfo.hProcess);
+ }
+ }
+
+#elif defined FFS_LINUX
+ if (type == EXEC_TYPE_SYNC)
+ {
+ int rv = ::system(utf8CvrtTo<std::string>(command).c_str()); //do NOT use std::system as its documentation says nothing about "WEXITSTATUS(rv)", ect...
+ if (rv == -1 || WEXITSTATUS(rv) == 127) //http://linux.die.net/man/3/system "In case /bin/sh could not be executed, the exit status will be that of a command that does exit(127)"
+ {
+ wxString errorMsg = _("Invalid command line: %x");
+ replace(errorMsg, L"%x", L"\n" + command);
+ wxMessageBox(errorMsg);
+ return;
+ }
+ }
+ else
+ {
+ // ! unfortunately it seems there is no way on Linux to get a failure notification for calling an invalid command line asynchronously !
+
+ //by default wxExecute uses a zero sized dummy window as a hack to keep focus which leaves a useless empty icon in ALT-TAB list
+ //=> use wxEXEC_NODISABLE and roll our own window disabler! (see comment in app.cpp: void *wxGUIAppTraits::BeforeChildWaitLoop())
+ wxWindowDisabler dummy; //disables all top level windows
+ wxExecute(command, wxEXEC_ASYNC | wxEXEC_NODISABLE);
+ }
+#endif
+}
+}
+}
+
+#endif //EXECUTE_HEADER_23482134578134134
diff --git a/wx+/string_conv.h b/wx+/string_conv.h
new file mode 100644
index 00000000..3f4574ab
--- /dev/null
+++ b/wx+/string_conv.h
@@ -0,0 +1,25 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef STRINGCONV_H_INCLUDED
+#define STRINGCONV_H_INCLUDED
+
+#include <zen/utf8.h>
+#include <wx/string.h>
+#include <zen/zstring.h>
+
+namespace zen
+{
+inline wxString operator+(const wxString& lhs, const char* rhs) { return wxString(lhs) += utf8CvrtTo<wxString>(rhs); }
+inline wxString operator+(const wxString& lhs, const Zstring& rhs) { return wxString(lhs) += utf8CvrtTo<wxString>(rhs); }
+
+
+//conversion between Zstring and wxString
+inline wxString toWx(const Zstring& str) { return utf8CvrtTo<wxString>(str); }
+inline Zstring toZ(const wxString& str) { return utf8CvrtTo<Zstring>(str); }
+}
+
+#endif // STRINGCONV_H_INCLUDED
diff --git a/wx+/timespan.h b/wx+/timespan.h
new file mode 100644
index 00000000..d11b328e
--- /dev/null
+++ b/wx+/timespan.h
@@ -0,0 +1,166 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef WX_TIMESPAN_CTRL_HEADER_INCLUDED
+#define WX_TIMESPAN_CTRL_HEADER_INCLUDED
+
+#include <wx/textctrl.h>
+#include <wx/datetime.h>
+#include <wx/spinbutt.h>
+#include <wx/sizer.h>
+
+//user friendly time span control
+//- constructor is compatible with a wxTextControl
+//- emits change event: wxEVT_TIMESPAN_CHANGE
+
+namespace zen
+{
+inline
+wxEventType getEventType() //external linkage
+{
+ static wxEventType evt = wxNewEventType();
+ return evt;
+}
+const wxEventType wxEVT_TIMESPAN_CHANGE = getEventType();
+
+
+class TimeSpanCtrl : public wxPanel
+{
+public:
+ TimeSpanCtrl(wxWindow* parent, wxWindowID id,
+ const wxString& value = wxEmptyString,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxTextCtrlNameStr) :
+ wxPanel(parent, id, pos, size, style, name),
+ FORMAT_TIMESPAN(wxT("%H:%M:%S"))
+ {
+ wxBoxSizer* bSizer27 = new wxBoxSizer( wxHORIZONTAL );
+
+ m_textCtrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_CENTRE );
+ bSizer27->Add(m_textCtrl, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
+
+ m_spinBtn = new wxSpinButton(this, wxID_ANY, wxDefaultPosition, wxSize( 20, -1 ), wxSP_ARROW_KEYS );
+ bSizer27->Add(m_spinBtn, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND, 5 );
+
+ SetSizer(bSizer27);
+ Layout();
+
+ //connect events
+ m_spinBtn ->Connect(wxEVT_SCROLL_LINEUP, wxEventHandler (TimeSpanCtrl::OnSpinUp), NULL, this);
+ m_spinBtn ->Connect(wxEVT_SCROLL_LINEDOWN, wxEventHandler (TimeSpanCtrl::OnSpinDown), NULL, this);
+ m_textCtrl->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (TimeSpanCtrl::OnKeyPress), NULL, this);
+ m_textCtrl->Connect(wxEVT_MOUSEWHEEL, wxMouseEventHandler(TimeSpanCtrl::OnMouseAction), NULL, this);
+
+ setValue(0);
+ }
+
+ void setValue(int span) //unit: [s]
+ {
+ wxString newValue;
+ if (span < 0)
+ {
+ newValue += wxT("- ");
+ span = -span;
+ }
+ newValue += wxTimeSpan::Seconds(span).Format(FORMAT_TIMESPAN);
+
+ long pos = m_textCtrl->GetInsertionPoint();
+ pos += newValue.size() - m_textCtrl->GetValue().size();
+
+ m_textCtrl->ChangeValue(newValue);
+ m_textCtrl->SetInsertionPoint(pos);
+
+ wxCommandEvent chgEvent(wxEVT_TIMESPAN_CHANGE);
+ wxPostEvent(this, chgEvent);
+ }
+
+ int getValue() const
+ {
+ wxString textVal = m_textCtrl->GetValue();
+ textVal.Trim(false);
+
+ bool isNegative = false;
+ if (textVal.StartsWith(wxT("-")))
+ {
+ isNegative = true;
+ textVal = textVal.substr(1);
+ }
+ textVal.Trim(false);
+
+ wxDateTime tmp(time_t(0));
+ if (tmp.ParseFormat(textVal, FORMAT_TIMESPAN, wxDateTime(tmp)) == NULL)
+ return 0;
+
+ return (isNegative ? -1 : 1) *
+ (tmp.GetHour () * 3600 +
+ tmp.GetMinute() * 60 +
+ tmp.GetSecond());
+ }
+
+private:
+ void OnSpinUp (wxEvent& event) { spinValue(true); }
+ void OnSpinDown(wxEvent& event) { spinValue(false); }
+
+ void OnKeyPress(wxKeyEvent& event)
+ {
+ const int keyCode = event.GetKeyCode();
+ switch (keyCode)
+ {
+ case WXK_UP:
+ case WXK_NUMPAD_UP:
+ return spinValue(true);
+ case WXK_DOWN:
+ case WXK_NUMPAD_DOWN:
+ return spinValue(false);
+ default:
+ event.Skip();
+ }
+ }
+
+ void OnMouseAction(wxMouseEvent& event)
+ {
+ int delta = event.GetWheelRotation();
+ if (delta > 0)
+ spinValue(true);
+ else if (delta < 0)
+ spinValue(false);
+ else
+ event.Skip();
+ }
+
+ void spinValue(bool up)
+ {
+ wxString textval = m_textCtrl->GetValue();
+ long pos = m_textCtrl->GetInsertionPoint();
+
+ int stepSize = 1;
+ if (pos <= static_cast<long>(textval.size()))
+ {
+ int delimCount = std::count(textval.begin() + pos, textval.end(), wxT(':'));
+ if (delimCount == 1)
+ stepSize = 60; //minute
+ else if (delimCount == 2)
+ stepSize = 3600; //hour
+ }
+
+ if (!up)
+ stepSize *= -1;
+
+ setValue(getValue() + stepSize);
+ }
+
+ wxTextCtrl* m_textCtrl;
+ wxSpinButton* m_spinBtn;
+
+ const wxString FORMAT_TIMESPAN;
+};
+}
+
+
+#endif //WX_TIMESPAN_CTRL_HEADER_INCLUDED
diff --git a/wx+/toggle_button.h b/wx+/toggle_button.h
new file mode 100644
index 00000000..98a39e32
--- /dev/null
+++ b/wx+/toggle_button.h
@@ -0,0 +1,94 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef TOGGLEBUTTON_H_INCLUDED
+#define TOGGLEBUTTON_H_INCLUDED
+
+#include <wx/bmpbuttn.h>
+
+class ToggleButton : public wxBitmapButton
+{
+public:
+ ToggleButton(wxWindow* parent,
+ wxWindowID id,
+ const wxBitmap& bitmap,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxDefaultSize,
+ long style = 0,
+ const wxValidator& validator = wxDefaultValidator,
+ const wxString& name = wxButtonNameStr) :
+ wxBitmapButton(parent, id, bitmap, pos, size, style, validator, name),
+ active(false)
+ {
+ SetLayoutDirection(wxLayout_LeftToRight); //avoid mirroring RTL languages like Hebrew or Arabic
+ }
+
+ void init(const wxBitmap& activeBmp,
+ const wxString& activeTooltip,
+ const wxBitmap& inactiveBmp,
+ const wxString& inactiveTooltip);
+
+ void setActive(bool value);
+ bool isActive() const { return active; }
+ void toggle() { setActive(!active); }
+
+private:
+ bool active;
+
+ wxBitmap m_activeBmp;
+ wxString m_activeTooltip;
+
+ wxBitmap m_inactiveBmp;
+ wxString m_inactiveTooltip;
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+//######################## implementation ########################
+inline
+void ToggleButton::init(const wxBitmap& activeBmp,
+ const wxString& activeTooltip,
+ const wxBitmap& inactiveBmp,
+ const wxString& inactiveTooltip)
+{
+ m_activeBmp = activeBmp;
+ m_activeTooltip = activeTooltip;
+ m_inactiveBmp = inactiveBmp;
+ m_inactiveTooltip = inactiveTooltip;
+
+ //load resources
+ setActive(active);
+}
+
+
+inline
+void ToggleButton::setActive(bool value)
+{
+ active = value;
+
+ if (active)
+ {
+ SetBitmapLabel(m_activeBmp);
+ SetToolTip(m_activeTooltip);
+ }
+ else
+ {
+ SetBitmapLabel(m_inactiveBmp);
+ SetToolTip(m_inactiveTooltip);
+ }
+}
+
+#endif // TOGGLEBUTTON_H_INCLUDED
diff --git a/wx+/tooltip.cpp b/wx+/tooltip.cpp
new file mode 100644
index 00000000..9c2587b2
--- /dev/null
+++ b/wx+/tooltip.cpp
@@ -0,0 +1,95 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#include "tooltip.h"
+#include <wx/stattext.h>
+#include <wx/sizer.h>
+#include <wx/statbmp.h>
+#include <wx/settings.h>
+
+using namespace zen;
+
+
+class Tooltip::PopupFrameGenerated : public wxFrame
+{
+public:
+ PopupFrameGenerated(wxWindow* parent,
+ wxWindowID id = wxID_ANY,
+ const wxString& title = wxEmptyString,
+ const wxPoint& pos = wxDefaultPosition,
+ const wxSize& size = wxSize( -1, -1 ),
+ long style = wxFRAME_NO_TASKBAR | wxSTAY_ON_TOP | wxSTATIC_BORDER) : wxFrame(parent, id, title, pos, size, style)
+ {
+ this->SetSizeHints(wxDefaultSize, wxDefaultSize);
+ this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK)); //both required: on Ubuntu background is black, foreground white!
+ this->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT)); //
+
+ wxBoxSizer* bSizer158;
+ bSizer158 = new wxBoxSizer(wxHORIZONTAL);
+
+ m_bitmapLeft = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, 0);
+ bSizer158->Add(m_bitmapLeft, 0, wxALL | wxALIGN_CENTER_VERTICAL, 5);
+
+ m_staticTextMain = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
+ bSizer158->Add(m_staticTextMain, 0, wxALL | wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL, 5);
+
+ this->SetSizer(bSizer158);
+ this->Layout();
+ bSizer158->Fit(this);
+ }
+
+ wxStaticText* m_staticTextMain;
+ wxStaticBitmap* m_bitmapLeft;
+};
+
+
+Tooltip::Tooltip() : tipWindow(new PopupFrameGenerated(NULL)), lastBmp(NULL)
+{
+#ifdef FFS_WIN //neither looks good nor works at all on Linux
+ tipWindow->Disable(); //prevent window stealing focus!
+#endif
+ hide();
+}
+
+
+Tooltip::~Tooltip()
+{
+ tipWindow->Destroy();
+}
+
+
+void Tooltip::show(const wxString& text, wxPoint pos, const wxBitmap* bmp)
+{
+ if (bmp != lastBmp)
+ {
+ lastBmp = bmp;
+ tipWindow->m_bitmapLeft->SetBitmap(bmp == NULL ? wxNullBitmap : *bmp);
+ }
+
+ if (text != tipWindow->m_staticTextMain->GetLabel())
+ {
+ tipWindow->m_staticTextMain->SetLabel(text);
+ tipWindow->m_staticTextMain->Wrap(600);
+ }
+
+#ifdef FFS_LINUX
+ tipWindow->Fit(); //Alas Fit() seems to be somewhat broken => this needs to be called EVERY time inside show, not only if text or bmp change.
+#endif
+
+ if (pos != tipWindow->GetScreenPosition())
+ tipWindow->SetSize(pos.x + 30, pos.y, wxDefaultCoord, wxDefaultCoord);
+ //attention!!! possible endless loop: mouse pointer must NOT be within tipWindow!
+ //Else it will trigger a wxEVT_LEAVE_WINDOW which will hide the window, causing the window to be shown again via this method, etc.
+
+ if (!tipWindow->IsShown())
+ tipWindow->Show();
+}
+
+
+void Tooltip::hide()
+{
+ tipWindow->Hide();
+}
diff --git a/wx+/tooltip.h b/wx+/tooltip.h
new file mode 100644
index 00000000..d9053e2c
--- /dev/null
+++ b/wx+/tooltip.h
@@ -0,0 +1,30 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+
+#ifndef CUSTOMTOOLTIP_H_INCLUDED
+#define CUSTOMTOOLTIP_H_INCLUDED
+
+#include <wx/frame.h>
+
+namespace zen
+{
+class Tooltip
+{
+public:
+ Tooltip();
+ ~Tooltip();
+
+ void show(const wxString& text, wxPoint pos, const wxBitmap* bmp = NULL); //absolute screen coordinates
+ void hide();
+
+private:
+ class PopupFrameGenerated;
+ PopupFrameGenerated* tipWindow;
+ const wxBitmap* lastBmp; //buffer last used bitmap pointer
+};
+}
+
+#endif // CUSTOMTOOLTIP_H_INCLUDED
bgstack15