diff options
Diffstat (limited to 'wx+')
-rw-r--r-- | wx+/app_main.h | 49 | ||||
-rw-r--r-- | wx+/button.cpp | 318 | ||||
-rw-r--r-- | wx+/button.h | 47 | ||||
-rw-r--r-- | wx+/choice_enum.h | 120 | ||||
-rw-r--r-- | wx+/dir_picker.h | 31 | ||||
-rw-r--r-- | wx+/file_drop.h | 113 | ||||
-rw-r--r-- | wx+/format_unit.cpp | 232 | ||||
-rw-r--r-- | wx+/format_unit.h | 64 | ||||
-rw-r--r-- | wx+/graph.cpp | 540 | ||||
-rw-r--r-- | wx+/graph.h | 325 | ||||
-rw-r--r-- | wx+/image_tools.h | 157 | ||||
-rw-r--r-- | wx+/mouse_move_dlg.cpp | 72 | ||||
-rw-r--r-- | wx+/mouse_move_dlg.h | 33 | ||||
-rw-r--r-- | wx+/pch.h | 106 | ||||
-rw-r--r-- | wx+/serialize.h | 274 | ||||
-rw-r--r-- | wx+/shell_execute.h | 115 | ||||
-rw-r--r-- | wx+/string_conv.h | 25 | ||||
-rw-r--r-- | wx+/timespan.h | 166 | ||||
-rw-r--r-- | wx+/toggle_button.h | 94 | ||||
-rw-r--r-- | wx+/tooltip.cpp | 95 | ||||
-rw-r--r-- | wx+/tooltip.h | 30 |
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 |